[ Team LiB ] Previous Section Next Section

5.8 POSIX Signal Handling

A signal is a notification to a process that an event has occurred. Signals are sometimes called software interrupts. Signals usually occur asynchronously. By this we mean that a process doesn't know ahead of time exactly when a signal will occur.

Signals can be sent

  • By one process to another process (or to itself)

  • By the kernel to a process

The SIGCHLD signal that we described at the end of the previous section is one that is sent by the kernel whenever a process terminates, to the parent of the terminating process.

Every signal has a disposition, which is also called the action associated with the signal. We set the disposition of a signal by calling the sigaction function (described shortly) and we have three choices for the disposition:

  1. We can provide a function that is called whenever a specific signal occurs. This function is called a signal handler and this action is called catching a signal. The two signals SIGKILL and SIGSTOP cannot be caught. Our function is called with a single integer argument that is the signal number and the function returns nothing. Its function prototype is therefore

    
    
    void handler (int signo);
    

    For most signals, calling sigaction and specifying a function to be called when the signal occurs is all that is required to catch a signal. But we will see later that a few signals, SIGIO, SIGPOLL, and SIGURG, all require additional actions on the part of the process to catch the signal.

  2. We can ignore a signal by setting its disposition to SIG_IGN. The two signals SIGKILL and SIGSTOP cannot be ignored.

  3. We can set the default disposition for a signal by setting its disposition to SIG_DFL. The default is normally to terminate a process on receipt of a signal, with certain signals also generating a core image of the process in its current working directory. There are a few signals whose default disposition is to be ignored: SIGCHLD and SIGURG (sent on the arrival of out-of-band data, Chapter 24) are two that we will encounter in this text.

signal Function

The POSIX way to establish the disposition of a signal is to call the sigaction function. This gets complicated, however, as one argument to the function is a structure that we must allocate and fill in. An easier way to set the disposition of a signal is to call the signal function. The first argument is the signal name and the second argument is either a pointer to a function or one of the constants SIG_IGN or SIG_DFL. But, signal is an historical function that predates POSIX. Different implementations provide different signal semantics when it is called, providing backward compatibility, whereas POSIX explicitly spells out the semantics when sigaction is called. The solution is to define our own function named signal that just calls the POSIX sigaction function. This provides a simple interface with the desired POSIX semantics. We include this function in our own library, along with our err_XXX functions and our wrapper functions, for example, that we specify when building any of our programs in this text. This function is shown in Figure 5.6 (the corresponding wrapper function, Signal, is not shown here as it would be the same whether it called our function or a vendor-supplied signal function).

Figure 5.6 signal function that calls the POSIX sigaction function.

lib/signal.c

 1 #include    "unp.h"

 2 Sigfunc *
 3 signal (int signo, Sigfunc *func)
 4 {
 5     struct sigaction act, oact;

 6     act.sa_handler = func;
 7     sigemptyset (&act.sa_mask);
 8     act.sa_flags = 0;
 9     if (signo == SIGALRM) {
10 #ifdef  SA_INTERRUPT
11         act.sa_flags |= SA_INTERRUPT;     /* SunOS 4.x */
12 #endif
13     } else {
14 #ifdef  SA_RESTART
15         act.sa_flags |= SA_RESTART; /* SVR4, 4.4BSD */
16 #endif
17     }
18     if (sigaction (signo, &act, &oact) < 0)
19         return (SIG_ERR);
20     return (oact.sa_handler);
21 }
Simplify function prototype using typedef

2–3 The normal function prototype for signal is complicated by the level of nested parentheses.


void (*signal (int signo, void (*func) (int))) (int);

To simplify this, we define the Sigfunc type in our unp.h header as


typedef    void    Sigfunc(int);

stating that signal handlers are functions with an integer argument and the function returns nothing (void). The function prototype then becomes


Sigfunc *signal (int signo, Sigfunc *func);

A pointer to a signal handling function is the second argument to the function, as well as the return value from the function.

Set handler

6 The sa_handler member of the sigaction structure is set to the func argument.

Set signal mask for handler

7 POSIX allows us to specify a set of signals that will be blocked when our signal handler is called. Any signal that is blocked cannot be delivered to a process. We set the sa_mask member to the empty set, which means that no additional signals will be blocked while our signal handler is running. POSIX guarantees that the signal being caught is always blocked while its handler is executing.

Set SA_RESTART flag

8–17 SA_RESTART is an optional flag. When the flag is set, a system call interrupted by this signal will be automatically restarted by the kernel. (We will talk more about interrupted system calls in the next section when we continue our example.) If the signal being caught is not SIGALRM, we specify the SA_RESTART flag, if defined. (The reason for making a special case for SIGALRM is that the purpose of generating this signal is normally to place a timeout on an I/O operation, as we will show in Section 14.2, in which case, we want the blocked system call to be interrupted by the signal.) Some older systems, notably SunOS 4.x, automatically restart an interrupted system call by default and then define the complement of this flag as SA_INTERRUPT. If this flag is defined, we set it if the signal being caught is SIGALRM.

Call sigaction

18–20 We call sigaction and then return the old action for the signal as the return value of the signal function.

Throughout this text, we will use the signal function from Figure 5.6.

POSIX Signal Semantics

We summarize the following points about signal handling on a POSIX-compliant system:

  • Once a signal handler is installed, it remains installed. (Older systems removed the signal handler each time it was executed.)

  • While a signal handler is executing, the signal being delivered is blocked. Furthermore, any additional signals that were specified in the sa_mask signal set passed to sigaction when the handler was installed are also blocked. In Figure 5.6, we set sa_mask to the empty set, meaning no additional signals are blocked other than the signal being caught.

  • If a signal is generated one or more times while it is blocked, it is normally delivered only one time after the signal is unblocked. That is, by default, Unix signals are not queued. We will see an example of this in the next section. The POSIX real-time standard, 1003.1b, defines some reliable signals that are queued, but we do not use them in this text.

  • It is possible to selectively block and unblock a set of signals using the sigprocmask function. This lets us protect a critical region of code by preventing certain signals from being caught while that region of code is executing.

    [ Team LiB ] Previous Section Next Section