How to Make Use of Signals When Writing Linux Applications

Let me start off with a correction (sigh). Last month I described the difference between slow and fast system calls and then described select() as a slow system call. That was incorrect. Since select() takes a timeout value, it always returns in a predictable amount of time, so it's considered a fast system call.

Let me start off with a correction (sigh). Last month I described the difference between slow and fast system calls and then described select() as a slow system call. That was incorrect. Since select() takes a timeout value, it always returns in a predictable amount of time, so it’s considered a fast system call.

I spent my last column giving an overview of the role signals play in the Linux system-call API and explaining the different ways signals could be handled by the kernel. Now that the background information is out of the way, I’ll describe how to make use of signals in Linux applications. I’m going to describe only the POSIX signal API; it has all the richness you need and is portable. Legacy applications should be rewritten to use it because it’s the best-defined signal API.

The data type that the signal functions rely on is called sigset_t. It represents a set of signals. If you remember fd_set you’ll notice that sigset_t is quite similar. Every signal is either contained in or absent from each sigset_t you use, which lets programs use sigset_t to easily pass a list of signals to the kernel. You should never manipulate a sigset_t object directly; always use the following functions:

 int sigemptyset(sigset_t * set);
int sigfillset(sigset_t * set);
int sigaddset(sigset_t * set, int signo);
int sigdelset(sigset_t * set, int signo);
int sigismember(const sigset_t * set, int signo);

The first argument to all of these functions is a pointer to the signal set that is being manipulated. The first function, sigemptyset(), removes all signals from the referenced signet_t, while the second function, sigfillset(), adds every signal to the sigset_t. All sigset_t variables should be initialized with one of these two functions.

The next two functions, sigaddset() and sigdelset(), add or remove the signal specified by the second argument to or from the signal set pointed to by the first argument. By doing this, programs can build sets that contain exactly the signals they need.

The final function, sigismember(), lets you test whether or not a given signal, which is passed as the second argument to the system call, is contained in a given sigset_t. It returns 0 if the signal is not a member of the set, and nonzero if it is a member.

Catching a Signal

Now that we’ve introduced sigset_t, I’ll explain how to catch a signal. The structsigaction data type tells the kernel how to deliver a signal to the program.

 typedef void (*sighandler_t)(int signo);

struct sigaction {
sighandler_t sa_handler;
sigset_t sa_mask;
unsigned long sa_flags;
void (*sa_restorer)(void);

I started off by giving the typedef for sighandler_t, which is a pointer to a function that takes a single int argument and returns void. This is the prototype for a signal handler. It is passed a single argument, the signal that it is handling. (This is a white lie; there is a more complicated way of catching signals that passes information on why the signal was generated in a siginfo_t, but it’s rarely used so I won’t discuss it here.) This is exactly the same as the signal_handler_type I talked about last month.

The first field in a sigaction structure, sa_handler, points to the function that should be used to handle the signal, either a signal-handling function (from now on, called a “signal handler”), or one of the following magic values:

SIG_IGN: Specifies that the signal should be ignored; the kernel will silently dispose of the signal.

SIG_DFL: The kernel should handle the signal in whatever way it thinks best; this often means killing the process, but it may be equivalent to SIG_IGN (depending on the signal).

The next field in structsigaction, sa_mask, specifies which signals should be blocked when the signal handler is being run. A blocked signal is not delivered until it is unblocked. By letting the program specify an arbitrary set of signals that should be blocked when a given signal is being caught, the kernel makes it quite easy to have a signal handler catch a number of different signals and still never be reinvoked while it’s already running.

By default, the kernel always blocks a signal when its signal handler is being run. For example, when a SIGCHLD signal handler is running, the kernel will block other SIGCHLDs from being delivered; I’ll explain why this is a good idea later, when I discuss how to write a good signal handler. The sa_mask field has no effect on this behavior, but the sa_flags field does let a program override this.

The sa_flags field is a bitmask of various flags logically ORed together, the combination of which specifies the kernel’s behavior when the signal is received. The values it may contain are:

SA_NOCLDSTOP: A SIGCHLD signal is normally generated when one of a process’s children has terminated or stopped. If this flag is specified, SIGCHLD is generated only when a child process has terminated; stopped children will not cause any signal.

SA_NOMASK: Remember that a program can’t use the sa_mask field of sigaction to allow a signal to be sent while its signal handler is currently running? This flag gives the opposite behavior, allowing a signal handler to be interrupted by the delivery of the same signal.

SA_NOMASK: For several reasons, SA_ NOMASK is a very bad idea, since it makes it impossible to write a properly functioning signal handler. It’s included in the POSIX signal API because many old versions of Unix provided this behavior (this is one of the behaviors the term “unreliable signals” describes), and the POSIX group wanted to be able to emulate that behavior for old applications that relied on it.

SA_ONESHOT: If this flag is specified, as soon as the signal handler for this signal is run, the kernel will reset the signal handler for this signal to SIG_DFL. Like SA_NOMASK, this is a bad idea. (In fact, this is the other behavior associated with unreliable signal implementations.) It is provided for two reasons. The first is to enable emulation of older platforms. The second is that ANSI C requires this type of signal behavior, and POSIX had to live with that not-so-bright decision.

SA_RESTART: Normally slow system calls return EINTR when a signal is delivered while they are running. If SA_RESTART is specified, the system calls don’t return (the kernel automatically restarts them) after a signal is delivered to the process. I talked last month about why this is handy.

The last field, sa_restorer, is actually the easiest. Just ignore it. It’s not used by Linux, and no other system uses it without setting a special flag in sa_flags that indicates sa_restorer is worth paying attention to.

Lights, Camera, sigaction

The sigaction() system call is used to tell the kernel how it should deliver a particular signal to the process.

