In this section, we show how to implement signal() using sigaction(). The implementation is straightforward, but needs to account for the fact that, historically and across different UNIX implementations, signal() has had different semantics. In particular, early implementations of signals were unreliable, meaning that:
On entry to a signal handler, the disposition of the signal was reset to its default. (This corresponds to the SA_RESETHAND
flag described in Changing Signal Dispositions: sigaction().) In order to have the signal handler invoked again for a subsequent delivery of the same signal, the programmer needed to make a call to signal() from within the handler to explicitly reestablish the handler. The problem in this scenario is that there is a small window of time between entering the signal handler and reestablishment of the handler, during which, if the signal arrives a second time, it would be processed according to its default disposition.
Delivery of further occurrences of a signal was not blocked during execution of a signal handler. (This corresponds to the SA_NODEFER
flag described in Changing Signal Dispositions: sigaction().) This meant that if the signal was delivered again while the handler was still executing, then the handler would be recursively invoked. Given a sufficiently rapid stream of signals, the resulting recursive invocations of the handler could overflow the stack.
As well as being unreliable, early UNIX implementations did not provide automatic restarting of system calls (i.e., the behavior described for the SA_RESTART
flag in Interruption and Restarting of System Calls).
The 4.2BSD reliable signals implementation rectified these limitations, and several other UNIX implementations followed suit. However, the older semantics live on today in the System V implementation of signal(), and even contemporary standards such as SUSv3 and C99 leave these aspects of signal() deliberately unspecified.
Tying the above information together, we implement signal() as shown in Example 22-1. By default, this implementation provides the modern signal semantics. If compiled with -DOLD_SIGNAL, then it provides the earlier unreliable signal semantics and doesn’t enable automatic restarting of system calls.
Example 22-1. An implementation of signal()
signals/signal.c
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int sig, sighandler_t handler) { struct sigaction newDisp, prevDisp; newDisp.sa_handler = handler; sigemptyset(&newDisp.sa_mask); #ifdef OLD_SIGNAL newDisp.sa_flags = SA_RESETHAND | SA_NODEFER; #else newDisp.sa_flags = SA_RESTART; #endif if (sigaction(sig, &newDisp, &prevDisp) == -1) return SIG_ERR; else return prevDisp.sa_handler; }signals/signal.c
The glibc implementation of the signal() library function has changed over time. In newer versions of the library (glibc 2 and later), the modern semantics are provided by default. In older versions of the library, the earlier unreliable (System V-compatible) semantics are provided.
The Linux kernel contains an implementation of signal() as a system call. This implementation provides the older, unreliable semantics. However, glibc bypasses this system call by providing a signal() library function that calls sigaction().
If we want to obtain unreliable signal semantics with modern versions of glibc, we can explicitly replace our calls to signal() with calls to the (nonstandard) sysv_signal() function.
#define _GNU_SOURCE
#include <signal.h>
void ( *sysv_signal
(int sig, void (*handler)(int)) ) (int);
Returns previous signal disposition on success, or SIG_ERR
on error
The sysv_signal() function takes the same arguments as signal().
If the _BSD_SOURCE
feature test macro is not defined when compiling a program, glibc implicitly redefines all calls to signal() to be calls to sysv_signal(), meaning that signal() has unreliable semantics. By default, _BSD_SOURCE
is defined, but it is disabled (unless also explicitly defined) if other feature test macros such as _SVID_SOURCE
or _XOPEN_SOURCE
are defined when compiling a program.
Because of the System V versus BSD (and old versus recent glibc) portability issues described above, it is good practice always to use sigaction(), rather than signal(), to establish signal handlers. We follow this practice throughout the remainder of this book. (An alternative is to write our own version of signal(), probably similar to Example 22-1, specifying exactly the flags that we require, and employ that version with our applications.) Note, however, that it is portable (and shorter) to use signal() to set the disposition of a signal to SIG_IGN
or SIG_DFL
, and we’ll often use signal() for that purpose.