Columns


Standard C

The Header <signal.h>

P.J. Plauger


P.J. Plauger is senior editor of The C Users Journal. He is secretary of the ANSI C standards committee, X3J11, and convenor of the ISO C standards committee, WG14. His latest book is The Standard C Library, published by Prentice-Hall. You can reach him care of The C Users Journal or via Internet at pjp@plauger.uunet.uu.net.

Introduction

A signal is an extraordinary event that occurs during the execution of a program. Synchronous signals occur because of actions that your program takes. Division by zero is one example. Accessing storage improperly is another. Asynchronous signals occur because of actions outside your program. Someone striking an attention key is one example. A separate program (executing asynchronously) signaling yours is another.

A signal that is not ignored by your program demands immediate handling. If you do not specify handling for a signal that occurs, it is treated as a fatal error. Your program terminates execution with unsuccessful status. In some implementations, the status indicates which signal occurred. In others, the Standard C library writes an error message to the standard error stream before it terminates execution.

The header <signal.h> defines the code values for an open-ended set of signals. It also declares two functions:

You can handle a signal one of three ways:

In the last case, the function that you designate is called a signal handler. The Standard C library calls a signal handler when its corresponding signal is reported. Normal execution of the program is suspended. If the signal handler returns to its caller, execution of the program resumes at the point where it was suspended. Aside from the delay, and any changes made by the signal handler, the behavior of the program is unaffected.

Synchronization

This sounds like elegant machinery, but it is not. The occurrence of a signal introduces a second thread of control within a program. That raises all sorts of issues about synchronization and reliable operation. The C Standard promises little in either regard. C programs have been handling signals since the earliest days of the language. Nevertheless, a portable program can safely take very few actions within a signal handler.

One problem is the Standard C library itself. If called with valid arguments, no library function should ever generate a synchronous signal. But an asynchronous signal can occur while the library is executing. The signal may suspend program execution part way through a print operation, for example. Should the signal handler print a message, an output stream can end up in a confused state. There is no way to determine from within a signal handler whether a library function is in an unsafe state.

Another problem concerns data objects that you declare to have volatile types. That warns the translator that surprising agents can access the data object, so it is careful how it generates accesses to such a data object. In particular, it knows not to perform optimizations that move the accesses to volatile data objects beyond certain sequence points. A signal handler is, of course, a surprising agent. Thus, you should declare any data object you access within a signal handler to have a volatile type. That helps, provided the signal is synchronous and occurs between two sequence points where the data object is not accessed. For an asynchronous signal however, no amount of protection suffices. Signals are not confined to suspending program execution only at sequence points.

The C Standard offers a partial solution to the problem of writing reliable signal handlers. The header <signal.h> defines the type sig_atomic_t. It is an integer type that the program accesses atomically. A signal should never suspend program execution part way through the access of a data object declared with this type. A signal handler can share with the rest of the program only data objects declared to have type volatile sig_atomic_t.

Shortcomings Of Signals

As a means of communicating information, signals leave much to be desired. The semantics spelled out for signals in the C Standard is based heavily on their behavior under the early UNIX operating system. That system had serious lapses in the way it managed signals:

Moreover, signals arise from an odd assortment of causes on any computer. The ones named in the C Standard are a subset of those supported by UNIX. These in turn derive from the interrupts and traps defined for the PDP-11. Mapping the sources of signals for a given computer onto those defined for C is often arbitrary. Mapping the semantics of signal handling for a given operating systems can be even more creative.

The C Standard had to weaken the already weak semantics of UNIX signals to accommodate an assortment of operating systems:

There's not much left.

Thus, no portable use for the functions declared in <signal.h> can be defined with complete safety. You could, in principle, specify a handler for a signal that only raise reports. It's hard to imagine a situation where that works better than instead using setjmp and longjmp, declared in <setjmp.h>. Besides, you cannot ensure that a given signal is never reported on an arbitrary implementation of C. Any time your program handles signals, accept the fact that you limit its portability.

What The C Standard Says

7.7 Signal handling <signal.h>

The header <signal.h> declares a type and two functions and defines several macros, for handling various signals (conditions that may be reported during program execution).

The type defined is

sig_atomic_t
which is the integral type of an object that can be accessed as an atomic entity, even in the presence of asynchronous interrupts.

The macros defined are

SIG_DFL
SIG_ERR
SIG_IGN
which expand to constant expressions with distinct values that have type compatible with the second argument to and the return value of the signal function, and whose value compares unequal to the address of any declarable function; and the following, each of which expands to a positive integral constant expression that is the signal number corresponding to the specified condition:

SIGABRT — abnormal termination, such as is initiated by the abort function

SIGFPE — an erroneous arithmetic operation, such as zero divide or an operation resulting in overflow