 int sigaction(int signum, struct sigaction * act,
struct sigaction * oact).

The first parameter, signum, is the signal whose delivery is being specified. The next parameter, act, should point to a structsigaction, which tells the kernel what to do when signal signum is delivered. If act is NULL, the current behavior is retained. If the final parameter, oact, is not NULL, the kernel fills in the struct sigaction to which oact points with the current disposition of that signal.

Listing One contains a simple program that illustrates how to use sigaction. Listing One installs a simple signal handler for two signals, SIGINT and SIGTSTP. SIGINT is sent to applications when the user presses ^C on the terminal, and SIGTSTP is sent when the user presses ^Z. (Well, those are the standard keystrokes anyway; they can be changed.) Don’t worry about how the kernel knows what processes to send the signals to. That’s something of a black art that I may discuss in the future. SIGINT normally kills a process, and SIGTSTP normally suspends it. By setting up a signal handler for those signals, the program disables those default behaviors.

Listing One: Sigaction in Action

 #include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

/* Simple signal handler */
void handler(int sig) {
if (sig == SIGINT)
printf(“got SIGINT\n”);
printf(“got SIGTSTP\n”);

int main(void) {
struct sigaction sa;
struct sigaction oldint;

/* Initialize the sa structure */
sa.sa_handler = handler;
sa.sa_flags = 0;

/* Set up the signal handlers; remember the old
handler for SIGINT so we can restore it later */
sigaction(SIGINT, &sa, &oldint);
sigaction(SIGTSTP, &sa, NULL);

/* Sleep for five seconds, or until a signal wakes
us up*/

/* Restore the behavior of SIGINT just to show it can
be done. */
sigaction(SIGINT, &oldint, NULL);

return 0;

After setting up a signal handler, the program calls sleep() for five seconds. Since sleep() takes a predictable amount of time, it’s considered a fast system call and will not be restarted after the signal is delivered.

Notice the oldint variable. It’s used to store the original sigaction information for SIGINT so it can be restored at the end of the program. While that’s not terribly important for this program, it’s a good illustration of the technique.

To give this program a try, compile it, run it, and press ^C or ^Z. You’ll get a message from the signal handler, and the program will exit gracefully.

Manipulating the Signal Mask

The only other major concept in signal programming is manipulating the process’s signal mask. The signal mask specifies which signals the process has blocked, which prevents those signals from being delivered to the process. If one is sent, the kernel waits to deliver it until the process unblocks that signal. (Remember from last month that signals aren’t queued, though; no matter how many times a blocked signal is sent, only one is delivered.)

Blocking signals is incredibly handy, since it restricts when a signal handler can be run and makes it much easier to write a good signal handler. Changing the process’s signal mask is done through sigprocmask().

 int sigprocmask(int how, const sigset_t *
modset, sigset_t *oldset);

There are three different things this system call can do: set the signal mask, remove signals from the current signal mask, and add signals to the current signal mask. The first parameter, how, tells sigprocmask() how to combine the current signal mask with the modset value passed as the second parameter. If the final parameter is not NULL, the sigset_t to which it points is filled in with the process’s original signal mask, which makes it easy to restore later.

Here are the values the how parameter can take, and how modset is combined with the signal mask in each case:

SIG_BLOCK: The signals contained in modset are added into the current signal mask. (Those signals become blocked.)

SIG_UNBLOCK: The signals contained in modset are removed from the current signal mask.

SIG_SETMASK: The current signal mask is set to exactly the signals contained in modset.

When a process has received a signal that is currently blocked and thus hasn’t yet been delivered, that signal is said to be pending. A process can find out what signals are pending through the sigpending() system call.

 int sigpending(sigset_t *set);

On return, the sigset_t pointed to by sigpending()‘s sole parameter contains exactly the signals that are currently pending.


The last major system call related to POSIX signals lets a process suspend itself until a signal (any signal) has been received. At the same time, the process can change its signal mask for the duration of that system call.

 int sigsuspend(const sigset_t *mask);

When sigsuspend() is called, the process’s signal mask is set to the sigset_t pointed to by mask (if mask is NULL, the process’s signal mask is left alone). Once a signal has been sent to the process and handled by the process, sigsuspend() restores the process’s signal mask and returns. (Note that multiple signals could arrive before sigsuspend returns.) The only gotcha here is that the signals in the sigset_t passed to sigsuspend are blocked; the application ends up waiting for signals that are not in that signal set.

On first glance, it might look like sigsuspend() is basically equivalent to this code snippet:

sleep(1000000); /* a close approximation of forever */
sigprocmask(SIG_SETMASK, &oldmask);

Unfortunately, it’s not the same at all. For most cases, it will behave identically. However, think about what happens if the program is written as follows:

  1. block SIGUSR1
  2. <do some work>
  3. unblock SIGUSR1
  4. wait for a SIGUSR1 to occur
  5. handle SIGUSR1
  6. block SIGUSR1 (restore the signal mask)
  7. go to step 2

Steps 3-5 are what both sigsuspend and the code snippet I showed claim to do. Now think about what happens if you use the code snippet and you get a SIGUSR1 delivered to us between steps 3 and 4. The program ends up doing this:

  1. block SIGUSR1
  2. <do some work>
  3. unblock SIGUSR1
  4. handle SIGUSR1
  5. wait for a SIGUSR1 to occur


Note that SIGUSR1 was handled before you waited for the signal to arrive, so when you start waiting for the signal, you keep on waiting even though you already got one! This isn’t what you intended.

This particular type of problem is called a race condition. It’s essentially a race between the signal arriving and the process getting to the “wait for SIGUSR1” step. The program will work properly most of the time, but if the signal arrives at the wrong moment, it breaks. Race conditions are notoriously difficult to debug, and the only good way of fixing them is to avoid them in the first place.

The sigsuspend() system call avoids this mess entirely. By combining steps 3, 4, and 5 into a single step, it doesn’t leave any place for an unwelcome signal to sneak in. The kernel guarantees that a signal won’t arrive in the middle of a fast system call, so the race condition is avoided.

Since fast system calls are single, indivisible units, they are said to be “atomic operations.” The idea of atomic operations that can’t be interrupted is important in many aspects of programming, and signals are a fairly gentle introduction to the idea.

Next month, I’ll explain what the various signals are and give some pointers on how to write a good signal handler. Until then, happy hacking.

Erik Troan is a developer for Red Hat Software and co-author of Linux Application Development. He can be reached at ewt@redhat.com.

Comments are closed.