A Process can be interrupted with various types of signals. This interruption can occur in the middle of most code.
Purely implemented in software, supported by the hardware mentioned above. The operating system forwards the signal to the correct process.
- Signals apply to a process. The kernel (aka the OS) or some other process delivers that signal.
- Each signal has a different meaning, number, and way of handling it.
Every signal has a current disposition (aka handler), or the “natural” way a process responds to the arrival of a signal. We can also define custom signal dispositions by writing our own signal handler.
Signals can interrupt other signals.
Changing how a signal is handled
int sigaction(int signum, struct sigaction* act, struct sigaction* old);
Specify the signal number, gives the signal a new handler, and contains the previous behavior as an output parameter in old
.
SIGKILL
and SIGSTOP
cannot have their handler changed and therefore always perform their default behaviors, which is… to kill and stop the process respectively.
Signal handlers
A function that takes in a parameter, the signal number that raised the handler, return type is void.
- Automatically called when process is interrupted by signal
- Can manipulate global state
- Signal handlers set by a process will be retained by children
Making changes to handlers and signal masks within a signal handler
Note that the OS implicitly resets the signal disposition and signal block mask to what it was before the handler was invoked. So after we exit the handler, any changes are not saved!
For more special behavior related to triggering handlers, see below.
Sending signals
int kill(pid_t pid, int sig);
Despite its name, this call allows us to send specific signals to a specific process (not just SIGKILL
).
Sending signals between processes
Signals are sent from process to process via the operating system. It would maybe be more efficient for a process to directly send the signal, but this poses major security concerns.
The OS enforces whether a process has the permission required to send a signal. Operating systems are a protection system for processes.
Example
Would it make sense to allow Discord to send
SIGKILL
to Chrome?
Signal blocking and signal masks
A process maintains a set of signals called a signal mask. Signals in that set/mask are “blocked” meaning they’re delayed in being delivered to the process. Once unblocked, the process responds to the signals accordingly.
A signal mask is implemented as a C bitset. It is literally a set; sending multiple of the same blocked signal only results in the handler being called once after it’s unblocked.
By default, the signal that triggers the handler is also blocked until we finish executing the handler and exit. This can be changed with the flag SA_NODEFER
.
sigset_t
Fortunately, someone has already implemented this signal mask for us! sigset_t
is a typedef
type that represents a signal bitset that we can manipulate.
Important
A
sigset_t
variable must first be initialized by callingsigemptyset()
before being used.
We manipulate signal sets (adding signals, removing them, etc.) using sigprocmask()
.
- To see the current signal mask, we can pass in
NULL
for the new set, and call it like so:sigprocmask(0, NULL, &previous_set)
. This gives us the current mask inprevious_set
.
Masking a function
In critical sections we don’t want signals to interrupt functions in the middle of execution. We can update the signal mask so signals are blocked before we start running function code.
- Set up appropriate mask
- Block those signals (in addition to those perhaps previously blocked), and store the previous signal mask somewhere
- Set the mask using
sigprocmask()
- After we finish executing function body, we restore the previous signal mask.