13.4.1. Signals
A signal is a software interrupt that is sent by one process to another process via the OS. When a process receives a signal, its current execution point is interrupted by the OS to run signal handler code. If the signal handler returns, the process’s execution continues from where it was interrupted to handle the signal. Sometimes the signal handler causes the process to exit, and thus it does not continue its execution from where it left off.
Signals are similar to hardware interrupts and traps, but are different from both. Whereas a trap is a synchronous software interrupt that occurs when a process explicitly invokes a system call, signals are asynchronous — a process may be interrupted by the receipt of a signal at any point in its execution. Signals also differ from asynchronous hardware interrupts in that they are triggered by software rather than hardware devices.
A process can send another process a signal by executing the kill
system
call which makes a request the OS to post a signal to another process. The OS
handles posting a signal to the process target and setting its execution state
to run the signal handler code associated with the particular posted signal.
The name of the |
The OS itself also uses signals to notify process of certain events. For example, the OS posts a SIGCHLD signal to a process when one of its child processes exits.
Systems define a fixed number of signals (e.g., Linux defines 32 different signals). As a result, signals provide a limited way in which processes can communicate with each other, as opposed to other interprocess communication methods such as messaging or shared memory.
The following are some of the defined signals. See the man page
(man 7 signal
) for additional examples:
Signal Name | Description |
---|---|
|
segmentation fault (e.g., dereference a NULL pointer) |
|
interrupt process (e.g., Ctrl-C in terminal window to kill process) |
|
child process has exited (e.g., a child is now a zombie after running exit) |
|
notify a process a timer has gone off (e.g., alarm(2) every 2 secs) |
|
terminate a process (e.g., |
|
a bus error occurred (e.g., a misaligned memory address to access an int value) |
|
suspend a process, move to Blocked state (e.g., Ctrl-Z) |
|
continue a blocked process (move it to the Ready state) (e.g., bg or fg) |
When a process receives a signal, one of several default actions can occur:
-
the process can terminate
-
the signal can be ignored
-
the process can be blocked
-
the process can be unblocked
The OS defines a default action and supplies the default signal handler code for every signal number. An application program, however, can change the default action of most signals and can write its own signal handler code for signals. If an application program doesn’t register its own signal handler function for a particular signal, then the OS’s default handler executes when the process receives a signal. For some signals, the OS defined default action cannot be overridden by application signal handler code. For example, if a process receives a SIGKILL signal, the OS will always force the process to exit, and receiving a SIGSTOP signal will always block the process until it receives a signal to continue (SIGCONT) or to exit (SIGKILL).
Linux supports two different system calls that can be used by to change
the default behavior of signal or to register a signal handler on a
particular signal: sigaction
and signal
. Because sigaction
is POSIX
compliant and more featureful, it should be used in production
software. However, we use signal
in our example code because it is easier
to understand.
Below is an example program that registers
signal handlers for SIGALARM
, SIGINT
, and SIGCONT
signals using
the signal
system call (error handling is removed for readability):
/*
* Example of signal handlers for SIGALARM, SIGINT, and SIGCONT
*
* A signal handler function prototype must match:
* void handler_function_name(int signum);
*
* Compile and run this program, then send this process signals by executing:
* kill -INT pid (or Ctrl-C) will send a SIGINT
* kill -CONT pid (or Ctrl-Z fg) will send a SIGCONT
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
/* signal handler for SIGALARM */
void sigalarm_handler(int sig) {
printf("BEEP, signal number %d\n.", sig);
fflush(stdout);
alarm(5); /* sends another SIGALRM in 5 seconds */
}
/* signal handler for SIGCONT */
void sigcont_handler(int sig) {
printf("in sigcont handler function, signal number %d\n.", sig);
fflush(stdout);
}
/* signal handler for SIGINT */
void sigint_handler(int sig) {
printf("in sigint handler function, signal number %d...exiting\n.", sig);
fflush(stdout);
exit(0);
}
/* main: register signal handlers and repeatedly block until receive signal */
int main() {
/* Register signal handlers. */
if (signal(SIGCONT, sigcont_handler) == SIG_ERR) {
printf("Error call to signal, SIGCONT\n");
exit(1);
}
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
printf("Error call to signal, SIGINT\n");
exit(1);
}
if (signal(SIGALRM, sigalarm_handler) == SIG_ERR) {
printf("Error call to signal, SIGALARM\n");
exit(1);
}
printf("kill -CONT %d to send SIGCONT\n", getpid());
alarm(5); /* sends a SIGALRM in 5 seconds */
while(1) {
pause(); /* wait for a signal to happen */
}
}
When run, the process receives a SIGALARM every 5
seconds (due to the call to alarm
in main
and sigalarm_handler
). The
SIGINT and SIGCONT signals can be triggered by running
the kill
or pkill
commands in another shell. For example, if the
process’s PID is 1234
and its executable is a.out
, then the
following shell command sends the process SIGINT and SIGCONT signals,
triggering their signal handler functions to run:
pkill -INT a.out
kill -INT 1234
pkill -CONT a.out
kill -CONT 1234
Writing a SIGCHLD
handler
Recall that when a process terminates, the OS delivers a SIGCHLD
signal
to its parent process. In programs that create child processes, the
parent process does not always want to block on a call to wait
until
their child processes exit. For example, when a shell program runs a
command in the background, it continues to run concurrently with its
child process, continuing to handle other shell commands in the foreground
as the child process runs in the background. A parent process,
however, needs to call wait
to reap its zombie child processes after
they exit. If not, the zombie processes will never die and will continue
to hold on to some system resources. In these cases, the parent process can
register a signal handler on SIGCHLD signals. When the parent receives
a SIGCHLD from an exited child process, its handler code runs and
makes calls to wait
to reap its zombie children.
Below is a code snippet showing the implementation of a signal handler
function for SIGCHLD signals. This snippet also shows parts of
a main
function that register the signal handler function on
SIGCHLD signal (note that this should be done before any calls to fork
):
/*
* signal handler for SIGCHLD: reaps zombie children
* signum: the number of the signal (will be 20 for SIGCHLD)
*/
void sigchld_handler(int signum) {
int status;
pid_t pid;
/*
* reap any and all exited child processes
* (loop because there could be more than one)
*/
while( (pid = waitpid(-1, &status, WNOHANG)) > 0) {
/* uncomment debug print stmt to see what is being handled
printf("signal %d me:%d child: %d\n", signum, getpid(), pid);
*/
}
}
int main() {
/* register SIGCHLD handler: */
if ( signal(SIGCHLD, sigchild_handler) == SIG_ERR) {
printf("ERROR signal failed\n");
exit(1);
}
...
/* create a child process */
pid = fork();
if(pid == 0) {
/* child code...maybe call execvp */
...
}
/* the parent continues executing concurrently with child */
...
The example above passes -1
as the PID to waitpid
, which means "reap any
zombie child process". It also passes the WNOHANG
flag, which means that the
call to waitpid
does not block if there are no zombie child processes to reap.
Also note that waitpid
is called inside a while loop that continues as long
as it returns a valid PID value (as long as a reaps a zombie child process).
It is important that the signal handler function calls waitpid
in a loop
because as it is running, the process could receive additional SIGCHLD signals
from other exited child process. The OS doesn’t keep track of the number of
SIGCHLD signals a process receives, it just notes that the process it received
a SIGCHLD and interrupts its execution to run the handler code. As a result,
without the loop, the signal handler could miss reaping some zombie children.
The signal handler executes whenever the parent receives
a SIGCHLD
signal, regardless of whether the parent is blocked on a
call to wait
or waitpid
. If the process is blocked on a call
to wait
when it receives a SIGCHLD
, it wakes-up and runs the
signal handler code to reap one or more of its zombie children. It
then continues execution at the point in the program after the call to
wait
(it just reaped an exited child process). If, however, the
process is blocked on a call to waitpid
for a specific PID, then
the parent will continue to block after the signal handler code
executes if the child processes it reaped in its signal handler did
not include the specific child’s exit on which the parent is waiting.
The parent process continues execution after its
call to waitpid
if the signal handler code reaped the process for which
it was waiting. A call to waitpid
with a PID of a non-existent child
process (perhaps one that was previously reaped in the signal handler
loop), does not block the caller.