Signals are the simplest form of interprocess communication in the POSIX world. They allow a process to be asynchronously interrupted by another process (or by the kernel) to handle some event. Once the signal has been handled, the interrupted process resumes from the point of interruption. Signals are used for tasks such as terminating processes and telling daemons to reread their configuration file.
Signals have always been an integral part of Unix. The kernel uses them to inform a process of a variety of events, including:
The death of one of the process’s children.
An alarm set by the process has expired.
The size of the terminal window has changed.
All of these messages share an important property: They are all asynchronous. The process has no control over when one of its children exits—it could happen at any point during the parent’s execution. Each of these events causes a signal to be sent to the process. When a process is signaled, the process can do one of three things:
Ignore the signal.
Have the kernel run a special part of the process before allowing the process to continue (called catching a signal).
Let the kernel invoke its default action, which depends on the particular signal being sent.
Conceptually, this is fairly straightforward. However, the age of the signal feature shows when you compare the different signal interfaces that are supported by the various flavors of Unix. BSD, System V, and System 3 supported different and incompatible signal APIs. POSIX defined a standard that is now supported by nearly all versions of Unix (including Linux), which was then extended to support new signal semantics (such as signal queuing) as part of the POSIX Real Time Signal definition. This chapter discusses the original implementation of Unix signals before explaining the base POSIX API and its Real Time Signal extensions, as many of the features in the POSIX API were motivated by weaknesses in earlier signal implementations.
Signals have a well-defined life; they are created, they are stored until the kernel can take an action based on the signal, and then they cause an action to occur. Creating a signal is variously called raising, generating, or sending a signal. Normally, a process sends a signal to another process while the kernel generates a signal to send to a process. When a process sends itself a signal, it is often called raising the signal. These terms are not used with complete consistency, however.
During the time between a signal being sent and the signal causing an action to occur, the signal is called pending. This means that the kernel knows the signal needs to be handled, but it has not yet had the chance to do so. Once the signal is given to the target process, the signal has been delivered. If delivering the signal causes a special piece of code (a signal handler) to be run, the signal has been caught. There are ways for a process to prevent asynchronous delivery of a signal but still handle the signal (through the sigwait()
system call, for example). When this happens, the signal has been accepted.
To help keep things clear, we use this set of terminology throughout the book.[1]
Originally, handling signals was simple. The signal()
system call was used to tell the kernel how to deliver a particular signal to the process:
#include <signal.h> void * signal(int signum, void * handler);
signum
is the signal to handle, and handler
defines the action to perform when the signal is delivered. Normally, the handler is a pointer to a function that takes no parameters and returns no value. When the signal is delivered to the process, the kernel executes the handler function as soon as possible. Once the function returns, the kernel resumes process execution wherever it was interrupted. System-level engineers will recognize this type of signal mechanism as analogous to hardware interrupt delivery; interrupts and signals are very similar and present many of the same problems.
There are many signal numbers available. Table 12.1 on page 217 lists all the non-real-time signals Linux currently supports. They have symbolic names that begin with SIG
, and we use SIG
FOO
when we talk about a generic signal.
Table 12.1. Signals
Signal | Description | Default Action |
---|---|---|
| Delivered by | Terminate, core |
| An | Terminate |
| Hardware-dependent error | Terminate, core |
| Child process terminated | Ignored |
| Process has been continued after being stopped | Ignored |
| Arithmetic point exception | Terminate, core |
| The process’s controlling tty was closed | Terminate |
| An illegal instruction was encountered | Terminate, core |
| User sent the interrupt character ( | Terminate |
| Asynchronous I/O has been received | Terminate |
| Uncatchable process termination | Terminate |
| Process wrote to a pipe w/o any readers | Terminate |
| Profiling segment ended | Terminate |
| Power failure detected | Terminate |
| User sent the quit character ( | Terminate, core |
| Memory violation | Terminate, core |
| Stops the process without terminating it | Process stopped |
| An invalid system call was made | Terminate, core |
| Catchable termination request | Terminate |
| Breakpoint instruction encountered | Terminate, core |
| User sent suspend character ( | Process stopped |
| Background process read from controlling tty | Process stopped |
| Background process wrote to controlling tty | Process stopped |
| Urgent I/O condition | Ignored |
| Process-defined signal | Terminate |
| Process-defined signal | Terminate |
|
| Terminate |
| Size of the controlling tty has changed | Ignored |
| CPU resource limit exceeded | Terminate, core |
| File-size resource limit exceeded | Terminate, core |
The handler
can take on two special values, SIG_IGN
and SIG_DFL
(both of which are defined through <signal.h>
). If SIG_IGN
is specified, the signal is ignored; SIG_DFL
tells the kernel to perform the default action for the signal, usually killing the process or ignoring the signal. Two signals, SIGKILL
and SIGSTOP
, cannot be caught. The kernel always performs the default action for these two signals, killing the process and stopping the process, respectively.
The signal()
function returns the previous signal handler (which could have been SIG_IGN
or SIG_DFL
). Signal handlers are preserved when new processes are created by fork()
, and any signals that are set to SIG_IGN
remain ignored after an exec()
.[2] All signals not being ignored are set to SIG_DFL
after an exec()
.
All this seems simple enough until you ask yourself: What will happen if SIG
FOO
is sent to a process that is already running a signal handler for SIG
FOO
? The obvious thing for the kernel to do is interrupt the process and run the signal handler again. This creates two problems. First, the signal handler must function properly if it is invoked while it is already running. Although this may be easy, signal handlers that manipulate program-wide resources, such as global data structures or files, need to be written very carefully. Functions that behave properly when they are called in this manner are called reentrant functions.[3]
The simple locking techniques that are sufficient to coordinate data access between concurrent processes do not allow reentrancy. For example, the file-locking techniques presented in Chapter 13 cannot be used to allow a signal handler that manipulates a data file to be reentrant. When the signal handler is called the first time, it can lock the data file just fine and begin writing to it. If the signal handler is then interrupted by another signal while it holds the lock, the second invocation of the signal handler cannot lock the file, because the first invocation holds the lock. Unfortunately, the invocation that holds the lock is suspended until the invocation that wants the lock finishes running.
The difficulty of writing reentrant signal handlers is a major reason for the kernel not to deliver signals that a process is already handling. Such a model also makes it difficult for processes to handle a large number of signals that are being sent to the process very rapidly. As each signal results in a new invocation of the signal handler, the process’s stack grows without bound, despite the program itself being well-behaved.
The first solution to this problem was ill-conceived. Before the signal handler was invoked, the handler for that signal was reset to SIG_DFL
and the signal handler was expected to set a more appropriate signal disposition as soon as it could. Although this did simplify writing signal handlers, it made it impossible for a developer to handle signals in a reliable fashion. If two occurrences of the same signal occurred quickly, the kernel handled the second signal in the default fashion. That meant that the second signal was ignored (and lost forever) or the process was terminated. This signal implementation is known as unreliable signals because it makes it impossible to write well behaved signal handlers.
Unfortunately, this is exactly the signal model used in the ANSI/ISO C standard.[4] Although reliable signal APIs that fix these shortcomings are widespread, ANSI/ISO’s unreliable standardization of the signal()
function will probably be around forever.
The implementers of BSD realized that a solution to the multiple signals problem would be to simply wait until the process finishes handling the first signal to deliver the second signal. This ensures that both signals are received and removes the risk of stack overflows. Recall that when the kernel is holding a signal for later delivery, the signal is said to be pending.
However, if a process is sent SIG
FOO
while a SIG
FOO
signal is already pending, only one of those SIG
FOO
signals is delivered to the process. There is no way for a process to know how many times a signal was sent to it, as multiple signals may have been coalesced into one. This is not normally much of a problem, however. As signals do not carry any information other than the signal number with them, sending a signal twice in a very short period of time is usually the same as sending it a single time, so if the program receives the signal only once, it does not matter much. This is different from performing the default action on the second signal (which occurs with unreliable signals).[5]
The idea of a signal’s being automatically blocked has been extended to allow a process to explicitly block signals. This makes it easy to protect critical pieces of code, while still handling all the signals that are sent. Such protection lets the signal handlers manipulate data structures that are maintained by other pieces of the code by providing simple synchronization.
Although BSD provided the basic signal model POSIX adopted, the POSIX standard committee made it simpler for system calls to modify the disposition of groups of signals by introducing new system calls that operate on sets of signals. A set of signals is represented by the data type sigset_t
, and a set of macros is provided to manipulate it.[6]
A signal is often delivered to a process that is waiting for an external event to occur. For instance, a text editor is often waiting for read()
to return input from the terminal. When the system administrator sends the process a SIGTERM
signal (the normal signal sent by the kill
command, allowing a process to terminate cleanly), the process could handle it in a few ways:
It could make no attempt to catch the signal and be terminated by the kernel (the default handling of SIGTERM
). This would leave the user’s terminal in a nonstandard configuration, making it difficult for them to continue.
It could catch the signal, have the signal handler clean up the terminal, and then exit. Although this is appealing, in complex programs it is difficult to write a signal handler that knows enough about what the program was doing when it was interrupted to clean it up properly.
It could catch the signal, set a flag indicating that the signal occurred, and somehow cause the blocked system call (in this case, read()
) to exit with an error indicating something unusual happened. The normal execution pathway could then check for the flag and handle it appropriately.
As the final choice seems much cleaner and easier than the others, the original signal implementation caused slow system calls to return EINTR
when they were interrupted by a signal, whereas fast system calls were completed before the signal was delivered.
Slow system calls take an indeterminate amount of time to complete. System calls that wait for unpredictable resources, such as other processes, network data, or a Homo sapiens to perform some action are considered slow. The wait()
family of system calls, for example, does not normally return until a child process exits. As there is no way to know how long that may take, wait()
is a slow system call. File access system calls are considered slow if they access slow files, and fast if they access fast files.[7]
It was the process’s job to handle EINTR
and restart system calls as necessary. Although this provided all the functionality people needed, it made it more difficult to write code that handled signals. Every time read()
was called on a slow file descriptor, the code had to be modified to check for EINTR
and restart the call, or the code might not perform as expected.
To “simplify” things, 4.2BSD automatically restarted certain system calls (notably read()
and write()
). For the most common operations, programs no longer needed to worry about EINTR
because the system call would continue after the process handled the signal. Later versions of Unix changed which system calls would be automatically restarted, and 4.3BSD allows you to choose whether to restart system calls. The POSIX signal standard does not specify which behavior should be used, but all popular systems agree on how to handle this case. By default, system calls are not restarted, but for each signal, the process can set a flag that indicates that it would like the system to automatically restart system calls interrupted by that signal.
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.
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]
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);
int sigdelset(sigset_t *set, int signum);
Removes signal
signum
fromset
.
int sigismember(const sigset_t *set, int signum);
Returns non-0 if signal
signum
is inset
, 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.
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_handler
s 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]
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]
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.
| The signals in |
| The signals in |
| Precisely the signals in |
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.
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.
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]
Linux has quite a few signals available for processes to use, all of which are summarized in Table 12.1. There are four default actions the kernel can take for a signal: ignore it, stop the process (it is still alive and can be restarted later), terminate the process, or terminate the process and generate a core dump.[13] The following are more detailed descriptions of each signal listed in Table 12.1:
| The |
| Sent when an alarm set by the |
| When a process violates hardware constraints other than those related to memory protections, this signal is sent. This usually occurs on traditional Unix platforms when an un-aligned access occurs, but the Linux kernel fixes unaligned access and continues the process. Memory alignment is discussed further on page 76. |
| This signal is sent to a process when one of that process’s children has exited or stopped. This allows the process to avoid zombies by calling one of the |
| This signal restarts a process that has been stopped. It may also be caught by a process, allowing it to take an action after being restarted. Most editors catch this signal and refresh the terminal when they are restarted. See Chapter 15 for more information on stopping and starting processes. |
| This signal is sent when a process causes an arithmetic exception. All floating-point exceptions, such as overflows and underflows, cause this signal, as does integer division by 0. |
| When a terminal is disconnected, the session leader for the session associated with the terminal is sent a |
Many daemon processes interpret | |
| The process attempted to run an illegal hardware instruction. |
| This signal is sent to all processes in the foreground process group when the user presses the interrupt character (normally, |
| An asynchronous I/O event has occurred. Asynchronous I/O is rarely used and is not documented in this book. We suggest consulting other books for information on using asynchronous I/O [Stevens, 1992]. |
| This signal is generated only by |
| The process has written to a pipe that has no readers. |
| The profile timer has expired. This signal is usually used by profilers, which are programs that examine another process’s run-time characteristics. Profilers are normally used to optimize a program’s execution speed by helping programmers to find execution bottlenecks.[15] |
| The system detected an impending loss of power. It is usually sent to init by a daemon that is monitoring the machine’s power source, allowing the machine to be cleanly shut down before power failure. |
| This signal is sent to all processes in the foreground process group when the user presses the quit character (usually, |
This signal is sent when a process attempts to access memory it is not allowed to access. It is generated when a process tries to read from unmapped memory, execute a page of memory that has not been mapped with execute permissions, or write to memory it does not have write access to. | |
| This signal is generated only by |
| When a program attempts a nonexistent system call, the kernel terminates the program with |
| This signal is generated only by |
| When a process has crossed a breakpoint, this signal is sent to the process. It is usually intercepted by a debugger process that set the breakpoint. |
| This signal is sent to all processes in the foreground process group when the user presses the suspend character (usually, |
| This signal is sent to a background process that has tried to read from its controlling terminal. See Chapter 15 for more information on job control. |
| This signal is sent to a background process that has tried to write to its controlling terminal. See Chapter 15 for more information on job control. |
| This signal is sent when out-of-band data has been received on a socket. Out-of-band data is an advanced networking topic outside the scope of this book; [Stevens, 2004] covers it thoroughly, however. |
There is no defined use for this signal; processes may use it for whatever purposes they like. | |
| There is no defined use for this signal; processes may use it for whatever purposes they like. |
| Sent when a timer set by |
| When a terminal window has changed size, such as when an xterm is resized, all processes in the foreground process group for that process are sent |
| The process has exceeded its soft CPU limit. This signal is sent once per second until the process exceeds its hard CPU limit; once that happens the process is terminated with |
| When a program exceeds its file size limit, |
[14] Assertions are discussed in most introductory C books [Kernighan, 1988]. [15] The gprof utility, included with all Linux distributions, is a simple profiler. |
Occasionally, an application needs a description of a signal to display to the user or put in a log. There are three ways to do this;[16] unfortunately, none of them is standardized.
The oldest method is through sys_siglist
, which is an array of strings describing each signal, indexed by the signal number itself. It includes descriptions for all signals except the real-time signals. Using sys_siglist
is more portable than the other methods described here. BSD systems provide psignal()
, which provides a shortcut for displaying messages. Here is a version of psignal()
:
#include <signal.h> #include <stdio.h> void psignal(int signum, const char * msg) { printf("%s: %s ", msg, sys_siglist[signum]); }
Note that it uses the same list of signals as sys_siglist
, so real-time signals are excluded.
The GNU C library used by Linux provides one more method, strsignal()
. This function is not provided by any standard, so to access the prototype C files need to define _GNU_SOURCE
.
#define _GNU_SOURCE #include <signal.h> char * strsignal(int signum);
Like sys_siglist, strsignal()
provides a description for signal number signum
. It uses sys_siglist
for most signals and constructs a description for the real-time signals. For example, SIGRTMIN + 5
would be described as “Real-time signal 5.” For an example of strsignal()
being used, look at lines 639-648 and 717 of ladsh4.c, which appears in Appendix B.
Although a signal handler looks like a normal C function, it is not called like one. Rather than being run as part of a program’s normal call sequence, signal handlers are called by the kernel. The key difference between the two cases is that a signal handler can be called at almost any time, even in the middle of a single C statement! There are only a few restrictions on when the system will call a signal handler on which you can rely:
The semantics of some signals restrict when they will be sent. SIGCHLD
, for example, is not normally sent to a program that has no children.[17] Most signals are like SIGHUP
, however, and are sent at unpredictable times.
If the process is in the middle of handling a particular signal, the signal handler is not reinvoked to handle the same signal unless the SA_NODEFER
option was specified. The process can also block additional signals when a signal processor is running through the sa_mask
member of struct sigaction
.
The process can block signals while running a part of code through use of sigprocmask()
. Page 215 has an example of using this facility to allow atomic updates to data structures.
Because signal handlers can be run at almost any time, it is important to write them so that they do not make unwarranted assumptions about what the rest of the program is doing at the time and so that they do not rearrange things in a way that could confuse the rest of the program when it starts running again.
One of the most important things to watch is modifying global data. Unless this is done carefully, race conditions result. The easiest way to keep updates of global data safe is simply to avoid them. The next best method is blocking all signal handlers that modify a particular data structure whenever the rest of the code is modifying it, ensuring that only one code segment is manipulating the data at a time.
Although it is safe for the signal handler to read a data structure when it has interrupted another reader of that structure, all other combinations are unsafe. It is no more safe for the signal handler to modify a data structure that the rest of the program is reading than it is for the signal handler to read a data structure the rest of the program is writing. Some specialized data structures have been designed to allow concurrent access, but those data structures are well beyond the scope of this book.
If you must access global data from a signal handler (which most signal handlers end up doing), keep the data structure simple. Although it is pretty easy to safely modify a single data element, such as an int
, more complicated structures usually require blocking signals. Any global variables that a signal handler may modify should be declared with the volatile
keyword. This tells the compiler that the variable may be changed outside the normal flow of the program and it should not try to optimize accesses to the variable.
The other thing to be careful of in signal handlers is calling other functions, as they may modify global data as well! The C stdio library tends to do this quite a bit and should never be used from a signal handler. Table 12.2 lists functions that are guaranteed to be safe to call from a signal handler;[18] all other system functions should be avoided.
Table 12.2. Reentrant Functions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Most system daemons keep log files indicating what they have been busy doing. As many Unix systems stay up for months without interruption, these log files can grow quite large. Simply removing (or renaming) the log files occasionally is not a good solution because the daemons would simply keep writing to the files despite their inaccessibility, and having to stop and start each daemon while the log files are cleaned up would result in system downtime (albeit not much). A common way for daemons to manage this situation is to catch SIGHUP
and reopen their log files. This allows log rotation (periodically starting new log files while keeping the old ones) to happen with a simple shell script like
cd /var/log mv messages messages.old killall -HUP syslogd
Logrotate[19] is one program that takes advantage of this feature to perform safe log rotation.
Including this ability in most daemons is straightforward. One of the easiest approaches is to include a global variable that indicates whether the logs need to be reopened. Then a SIGHUP
signal handler sets this variable whenever it is invoked, and the main part of the program checks the variable as often as possible. The following is an example program that does this:
1: /* sighup.c */ 2: 3: #include <errno.h> 4: #include <signal.h> 5: #include <stdio.h> 6: #include <string.h> 7: #include <unistd.h> 8: 9: volatile int reopenLog = 0; /* volatile as it is modified 10: by a signal handler */ 11: 12: /* write a line to the log */ 13: void logstring(int logfd, char * str) { 14: write(logfd, str, strlen(str)); 15: } 16: 17: /* When SIGHUP occurs, make a note of it and continue. */ 18: void hupHandler(int signum) { 19: reopenLog = 1; 20: } 21: 22: int main() { 23: int done = 0; 24: struct sigaction sa; 25: int rc; 26: int logfd; 27: 28: logfd = STDOUT_FILENO; 29: 30: /* Set up a signal handler for SIGHUP. Use memset() to 31: initialize the struct sigaction to be sure we clear all 32: of it. */ 33: memset(&sa, 0, sizeof(sa)); 34: sa.sa_handler = hupHandler; 35: 36: if (sigaction(SIGHUP, &sa, NULL)) perror("sigaction"); 37: 38: /* Log a message every two seconds, and reopen the log file 39: as requested by SIGHUP. */ 40: while (!done) { 41: /* sleep() returns nonzero if it didn't sleep long enough */ 42: rc = sleep(2); 43: if (rc) { 44: if (reopenLog) { 45: logstring(logfd, 46: "* reopening log files at SIGHUP's request "); 47: reopenLog = 0; 48: } else { 49: logstring(logfd, 50: "* sleep interrupted by unknown signal " 51: "-- dying "); 52: done = 1; 53: } 54: } else { 55: logstring(logfd, "Periodic message "); 56: } 57: } 58: 59: return 0; 60: }
To test this program, run it in one xterm
and send it SIGHUP
from another. For each SIGHUP
the program receives, it prints out a message where it would ordinarily rotate its logs. Remember that if a signal arrives while another instance of the signal is already pending, only one instance of the signal is delivered, so do not send the signals too quickly.
To address some of the limitations of the POSIX signal model, notably the lack of any data attached to the signal and the possibility of multiple signals being collapsed into a single delivery, the POSIX Real Time Signals extension was developed.[20] Systems that support real-time signals, including Linux, also support the traditional POSIX signals mechanism we described earlier. For the highest levels of portability between systems, we suggest using the standard POSIX interfaces unless there is a need for some of the extra features provided by the real-time extension.
Two of the limitations of the standard POSIX signal model are that when a signal is pending sending that signal again does not result in multiple signal deliveries and the lack of ordering guarantees for the delivery of multiple different signals (if you send a SIGTERM
followed by a SIGKILL
there is no way of knowing which one will be delivered first). The POSIX Real Time Signal extensions have added a new set of signals that are not subject to these constraints.
There are a number of real-time signals available, and they are not used by the kernel for any predefined purpose. All of the signals between SIGRTMIN
and SIGRTMAX
are real-time signals, although the exact number of these is not specified by POSIX (Linux provides 32 at the time of writing, but that could be increased in the future).
Real-time signals are always queued; every real-time signal sent to an application is delivered to that application (unless the application terminates before some of the signals have been delivered). The ordering of real-time signals is also well defined. Real-time signals with smaller signal numbers are always delivered before signals with larger signal numbers, and when multiple signals with the same signal number have been queued, they are delivered in the order they were sent. The ordering between non-real-time signals is not defined, nor is the ordering between non-real-time signals and real-time signals.
Here is some sample code to illustrate signal queuing and ordering:
1: /* queued.c */ 2: 3: /* get the definition of strsignal() from string.h */ 4: #define _GNU_SOURCE 1 5: 6: #include <sys/signal.h> 7: #include <stdlib.h> 8: #include <stdio.h> 9: #include <string.h> 10: #include <unistd.h> 11: 12: /* Globals for building a list of caught signals */ 13: int nextSig = 0; 14: int sigOrder[10]; 15: 16: /* Catch a signal and record that it was handled. */ 17: void handler(int signo) { 18: sigOrder[nextSig++] = signo; 19: } 20: 21: int main() { 22: sigset_t mask; 23: sigset_t oldMask; 24: struct sigaction act; 25: int i; 26: 27: /* Signals we're handling in this program */ 28: sigemptyset(&mask); 29: sigaddset(&mask, SIGRTMIN); 30: sigaddset(&mask, SIGRTMIN + 1); 31: sigaddset(&mask, SIGUSR1); 32: 33: /* Send signals to handler() and keep all signals blocked 34: that handler() has been configured to catch to avoid 35: races in manipulating the global variables. */ 36: act.sa_handler = handler; 37: act.sa_mask = mask; 38: act.sa_flags = 0; 39: 40: sigaction(SIGRTMIN, &act, NULL); 41: sigaction(SIGRTMIN + 1, &act, NULL); 42: sigaction(SIGUSR1, &act, NULL); 43: 44: /* Block the signals we're working with so we can see the 45: queuing and ordering behavior. */ 46: sigprocmask(SIG_BLOCK, &mask, &oldMask); 47: 48: /* Generate signals */ 49: raise(SIGRTMIN + 1); 50: raise(SIGRTMIN); 51: raise(SIGRTMIN); 52: raise(SIGRTMIN + 1); 53: raise(SIGRTMIN); 54: raise(SIGUSR1); 55: raise(SIGUSR1); 56: 57: /* Enable delivery of the signals. They'll all be delivered 58: right before this call returns (on Linux; this is NOT 59: portable behavior). */ 60: sigprocmask(SIG_SETMASK, &oldMask, NULL); 61: 62: /* Display the ordered list of signals we caught */ 63: printf("signals received: "); 64: for (i = 0; i < nextSig; i++) 65: if (sigOrder[i] < SIGRTMIN) 66: printf(" %s ", strsignal(sigOrder[i])); 67: else 68: printf(" SIGRTMIN + %d ", sigOrder[i] - SIGRTMIN); 69: 70: return 0; 71: }
This program sends itself a number of signals and records the order the signals arrive in for display. While the signals are being sent, it blocks those signals to prevent them from being delivered immediately. It also blocks the signals whenever the signal handler is being run by setting the sa_mask
member of struct sigaction
when installing the signal handler for each signal. This prevents possible races accessing the global variables nextSig
and sigOrder
from inside the signal handler.
Running this program gives the following results:
signals received: User defined signal 1 SIGRTMIN + 0 SIGRTMIN + 0 SIGRTMIN + 0 SIGRTMIN + 1 SIGRTMIN + 1
This shows that all of the real-time signals were delivered, while only a single instance of SIGUSR1
was delivered. You can also see the reordering of real-time signals, with all of the SIGRTMIN
signals delivered before SIGRTMIN + 1
.
The signals we have discussed so far carry no data with them; the arrival of the signal is the only information the application gets. In some cases it would be nice to know what caused the signal to be sent (such as the illegal memory address that caused a SIGSEGV
) or to be able to include data with application-generated signals. The Real Time Signal extensions address both of these needs.
The information about how and why a signal was generated is called the signal’s context.[21] Applications that wish to view a signal’s context use a different signal handler than the normal one. It includes two more parameters, a pointer to a siginfo_t
, which provides the signal’s context and a pointer to void *
, which can be used by some low-level system libraries.[22] Here is what the full handler prototype looks like:
void handler(int signum, siginfo_t * siginfo, void * context);
Applications need to tell the kernel to pass full context information by setting the SA_SIGINFO
flag in the sa_mask
member of the struct sigaction
used to register the signal handler. The sa_handler
member is also not used, as it is a pointer to a function with a different prototype. Instead, a new member, sa_sigaction
, is set to point to the signal handler with the proper prototype.
To help reduce memory usage, sa_handler
and sa_sigaction
are allowed to use the same region of memory, so only one should be used at a time. To make this transparent, the C library defines struct sigaction
like this:
#include <signal.h> struct sigaction { union { __sighandler_t sa_handler; __sigaction_t sa_sigaction; } __sigaction_handler; sigset_t sa_mask; unsigned long sa_flags; }; #define sa_handler __sigaction_handler.sa_handler #define sa_sigaction __sigaction_handler.sa_sigaction
Using this combination of a union and macros allows the two members to overlap in memory without the data structure getting overly complicated from the application’s view.
The siginfo_t
structure contains information about where and why a signal was generated. Two members are available for all signals: si_signo
and si_code
. Which other members are available depends on the signal being delivered, and those members overlap in memory in a way similar to sa_handler
and sa_sigaction
in struct sigaction
. The si_signo
member contains the signal number that is being delivered and is the same as the first parameter passed to the signal handler, while si_code
specifies why the signal was generated and changes depending on the signal number. For most signals, it is one of the following:
| A user-space application used |
| Auser-space application used |
| The signal was sent by a user-space application using the #ifndef SI_TKILL #define SI_TKILL -6 #endif |
| |
| The signal was generated by the kernel.[24] |
[23] The [24] There are more values for |
When SIGILL, SIGFPE, SIGSEGV, SIGBUS
, and SIGCHLD
are sent by the kernel, si_code
takes on the values shown in Table 12.3 instead of SI_KERNEL
.[25]
Table 12.3. Values of si_code
for Special Signals
Signal |
| Description |
---|---|---|
| ILL_ILLOPC | Illegal opcode |
ILL_ILLOPC | Illegal operand | |
ILL_ILLOPC | Illegal addressing mode | |
ILL_ILLOPC | Illegal trap | |
ILL_ILLOPC | Privileged opcode | |
ILL_ILLOPC | Privileged register | |
ILL_ILLOPC | Internal stack error | |
ILL_ILLOPC | Coprocessor error | |
| FPE_INTDIV | Integer divide by zero |
FPE_INTOVF | Integer overflow | |
FPE_FLTDIV | Floating point divide by zero | |
FPE_FLTOVF | Floating point overflow | |
FPE_FLTUND | Floating point underflow | |
FPE_FLTRES | Floating point inexact result | |
FPE_FLTINV | Floating point invalid operation | |
FPE_FLTSUB | Floating point subscript out of range | |
| SEGV_MAPERR | Address not mapped in an object |
SEGV_ACCERR | Invalid permissions for address | |
| BUS_ADRALN | Invalid address alignment |
BUS_ADRERR | Nonexistent physical address | |
BUS_OBJERR | Object specific hardware error | |
| CLD_EXITED | Child has exited |
CLD_KILLED | Child was killed without a core file | |
CLD_DUMPED | Child was killed and a core file was created | |
CLD_TRAPPED | Child has hit a breakpoint | |
CLD_STOPPED | Child has stopped |
To help clarify the various values si_code
can take, here is an example that generates SIGCHLD
in four different ways: kill(), sigqueue(), raise()
(which uses the tkill()
system call), and by creating a child that immediately terminates.
1: /* sicode.c */ 2: 3: #include <sys/signal.h> 4: #include <stdlib.h> 5: #include <stdio.h> 6: #include <unistd.h> 7: 8: #ifndef SI_TKILL 9: #define SI_TKILL -6 10: #endif 11: 12: void handler(int signo, siginfo_t * info, void * f) { 13: static int count = 0; 14: 15: printf("caught signal sent by "); 16: switch (info->si_code) { 17: case SI_USER: 18: printf("kill() "); break; 19: case SI_QUEUE: 20: printf("sigqueue() "); break; 21: case SI_TKILL: 22: printf("tkill() or raise() "); break; 23: case CLD_EXITED: 24: printf("kernel telling us child exited "); exit(0); 25: } 26: 27: if (++count == 4) exit(1); 28: } 29: 30: int main() { 31: struct sigaction act; 32: union sigval val; 33: pid_t pid = getpid(); 34: 35: val.sival_int = 1234; 36: 37: act.sa_sigaction = handler; 38: sigemptyset(&act.sa_mask); 39: act.sa_flags = SA_SIGINFO; 40: sigaction(SIGCHLD, &act, NULL); 41: 42: kill(pid, SIGCHLD); 43: sigqueue(pid, SIGCHLD, val); 44: raise(SIGCHLD); 45: 46: /* To get a SIGCHLD from the kernel we create a child and 47: have it exit immediately. The signal handler exits after 48: receiving the signal from the kernel, so we just sleep for 49: a while and let the program terminate that way. */ 50: 51: if (!fork()) exit(0); 52: sleep(60); 53: 54: return 0; 55: }
If si_code
is SI_USER, SI_QUEUE
, or SI_TKILL
, two additional members of siginfo_t
are available, si_pid
and si_uid
, which provide the process ID that sent the signal and the real user ID of that process.
When a SIGCHLD
is sent by the kernel, the si_pid, si_status, si_utime
, and si_stime
members are available. The first, si_pid
, gives the pid of the process whose status has changed.[26] Information on the new status is available in both si_code
(as specified by Table 12.3) and si_status
, which is identical to the status integer returned by the wait()
family of functions. The final two members, si_utime
and si_stime
, specify the amount of the time the child application has spent in user space and kernel space, respectively (this is similar to measures wait3()
and wait4()
return in struct
rusage). They are measured in clock ticks, which is an integer. The number of clock ticks per second is defined by the _SC_CLK_TCK
macro, defined in <sysconf.h>
.
SIGSEGV, SIGBUS, SIGILL
, and SIGFPE
all provide si_addr
, which specifies the address that caused the fault described by si_code
.
Here is a simple example of examining a signal’s context. It installs a signal handler for SIGSEGV
that prints out the context for that signal and then terminates the process. A segmentation violation is generated by trying to dereference NULL
.
1: /* catch-segv.c */ 2: 3: #include <sys/signal.h> 4: #include <stdlib.h> 5: #include <stdio.h> 6: 7: void handler(int signo, siginfo_t * info, void * f) { 8: printf("caught "); 9: if (info->si_signo == SIGSEGV) 10: printf("segv accessing %p", info->si_addr); 11: if (info->si_code == SEGV_MAPERR) 12: printf(" SEGV_MAPERR"); 13: printf(" "); 14: 15: exit(1); 16: } 17: 18: int main() { 19: struct sigaction act; 20: 21: act.sa_sigaction = handler; 22: sigemptyset(&act.sa_mask); 23: act.sa_flags = SA_SIGINFO; 24: sigaction(SIGSEGV, &act, NULL); 25: 26: *((int *) NULL) = 1; 27: 28: return 0; 29: }
The siginfo_t
mechanism also allows signals sent from programs to attach a single data element to the signal (this element may be a pointer, allowing an arbitrary amount of data to be passed indirectly). To send data, a union sigval
is used.
#include <signal.h> union sigval { int sival_int; void * sival_ptr; };
Either sival_int
or sival_ptr
may be set to an arbitrary value that is included in the siginfo_t
delivered with the signal. To generate a signal with a union sigval, sigqueue()
must be used.
#include <signal.h> void * sigqueue(pid_t pid, int signum, const union sigval value);
Unlike kill(), pid
must be a valid process ID number (no negative values are allowed). The signum
specifies the signal number to send. Like kill(), sigqueue()
allows signum
to be zero to check whether the calling process is allowed to send the target pid
a signal without actually sending one. The final parameter, value
, provides the datum that is delivered along with the signal.
To receive the union sigval
, the process catching the signal must use SA_SIGINFO
when registering its signal handler with sigaction()
. When the si_code
member of siginfo_t
is SI_QUEUE, siginfo_t
provides a si_value
member that is the same as the value
passed to sigqueue
.
Here is an example of sending data elements with a signal. It queues three SIGRTMIN
signals with different data elements. It demonstrates that the signals were delivered in the same order they were sent, which is what we would expect for queued real-time signals.[27] A more involved example uses signals to monitor changes in directories and is presented on page 319.
1: /* sigval.c */ 2: 3: #include <sys/signal.h> 4: #include <stdlib.h> 5: #include <stdio.h> 6: #include <string.h> 7: #include <unistd.h> 8: 9: /* Catch a signal and record that it was handled. */ 10: void handler(int signo, siginfo_t * si, void * context) { 11: printf("%d ", si->si_value.sival_int); 12: } 13: 14: int main() { 15: sigset_t mask; 16: sigset_t oldMask; 17: struct sigaction act; 18: int me = getpid(); 19: union sigval val; 20: 21: /* Send signals to handler() and keep all signals blocked 22: that handler() has been configured to catch to avoid 23: races in manipulating the global variables. */ 24: act.sa_sigaction = handler; 25: act.sa_mask = mask; 26: act.sa_flags = SA_SIGINFO; 27: 28: sigaction(SIGRTMIN, &act, NULL); 29: 30: /* Block SIGRTMIN so we can see the queueing and ordering */ 31: sigemptyset(&mask); 32: sigaddset(&mask, SIGRTMIN); 33: 34: sigprocmask(SIG_BLOCK, &mask, &oldMask); 35: 36: /* Generate signals */ 37: val.sival_int = 1; 38: sigqueue(me, SIGRTMIN, val); 39: val.sival_int++; 40: sigqueue(me, SIGRTMIN, val); 41: val.sival_int++; 42: sigqueue(me, SIGRTMIN, val); 43: 44: /* Enable delivery of the signals. */ 45: sigprocmask(SIG_SETMASK, &oldMask, NULL); 46: 47: return 0; 48: }
[1] This terminology is also used in much of the standards literature, including the Single Unix Specification.
[2] This is the mechanism used by the nohup
utility.
[3] The need for reentrant functions is not limited to signal handlers. Multithreaded applications must take great care to ensure proper reentrancy and locking.
[4] Well, not exactly. The ANSI/ISO C signal handling model is not as well specified as the one we just presented. It does, however, mandate that signal handlers be reset to SIG_DFL
before a signal is delivered, forcing all ANSI/ISO C signal()
functions to be unreliable.
[5] The POSIX Real Time Signal specification allows some signals to be queued and for signals to carry a limited amount of data, changing this model significantly. Real-time signals are discussed on pages 227-230.
[6] This is similar to the fd_set
type used by the select()
system call discussed in Chapter 13.
[7] The difference between fast files and slow files is the same as the difference between fast and slow system calls and is discussed in more detail on page 167.
[8] There are some other differences between the two relating to multithreaded programs, which we do not cover in this book.
[9] It actually sends the signal to the current thread of the current process.
[10] The flags given are those defined by the Single Unix Specification. Many of these have other names that are described in the text.
[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.
[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.
[13] See page 130 for more information on core dumps.
[16] These methods parallel the ways of getting descriptions for system errors described in Chapter 9.
[17] Although users can send SIGCHLD
to any processes they own, programs are not expected to respond reasonably to unexpected signals.
[18] The table lists functions that may not be present on some, or even any, Linux systems. We have included all of the functions that POSIX specifies as safe to call from signal handlers for completeness.
[20] Real time is a misnomer here, as the extension makes no attempt to provide guarantees on the latency of signal delivery. The features it does add are useful in building soft real-time implementations, however.
[21] Before the POSIX standards, applications could access a struct sigcontext
for the same type of information now provided by siginfo_t
, and the term context has stuck from this older implementation.
[22] This third parameter is actually a pointer to a struct ucontext
, which allows processes to do full context switching in user space. Doing this is beyond the scope of this book, but it is well documented in the Single Unix Specification.
[25] It also takes on special values for SIGTRAP
, which is used by debuggers, and SIGPOLL
, which is used by an unreliable asynchronous I/O mechanism. Neither of these topics is covered in this book, so the details on these signals have been omitted from Table 12.3.
[26] Recall that SIGCHLD
is sent not only when a child has exited, but also when a child has stopped or resumed.
[27] For more examples of signal handling, look at the sample programs for file leases (page 287), tty handling (page 355), and interval timers (page 493).
3.138.126.169