SIGILL — detection of an invalid function image, such as an illegal instruction

SIGINT — receipt of an interactive attention signal

SIGSEGV — an invalid access to storage

SIGTERM — a termination request sent to the program

An implementation need not generate any of these signals, except as a result of explicit calls to the raise function. Additional signals and pointers to undeclarable functions, with macro definitions beginning, respectively, with the letters SIG and an uppercase letter or with SIG_ and an uppercase letter,108 may also be specified by the implementation. The complete set of signals, their semantics, and their default handling is implementation-defined; all signal numbers shall be positive.

7.7.1 Specify signal handling

7.7.1.1 The signal function

Synopsis

#include <signal.h>
void (*signal(int sig, void
(*func) (int))) (int);

Description

The signal function chooses one of three ways in which receipt of the signal number sig is to be subsequently handled. If the value of func is SIG_DFL, default handling for that signal will occur. If the value of func is SIG_IGN, the signal will be ignored. Otherwise, func shall point to a function to be called when that signal occurs. Such a function is called a signal handler.

When a signal occurs, if func points to a function, first the equivalent of signal(sig, SIG_DFL); is executed or an implementation-defined blocking of the signal is performed. (If the value of sig is SIGILL, whether the reset to SIG_DFL occurs is implementation-defined.) Next the equivalent of (*func) (sig); is executed. The function func may terminate by executing a return statement or by calling the abort, exit, or longjmp function. If func executes a return statement and the value of sig was SIGFPE or any other implementation-defined value corresponding to a computational exception, the behavior is undefined. Otherwise, the program will resume execution at the point it was interrupted.

If the signal occurs other than as the result of calling the abort or raise function, the behavior is undefined if the signal handler calls any function in the standard library other than the signal function itself (with a first argument of the signal number corresponding to the signal that caused the invocation of the handler) or refers to any object with static storage duration other than by assigning a value to a static storage duration variable of type volatile sig_atomic_t. Furthermore, if such a call to the signal function results in a SIG_ERR return, the value of errno is indeterminate.109

At program startup, the equivalent of

signal (sig, SIG_IGN);
may be executed for some signals selected in an implementation-defined manner; the equivalent of

signal (sig, SIG_DFL);
is executed for all other signals defined by the implementation.

The implementation shall behave as if no library function calls the signal function.

Returns

If the request can be honored, the signal function returns the value of func for the most recent call to signal for the specified signal sig. Otherwise, a value of SIG_ERR is returned and a positive value is stored in errno.

Forward references: the abort function (7.10.4.1), the exit function (7.10.4.3).

7.7.2 Send signal

7.7.2.1 The raise function

Synopsis

#include <signal .h>
int raise(int sig);

Description

The raise function sends the signal sig to the executing program.

Returns

The raise function returns zero if successful, nonzero if unsuccessful.

Footnotes:

108. See "future library directions" (7.13.5). The names of the signal numbers reflect the following terms (respectively): abort, floating-point exception, illegal instruction, interrupt, segmentation violation, and termination.

109. If any signal is generated by an asynchronous signal handler, the behavior is undefined.

Using <signal.h>

Signal handling is essentially nonportable. Use the functions declared in <signal.h> only when you must specify the handling of signals for a known set of operating systems. Don't try too hard to generalize the code.

If default handling for a signal is acceptable, then by all means choose that option. Adding your own signal handler decreases portability and raises the odds that the program will mishandle the signal. If you must provide a handler for a signal, categorize it as follows:

As a rule, the second category contains asynchronous signals not intended to cause immediate program termination. Rarely will you find a signal that does not fit clearly in one of these categories.

A signal handler that must not return ends in a call to abort, exit, or longjmp. Do not, of course, end a handler for SIGABRT with a call to abort. The handler should not reestablish itself by calling signal. Leave that to some other agency, if the program does not terminate. If the signal is asynchronous, be wary of performing any input or output. You may have interrupted the library part way through such an operation.

A signal handler that must return ends in a return statement. If it is to reestablish itself, it should do so immediately on entry. If the signal is asynchronous, store a nonzero value in a volatile data object of type sig_atomic_t. Do nothing else that has side effects visible to the executing program, such as input or output and accessing other data objects.

A sample asynchronous signal handler might look like:

#include <signal.h>

static sig_atomic_t intflag = 0;

static void field_int(int sig)
   {   /* handle SIGINT */
   signal (SIGINT, &field_int);
   intflag = 1;
   return;
   }
The program calls signal(SIGINT, &field_int) to establish the handler. From time to time, it can then check for the occurrence of asynchronous interactive attention interrupts by executing code such as:

if (intflag)
   {   /* act on interrupt */
   intflag = 0;
   .....
   }
Note that two small windows exist where these signals can go astray:

Those are inherent limitations of signals.

Standard Signals

