Chapter 12. Signal Processing

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:

  1. Ignore the signal.

  2. Have the kernel run a special part of the process before allowing the process to continue (called catching a signal).

  3. 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.

Signal Concepts

Life Cycle of a Signal

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]

Simple Signals

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 SIGFOO when we talk about a generic signal.

Table 12.1. Signals

Signal

Description

Default Action

SIGABRT

Delivered by abort()

Terminate, core

SIGALRM

An alarm() has expired

Terminate

SIGBUS

Hardware-dependent error

Terminate, core

SIGCHLD

Child process terminated

Ignored

SIGCONT

Process has been continued after being stopped

Ignored

SIGFPE

Arithmetic point exception

Terminate, core

SIGHUP

The process’s controlling tty was closed

Terminate

SIGILL

An illegal instruction was encountered

Terminate, core

SIGINT

User sent the interrupt character (^C)

Terminate

SIGIO

Asynchronous I/O has been received

Terminate

SIGKILL

Uncatchable process termination

Terminate

SIGPIPE

Process wrote to a pipe w/o any readers

Terminate

SIGPROF

Profiling segment ended

Terminate

SIGPWR

Power failure detected

Terminate

SIGQUIT

User sent the quit character (^)

Terminate, core

SIGSEGV

Memory violation

Terminate, core

SIGSTOP

Stops the process without terminating it

Process stopped

SIGSYS

An invalid system call was made

Terminate, core

SIGTERM

Catchable termination request

Terminate

SIGTRAP

Breakpoint instruction encountered

Terminate, core

SIGTSTP

User sent suspend character (^Z)

Process stopped

SIGTTIN

Background process read from controlling tty

Process stopped

SIGTTOU

Background process wrote to controlling tty

Process stopped

SIGURG

Urgent I/O condition

Ignored

SIGUSR1

Process-defined signal

Terminate

SIGUSR2

Process-defined signal

Terminate

SIGVTALRM

setitimer() timer has expired

Terminate

SIGWINCH

Size of the controlling tty has changed

Ignored

SIGXCPU

CPU resource limit exceeded

Terminate, core

SIGXFSZ

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 SIGFOO is sent to a process that is already running a signal handler for SIGFOO? 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.

Reliable Signals

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 SIGFOO while a SIGFOO signal is already pending, only one of those SIGFOO 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]

Signals and System Calls

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:

  1. 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.

  2. 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.

  3. 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.

The Linux (and POSIX) Signal API

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.

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]

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.

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]

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.

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]

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, &currentSet);

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.

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.

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]

Available Signals

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:

SIGABRT

The abort() function sends this signal to the process that called it, terminating the process with a core file. Under Linux, the C library calls abort() whenever an assertion fails.[14]

SIGALRM

Sent when an alarm set by the alarm() system call has expired. Alarms are the basis of the sleep() function discussed in Chapter 18.

SIGBUS

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.

SIGCHLD

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 wait() functions from the signal handler. If the parent always waits for its children to exit before continuing, this signal can be safely ignored. This signal is different from the SIGCLD signal provided by early releases of System V. SIGCLD is obsolete and should not be used anymore.

SIGCONT

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.

SIGFPE

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.

SIGHUP

When a terminal is disconnected, the session leader for the session associated with the terminal is sent a SIGHUP signal unless that terminal’s CLOCAL flag has been set. If a session leader exits, SIGHUP is sent to the process group leader for each process group in the session. Most processes terminate when SIGHUP is received because it indicates the user is no longer present.

 

Many daemon processes interpret SIGHUP as a request to close and reopen log files and reread configuration files.

SIGILL

The process attempted to run an illegal hardware instruction.

SIGINT

This signal is sent to all processes in the foreground process group when the user presses the interrupt character (normally, ^C).

SIGIO

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].

SIGKILL

This signal is generated only by kill() and allows a user to terminate a process unconditionally.

SIGPIPE

The process has written to a pipe that has no readers.

SIGPROF

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]

SIGPWR

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.

SIGQUIT

This signal is sent to all processes in the foreground process group when the user presses the quit character (usually, ^).

SIGSEGV

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.

SIGSTOP

This signal is generated only by kill() and allows a user to unconditionally stop a process. See Chapter 15 for more information on stopping processes.

SIGSYS

When a program attempts a nonexistent system call, the kernel terminates the program with SIGSYS. This should never happen to programs that make system calls through the system’s C library.

SIGTERM

