Signals are sometimes called software interrupts because they allow for the asynchronous execution of certain sections of code. Posix.1 requires that the following signals (minimally) be supported by a Posix-compliant operating system:
- SIGABRT abnormal termination (by abort, for example)
- SIGALRM alarm timer expired
- SIGFPE arithmetic error, e.g. division by zero
- SIGHUP hangup
- SIGILL illegal instruction
- SIGINT terminal interrupt, e.g. ctrl-c
- SIGKILL termination (cannot catch or ignore)
- SIGPIPE write to pipe with no readers
- SIGQUIT terminal quit
- SIGSEGV invalid memory reference
- SIGTERM termination
- SIGUSR1 user-defined signal 1
- SIGUSR2 user-defined signal 2
- Another a group of signals required by Posix implementations provide job control:
- SIGCHLD child process changed status (terminated)
- SIGCONT continue process if stopped
- SIGSTOP stop (cannot catch or ignore)
- SIGTSTP terminal stop
- SIGTTIN background read from terminal
- SIGTTOU background write to terminal
Signals are designed to notify processes of events. The SIGINT signal, for example, is generated in response to a ctrl-C (on most machines), and sent to the foreground process. The process can respond to this event in one of three ways: It can ignore the signal, it can let the OS decide what to do, or it can perform some customized set of actions, such as writing the contents of a cache to disk before it deliberately terminates.
It is up to you, the programmer, to install any signal handling which differs from option two, the default behavior imposed by the operating system, which in most cases is to terminate the process. If you want to ignore a certain signal, say SIGINT, you must tell the OS that the signal handler for SIGINT in your process will be SIG_IGN. This identifier tells the OS not to bother you with SIGINTs. (Note that SIGKILL, the signal sent by typing the command kill -9, cannot be ignored.)
In Posix, the way to tell the operating system how to handle signals sent to your process is by calling the function sigaction, declared in <signal.h>. The prototype of this function is:
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);The sigaction struct looks like this:
struct sigaction { void (*sa_handler)(int[?]); sigset_t sa_mask; int sa_flags; };The following code segment illustrates the use of sigaction to install the function writeCache(int) as the signal handler for SIGINT.
#include <signal.h> #include <iostream.h> void writeCache(int signo) { // ... operations to flush cache to disk ... } // declare a sigaction struct struct sigaction act; // set the signal handler to the writeCache function act.sa_handler=writeCache; // fill the signal set with all signals sigfillset(&act.sa_mask); // no flags act.sa_flags=0; // install the sigaction struct if (sigaction(SIGINT,&act,NULL) < 0) { cout << "could not install sighandler for SIGINT!" << endl; }The sa_mask member of the sigaction struct is a signal set. It contains the set of signals to be blocked while the signal is being handled. In addition to sigfillset, there are functions like sigemptyset, sigaddset, and sigdelset that let you construct an arbitrary set of signals.
The sa_handler member of the sigaction struct is either SIG_IGN, SIG_DFL, or a pointer to a function taking an int argument. In the example it is set to the function writeCache. If sa_handler is SIG_IGN, however, the signal will be ignored. and if it is SIG_DFL, the default action will be taken. (As I already mentioned, this usually means that the process will be terminated or else the signal will be ignored).
The sa_flags member, in a strict Posix program, should be set to a nonzero value only for SIGCHLD (one of the job-control signals), in which case a value of SA_NOCLDSTOP indicates that SIGCHLD should not be generated when child processes stop.
The call to sigaction installs this struct as the rule to follow when the SIGINT event occurs. The third argument to sigaction (a null pointer in this case) can be used to retrieve the previously installed sigaction struct for this signal. Henceforth, when the process receives the SIGINT signal, it will call the function writeCache with argument SIGINT.
If you do not install signal handling, SIG_DFL is assumed for all signals.
Signal handling is an essential part of any but the most trivial UNIX program, and a great deal has been written on the effective use of signals. Stevens and Robbins & Robbins (see References) both provide excellent comprehensive treatments of signals. o