Here is a brief characterization of the signals defined for all implementations of Standard C. Note that a given implementation may well define more. Display the contents of <signal.h> for other defined macro names that begin with SIG. These should expand to (small) positive integers that represent additional signals.

SIGABRT — This signal occurs when the program is terminating unsuccessfully, as by an explicit call to abort, declared in <stdlib.h>. Do not ignore this signal. If you provide a handler, do as little as possible. End the handler with a return statement or a call to exit, declared in <stdlib.h>.

SIGFPE — The name originally meant "floating-point exception." The C Standard generalizes this signal to cover any arithmetic exception such as overflow, underflow, or zero divide. Implementations vary considerably on what exceptions they report, if any. Rarely does an implementation report integer overflow. Ignoring this signal may be rash. A handler must not return.

SIGINT — This is the conventional way of reporting an asynchronous interactive attention signal. Most systems provide some keystroke combination that you can type to generate such a signal. Examples are ctl-C, DEL, and ATTN. It offers a convenient way to terminate a tiresome loop early. But be aware that an asynchronous signal can catch the program part way through an operation that should be atomic. If the handler does not return control, the program may subsequently misbehave. You can safely ignore this signal.

SIGSEGV — The name originally meant "segmentation violation," because the PDP-11 managed memory as a set of segments. The C Standard generalizes this signal to cover any exception raised by an invalid storage access. The program has attempted to access storage outside any of the functions or data objects defined by C, as with an ill-formed function designator or lvalue. Or the program has attempted to store a value in a data object with a const type. In any event, the program cannot safely continue execution. Do not ignore this signal or return from its handler.

SIGTERM — This signal is traditionally sent from the operating system or from another program executing asynchronously with yours. Treat it as a polite but firm request to terminate execution. It is an asynchronous signal, so it may occur at an inopportune point in your program. You may want to defer it, using the techniques described above. You can ignore this signal safely, although it may be bad manners to do so.

Implementing <signal.h>

Listing 1 shows the file signal.h. The header <signal.h> I present here is minimal. A UNIX system, for example, defines dozens of signals. Many systems endeavor to look as much as possible like UNIX in this regard. They too define all these signals even if they do not generate many of them. Notwithstanding this concerted group behavior, the choice of signals and their codes both vary considerably. I have endeavored here to choose codes that are most widely used.

As usual, I make use of the internal header <yvals.h> to provide parameters that can vary among systems. The code for SIGABRT is one. The highest valid signal code is another. Some functions in this implementation use the macro _NSIG to determine the lowest positive number that is not a valid signal code. Thus, the header <yvals.h> defines two macros of interest here. For a typical UNIX system, the definitions are:

#define _SIGABRT    6
#define _SIGMAX     32
The header <signal.h> makes an additional concession to widespread UNIX practice. It defines the macros SIG_ERR and SIG_IGN in a moderately ugly way. The values -1 and 1 could conceivably be valid function addresses in some implementation. Admittedly, that is only rarely possible. Where it is possible, the linker can be jiggered to avoid the possibility. Still, other values would be more gracious. (The addresses of signal and raise, for example, are not likely to specify useful signal handlers.) But the values chosen here are the ones used widely in UNIX implementations. They are also widely imitated under other operating systems. I chose these for compatibility with existing machinery.

That compatibility is often necessary. Almost invariably, the functions signal and raise must be tailored for each operating system. UNIX is the extreme case. In that environment, the system service signal does the whole job. If you have access to a C-callable function of that name, just use it. Let other functions call it directly. If the system service has a private name, such as _Signal, you can write signal as:

/* signal function - UNIX version */
#include <signal.h>

_Sigfun *_Signal(int, _Sigfun *)

_Sigfun *(signal)(int sig,
                   _Sigfun *fun)
   {   /* call the system service */
   return (_Signal(sig, fun));
   }
This is an obvious candidate for a masking macro in <signal.h>.

The function raise is only slightly more difficult. It uses the system service kill to send a signal to itself. ("Kill" is a misnomer stemming from its earliest use for sending only the signal SIGKILL.) To identify itself, raise also needs the system service getpid. Assuming suitable secret names for these two system services, such as _Kill and _Getpid, you can write raise as:

/* raise function - UNIX version */
#include <signal.h>

int _Getpid (void);
int _Raise(int, int);

int (raise)(int sig}
   {   /* raise a signal */
   return (_Kill(_Getpid(), sig));
   }
Here is another obvious candidate for a masking macro.

I have also written more generic versions of signal and raise, but I lack the space to present them here. They manage a table of pointers to signal handlers for an environment that does less of this work for you. Of course, you still have to add system-specific code to handle any hardware interrupts. As I pointed out earlier, there's no such thing as portable signals.

This article is excerpted from P.J. Plauger, The Standard C Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1992).