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
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:
-
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.
-
We can
ignore a signal by setting its
disposition to SIG_IGN. The two signals SIGKILL
and SIGSTOP cannot be ignored.
-
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鈥?
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鈥?7
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鈥?0
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.
|