3.3 Signals

Signals are mechanisms for communicating with and manipulating processes in Linux. The topic of signals is a large one; here we discuss some of the most important signals and techniques that are used for controlling processes.

A signal is a special message sent to a process. Signals are asynchronous; when a process receives a signal, it processes the signal immediately, without finishing the current function or even the current line of code. There are several dozen different signals, each with a different meaning. Each signal type is specified by its signal number, but in programs, you usually refer to a signal by its name. In Linux, these are defined in /usr/include/bits/signum.h. (You shouldn't include this header file directly in your programs; instead, use <signal.h>.)

When a process receives a signal, it may do one of several things, depending on the signal's disposition. For each signal, there is a default disposition, which determines what happens to the process if the program does not specify some other behavior. For most signal types, a program may specify some other behavior—either to ignore the signal or to call a special signal-handler function to respond to the signal. If a signal handler is used, the currently executing program is paused, the signal handler is executed, and, when the signal handler returns, the program resumes.

The Linux system sends signals to processes in response to specific conditions. For instance, SIGBUS (bus error), SIGSEGV (segmentation violation), and SIGFPE (floating point exception) may be sent to a process that attempts to perform an illegal operation. The default disposition for these signals it to terminate the process and produce a core file.

A process may also send a signal to another process. One common use of this mechanism is to end another process by sending it a SIGTERM or SIGKILL signal. [3] Another common use is to send a command to a running program. Two "user-defined" signals are reserved for this purpose: SIGUSR1 and SIGUSR2. The SIGHUP signal is sometimes used for this purpose as well, commonly to wake up an idling program or cause a program to reread its configuration files.

[3] What's the difference? The SIGTERM signal asks a process to terminate; the process may ignore the request by masking or ignoring the signal. The SIGKILL signal always kills the process immediately because the process may not mask or ignore SIGKILL.

The sigaction function can be used to set a signal disposition. The first parameter is the signal number. The next two parameters are pointers to sigaction structures; the first of these contains the desired disposition for that signal number, while the second receives the previous disposition. The most important field in the first or second sigaction structure is sa_handler. It can take one of three values:

·         SIG_DFL, which specifies the default disposition for the signal.

·         SIG_IGN, which specifies that the signal should be ignored.

·         A pointer to a signal-handler function. The function should take one parameter, the signal number, and return void.

Because signals are asynchronous, the main program may be in a very fragile state when a signal is processed and thus while a signal handler function executes. Therefore, you should avoid performing any I/O operations or calling most library and system functions from signal handlers.

A signal handler should perform the minimum work necessary to respond to the signal, and then return control to the main program (or terminate the program). In most cases, this consists simply of recording the fact that a signal occurred. The main program then checks periodically whether a signal has occurred and reacts accordingly.

It is possible for a signal handler to be interrupted by the delivery of another signal. While this may sound like a rare occurrence, if it does occur, it will be very difficult to diagnose and debug the problem. (This is an example of a race condition, discussed in Chapter 4, "Threads," Section 4.4, "Synchronization and Critical Sections.") Therefore, you should be very careful about what your program does in a signal handler.

Even assigning a value to a global variable can be dangerous because the assignment may actually be carried out in two or more machine instructions, and a second signal may occur between them, leaving the variable in a corrupted state. If you use a global variable to flag a signal from a signal-handler function, it should be of the special type sig_atomic_t. Linux guarantees that assignments to variables of this type are performed in a single instruction and therefore cannot be interrupted midway. In Linux, sig_atomic_t is an ordinary int; in fact, assignments to integer types the size of int or smaller, or to pointers, are atomic. If you want to write a program that's portable to any standard UNIX system, though, use sig_atomic_t for these global variables.

This program skeleton in Listing 3.5, for instance, uses a signal-handler function to count the number of times that the program receives SIGUSR1, one of the signals reserved for application use.

Listing 3.5 (sigusr1.c) Using a Signal Handler
#include <signal.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/types.h> 
#include <unistd.h> 
 
sig_atomic_t sigusr1_count = 0; 
 
void handler (int signal_number) 
{
  ++sigusr1_count; 
} 
 
int  main () 
{
  struct sigaction sa; 
  memset (&sa, 0, sizeof  (sa)); 
  sa.sa_handler = &handler; 
  sigaction (SIGUSR1, &sa, NULL); 
 
  /* Do some lengthy  stuff here. */ 
  /*  ... */ 
 
  printf ("SIGUSR1 was raised %d times\n", sigusr1_count); 
  return 0; 
}