12.2. The Linux (and POSIX) Signal API 12.2.1. Sending Signals Sending signals from one process to another is normally done through the kill() system call. This system call is discussed in detail on page 129. A variant of kill() is tkill(), which is not intended to be used directly by programs. int tkill(pid_t pid, int signum); There are two differences between kill() and tkill().[8] First, the pid must be a positive number; tkill() cannot be used to send signals to groups of processes like kill() can. The other difference allows signal handlers to detect whether kill() or tkill() was used to generate the signal; see page 232 for details. [8] There are some other differences between the two relating to multithreaded programs, which we do not cover in this book. The raise() function, which is how ANSI/ISO C specifies signal generation, uses the tkill() system call to generate the signal on Linux systems. int raise(int signum); The raise() function sends the signal specified by signum to the current process.[9] [9] It actually sends the signal to the current thread of the current process. 12.2.2. Using sigset_t Most POSIX signal functions take a set of signals as one of the parameters (or as part of one of the parameters). The sigset_t data type is used to represent a signal set, and it is defined in <signal.h>. POSIX defines five functions for manipulating signal sets: #include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); int sigismember(const sigset_t *set, int signum); int sigemptyset(sigset_t *set); Makes the signal set pointed to by set empty (no signals are present in the set). int sigfillset(sigset_t *set); Includes all available signals in set. int sigaddset(sigset_t *set, int signum); Adds signal signum to set. int sigdelset(sigset_t *set, int signum); Removes signal signum from set. int sigismember(const sigset_t *set, int signum); Returns non-0 if signal signum is in set, 0 otherwise. The only way any of these functions can return an error is if their signum parameter is an invalid signal. In that case, they return EINVAL. Needless to say, this should never happen. 12.2.3. Catching Signals Rather than use the signal() function (whose semantics were already irregular because of its evolution), POSIX programs register signal handlers through sigaction(). #include <signal.h> int sigaction(int signum, struct sigaction * act, struct sigaction * oact); This system call sets the handler for signal signum as defined by act. If oact is not NULL, it is set to describe the disposition of the signal before sigaction() was called. If act is NULL, the current signal disposition is left unchanged, allowing a program to discover the current disposition for a signal without modifying it. sigaction() returns 0 on success, non-0 on error. Errors occur only if one or more of the parameters passed to sigaction() are invalid. The kernel's handling of a signal is fully described by struct sigaction. #include <signal.h> struct sigaction { __sighandler_t sa_handler; sigset_t sa_mask; int sa_flags; }; sa_handler is a pointer to a function with the following prototype: void handler(int signum); where signum is set to the signal number that caused the function to be invoked. sa_handlers can point to a function of this type, or contain SIG_IGN or SIG_DFL. A program also specifies a set of signals that should be blocked while the signal handler is being run. If a signal handler is designed to handle several different signals (which the signum parameter makes easy to do), this feature is essential to prevent race conditions. The sa_mask is a signal set that includes all the signals that should be blocked when the handler for the signal is invoked. However, the signal that is being delivered is blocked no matter what sa_mask contains if you do not want it blocked, specify this through the sa_flags member of struct sigaction. The sa_flags member lets the process modify various signal behaviors. It consists of one or more flags bitwise OR'ed together.[10] [10] The flags given are those defined by the Single Unix Specification. Many of these have other names that are described in the text. SA_NOCLDSTOP | Normally, SIGCHLD is generated when one of a process's children has terminated or stopped (that is, whenever wait4() would return status information on the process). If SA_NOCLDSTOP has been specified for the SIGCHLD signal, the signal is generated only when a child process has terminated; children stopped do not cause any signal. SA_NOCLDSTOP has no effect on any other signal. | SA_NODEFER | When the process's signal handler is invoked, the signal is not automatically blocked. Using this flag results in unreliable signals and should be used only to emulate unreliable signals for applications that depend on this behavior. It is identical to System V's SA_NOMASK flag. | SA_RESETHAND | When this signal is sent, the signal handler is reset to SIG_DFL. This flag allows the ANSI/ISO C signal() function to be emulated in a user-space library. This is identical to System V's SIG_ONESHOT flag. | SA_RESTART | When the signal is sent to the process while it is executing a slow system call, the system call is restarted after the signal handler returns. If this flag is not specified, the system call instead returns an error and sets errno to EINTR. |
12.2.4. Manipulating a Process's Signal Mask It is common for a signal handler to manipulate data structures that are used in other parts of the program. Unfortunately, the asynchronous nature of signals makes this dangerous unless it is done carefully. Manipulating all but the most simple of data structures subjects a program to race conditions. An example should make this problem a bit more clear. Here is a simple SIGHUP handler that changes the value of a string pointed to by the global variable someString: void handleHup(int signum) { free(someString); someString = strdup("a different string"); } In real-world programs, the new value for someString would probably be read from an external source (such as a FIFO), but the same concepts apply. Now assume the main part of a program is copying a string (this code is similar to the code in a strcpy() implementation, although not very optimized) when a SIGHUP signal arrives: src = someString; while (*src) *dest++ = *src++; When the main part of the program resumes execution, src will be pointing to memory that was freed by the signal handler. Needless to say, this is a very bad idea.[11] [11] Although referencing memory that has been freed may work on some systems, it is not portable. Some malloc() implementations return memory to the operating system, which causes referencing the returned memory to cause a segmentation fault; others overwrite portions of the freed memory with bookkeeping information. To solve this type of problem, the POSIX signal API allows a process to block an arbitrary set of signals from being delivered to the process. The signals are not thrown away their delivery is delayed until the process indicates it is willing to handle those signals by unblocking them. To make the string copy shown earlier legal, the program would have to block SIGHUP before the string copy and unblock it afterward. After discussing the interface for manipulating the signal mask, we present a proper version of the code. The set of signals that a process is currently blocking is often called the process's signal mask. The signal mask for a process is a sigset_t that contains the signals currently being blocked. The sigprocmask() function allows a process to control its current signal mask. #include <signal.h> int sigprocmask(int what, const sigset_t * set, sigset_t * oldset); The first parameter, what, describes how the signal mask is to be manipulated. If set is NULL, what is ignored. SIG_BLOCK | The signals in set are added to the current signal mask. | SIG_UNBLOCK | The signals in set are removed from the current signal mask. | SIG_SETMASK | Precisely the signals in set are blocked the rest are unblocked. |
In all three cases, the sigset_t pointed to by oldset is set to the original signal mask unless oldset is NULL, in which case oldset is ignored. The following finds the current signal mask for the running process: sigprocmask(SIG_BLOCK, NULL, ¤tSet); The sigprocmask() system call allows us to fix the code presented earlier, which was afflicted by a race condition. All we need to do is block SIGHUP before we copy the string and unblock it afterward. The following change renders the code safe: sigset_t hup; sigemptyset(&hup); sigaddset(&hup, SIGHUP); sigprocmask(SIG_BLOCK, &hup, NULL); src = someString; while (*src) *dest++ = *src++; sigprocmask(SIG_UNBLOCK, &hup, NULL); The complexity of making signal handlers safe from race conditions should encourage you to keep your signal handlers as simple as possible. 12.2.5. Finding the Set of Pending Signals It is easy to find out which signals are currently pending (signals that need to be delivered, but are currently blocked). #include <signal.h> int sigpending(sigset_t * set); On return, the sigset_t pointed to by set contains the signals currently pending. 12.2.6. Waiting for Signals When a program is built primarily around signals, it is often designed to wait for a signal to occur before continuing. The pause() system call provides a simple way of doing this. #include <unistd.h> int pause(void); pause() does not return until after a signal has been delivered to the process. If a signal handler is present for that signal, the signal handler is run before pause() returns. pause() always returns -1 and sets errno to EINTR. The sigsuspend() system call provides an alternate method of waiting for a signal call. #include <signal.h> int sigsuspend(const sigset_t * mask); Like pause(), sigsuspend() suspends the process until a signal has been received (and processed by a signal handler, if one is available), returning -1 and setting errno to EINTR. Unlike pause(), sigsuspend() temporarily sets the process's signal mask to the value pointed to by the mask parameter before waiting for a signal to occur. Once the signal occurs, the signal mask is restored to the value it had before sigsuspend() was called. This allows a process to wait for a particular signal to occur by blocking all other signals.[12] [12] Using sigprocmask() and pause() to get this behavior presents a race condition if the signal that is being waited for occurs between the two system calls. |