This signal is generated only by kill() and allows a user to gracefully terminate a process. A process should exit as soon as possible after receiving this signal.

SIGTRAP

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.

SIGTSTP

This signal is sent to all processes in the foreground process group when the user presses the suspend character (usually, ^Z). See Chapter 15 for more information on job control.

SIGTTIN

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.

SIGTTOU

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.

SIGURG

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.

SIGUSR1

There is no defined use for this signal; processes may use it for whatever purposes they like.

SIGUSR2

There is no defined use for this signal; processes may use it for whatever purposes they like.

SIGVTALRM

Sent when a timer set by setitimer() has expired. For information on using timers, see Chapter 18.

SIGWINCH

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 SIGWINCH. See page 376 for information on finding the current size of the controlling terminal.

SIGXCPU

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 SIGKILL. For information on process resource limits, see pages 118-119.

SIGXFSZ

When a program exceeds its file size limit, SIGXFSZ is sent to it, which normally kills the process. If the signal is caught, the system call that would have exceeded the file size limit instead returns the error EFBIG. For information on process resource limits, see pages 118-119.

[14] Assertions are discussed in most introductory C books [Kernighan, 1988].

[15] The gprof utility, included with all Linux distributions, is a simple profiler.

Describing Signals

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.

Writing Signal Handlers

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:

  1. 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.

  2. 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.

  3. 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

abort()

accept()

access()

aio_error()

aio_return()

aio_suspend()

alarm()

bind()

cfgetispeed()

cfgetospeed()

cfsetispeed()

cfsetospeed()

chdir()

chmod()

chown()

close()

connect()

creat()

dup()

dup2()

execle()

execve()

_exit()

fchmod()

fchown()

fcntl()

fdatasync()

fork()

fpathconf()

fstat()

fsync()

getegid()

geteuid()

getgid()

getgroups()

getpeername()

getpgrp()

getpid()

getppid()

getuid()

kill()

link()

listen()

lseek()

lstat()

mkdir()

mkfifo()

open()

pathconf()

pause()

pipe()

poll()

posix_trace_event()

pselect()

raise()

read()

readlink()

recv()

recvfrom()

recvmsg()

rename()

rmdir()

select()

sem_post()

send()

sendmsg()

sendto()

setgid()

setpgid()

setsid()

setsockopt()

setuid()

shutdown()

sigaction()

sigaddset()

sigdelset()

sigemptyset()

sigfillset()

sigismember()

signal()

sigpause()

sigpending()

sigprocmask()

sigqueue()

sigset()

sigsuspend()

sleep()

socket()

socketpair()

stat()

symlink()

sysconf()

tcdrain()

tcflow()

tcflush()

tcgetattr()

tcgetpgrp()

tcsendbreak()

tcsetattr()

tcsetpgrp()

time()

timer_getoverrun()

timer_gettime()

timer_settime()

times()

umask()

uname()

unlink()

utime()

wait()

wait3()

wait4()

waitpid()

write()

Reopening Log Files

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.

Real-Time Signals

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.

Signal Queueing and Ordering

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.

Learning About a Signal

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.

Getting a Signal’s Context

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:

SI_USER

A user-space application used kill() to send the signal.[23]

SI_QUEUE

Auser-space application used sigqueue() to send the signal, which is discussed on page 237.

SI_TKILL

The signal was sent by a user-space application using the tkill() system call. While the Linux kernel uses SI_TKILL, its value is not specified in current versions of the C library. If you need to check for SI_TKILL, use the following code segment to define its value:

#ifndef SI_TKILL
#define SI_TKILL -6
#endif
 

SI_TKILL is not specified by any standard (but it is allowed by them), so it needs to be used with care in portable programs.

SI_KERNEL

The signal was generated by the kernel.[24]

[23] The sigsend() function, which Linux includes for compatibility with some other Unix systems, also causes SI_USER.

[24] There are more values for si_code than we talk about here, related to features like asynchronous I/O, message queues, and real-time timers, which are outside the scope of this book.

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

si_code

Description

SIGILL

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

SIGFPE

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

SIGSEGV

SEGV_MAPERR

Address not mapped in an object

 

SEGV_ACCERR

Invalid permissions for address

SIGBUS

BUS_ADRALN

Invalid address alignment

 

BUS_ADRERR

Nonexistent physical address

 

BUS_OBJERR

Object specific hardware error

SIGCHLD

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: }

Sending Data with a Signal

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).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset