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 requests that the OS post a signal to another process. The OS
handles posting the signal to the target process 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 processes 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 one another, as opposed to other interprocess communication methods such as messaging or shared memory.
Table 1 lists some of the defined signals. See the man page
(man 7 signal
) for additional examples.
Signal Name | Description |
---|---|
|
Segmentation fault (e.g., dereferencing 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 |
|
Notify a process a timer goes off (e.g., |
|
Terminate a process (e.g., |
|
Bus error occurred (e.g., a misaligned memory address to access an |
|
Suspend a process, move to Blocked state (e.g., Ctrl-Z) |
|
Continue a blocked process (move it to the Ready state; e.g., |
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. Application programmers, however, can change the
default action of most signals and can write their own signal handler code. 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 to change
the default behavior of a 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.
Following is an example program that registers
signal handlers for SIGALRM
, SIGINT
, and SIGCONT
signals using
the signal
system call (error handling is removed for readability):
/*
* Example of signal handlers for SIGALRM, 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 SIGALRM */
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(void) {
/* 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, SIGALRM\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 SIGALRM
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
its child processes exit. For example, when a shell program runs a
command in the background, it continues to run concurrently with its
child process, handling 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 for the
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(void) {
/* 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 it 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 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 parent 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
parent is blocked on a call to waitpid
for a specific child, then the parent
may or may not continue to block after its signal handler code runs to reap an
exited child. The parent process continues execution after its call to
waitpid
if the signal handler code reaped the child for which it was
waiting. Otherwise, the parent continues to block on the call to waitpid
to
wait for the specified child to exit. 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.