Signal Processing in Unix/Linux

1. Signal Types

Unix/Linux supports 31 different signals, which are defined in the signal.h file.

#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGPOLL 29
#define SIGPWR 30
#define SIGSYS 31

Each signal has a symbolic name, such as SIGHUP(1), SIGINT(2), SIGKILL(9), SIGSEGV(11), etc.

2. Origin of Signals

. Signals from Hardware Interrupts: While a process executes, some hardware interrupts are converted to signals sent to the process. Examples of hardware signals are Interrupt key (Control-C), which results in a SIGINT(2) signal.

Interval timers, which generate a SIGALRM(14) or

SIGVTALRM(26) or SIGPROF(27) signal when their time expires.

Other hardware errors, such as bus-error, IO trap, etc.

. Signals from Exceptions: When a process in user mode encounters an exception, it traps to kernel mode to generate a signal to itself. Examples of familiar trap signals are SIGFPE(8) for floating point exception (divide by 0) and the most common and dreadful SIGSEGV(11) for segmentation fault, etc.

. Signals from Other Processes: Processes may use the kill(pid, sig) system call to send a signal to a target process identified by pid. The reader may try the following experiment. Under Linux, run the trivial C program

main(){ while(1); }

which causes the process to loop forever. From another (X-window) terminal, use ps -u to find the looping process pid. Then enter the sh command

kill -s 11 pid

The looping process will die with segmentation fault. The reader may wonder: how can that be? All the process does is executing in a while(1) loop, how can it generate a segmentation fault? The answer is: it does not matter. Whenever a process dies by a signal, its exitValue contains the signal number. The parent sh simply converts the signal number of the dead child process to an error string, whatever that is.

3. Signals in Process PROC Structure:

Each process PROC has a 32-bit vector, which records signals sent to the process. In the bit vector, each bit (except bit 0) represents a signal number. In addition, it also has a signal MASK bit-vector for masking out the corresponding signals. A set of system calls, such as sigmask, sigsetmask, siggetmask, sigblock, etc. can be used to set, clear and examine the MASK bit-vector. A pending signal becomes effective only if it is not masked out. This allows a process to defer processing masked out signals, similar to CPU masking out certain interrupts.

4. Signal Handlers

Each process PROC has a signal handler array, int sig[32]. Each entry of the sig[32] array specifies how to handle a corresponding signal, where 0 means DEFault, 1 means IGNore, other nonzero value means by a preinstalled signal catcher (handler) function in user mode. Figure 6.1 shows the signal bit-vector, masking bit-vector and signal handlers

A signal I is generated or sent to the process if bit-I in the signal-bit vector is 1. The signal is blocked or masked out if bit-I of the mask bit-vector is 1. Otherwise, it is unblocked. A signal will take effect or delivered to the process only if the signal is present and unblocked. When the process in kernel mode finds an unblocked signal, it clears the signal bit to 0 and tries to handle the signal by the handler function in the signal handler array. A 0 entry means DEFault, a 1 means IGNore and other values means a preinstalled catcher function in user space.

5. Install Signal Catchers

A process may use the system call

int r = signal(int signal_number, void *handler);

to change the handler function of a selected signal number, except SIGKILL(9) and SIGSTOP(19), which can not be changed. The installed handler, if not 0 or 1, must be the entry address of a signal catcher function in user space of the form

void catcher(int signal_number){………….. }

The signal() system call is available in all Unix-like systems but it has some undesirable features.

  1.  Before executing the installed signal catcher, the signal handler is usually reset to DEFault. In order to catch the next occurrence of the same signal, the catcher must be installed again. This may lead to a race condition between the next signal and reinstalling the signal handler. In contrast, sigaction() automatically blocks the next signal while executing the current catcher, so there is no race condition.
  2.  Signal() can not block other signals. If needed, the user must use sigprocmask() to explicitly block/unblock other signals. In contrast, sigaction() can specify other signals to be blocked.
  3.  Signal() can only transmit a signal number to the catcher function. Sigaction() can transmit addition information about the signal.
  4.  Signal() may not work for threads in a multi-threaded program. Sigaction() works for threads.
  5.  Signal() may vary across different versions of Unix. Sigaction() is the POISX standard, which is more portable.

For these reasons, signal() has been replaced by the POSIX sigaction() function. In Linux (Bovet and Cesati 2005), sigaction() is a system call. It has the prototype

int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);

The sigaction structure is defined as

struct sigaction{

void     (*sa_handler)(int);

void     (*sa_sigaction)(int, siginfo_t *, void *);

sigset_t sa_mask;

int      sa_flags;

void     (*sa_restorer)(void);

};

The most important fields are:

. sa_handler: This is the pointer to a handler function that has the same prototype as a handler for signal().

. sa_sigaction: This is an alternative way to run the signal handler. It has two additional arguments beside the signal number where the siginfo_t * provides more information about the received signal.

. sa_mask: allows to set signals to be blocked during execution of the handler.

. sa_flags: allows to modify the behavior of the signal handling process. To use the sa_sigaction handler, sa_flags must be set to SA_SIGINFO.

For a detailed description of the sigaction structure fields, see the sigaction manual page. The following shows a simple example of using the sigaction() system call.

Example 6.6 Example use of sigaction()

/****** sigaction.c file *******/

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

void handler(int sig, siginfo_t *siginfo, void *context)

{

printf(“handler: sig=%d from PID=%d UID=%d\n”,

sig, siginfo->si_pid, siginfo->si_uid);

}

int main(int argc, char *argv[])

{

struct sigaction act;

memset(&act, 0, sizeof(act));

act.sasigaction = &handler;

act.sa_flags = SA_SIGINFO;

sigaction(SIGTERM, &act, NULL);

printf(“proc PID=%d looping\n”);

printf(“enter kill PID to send SIGTERM signal to it\n”, getpid());

while(1)

( sleep(10);

}

}

While the program is running, the reader may enter kill PID from another X-terminal, which sends a SIGTERM(15) signal to the process. Any signal would wake up the process even if it is in the sleep state, allowing it to handle the signal. In the signal handler we read two fields from the siginfo parameter to show the signal sender’s PID and UID, which are unavailable if the signal handler was installed by the signal() system call.

6. A process may use the system call

int r = kill(pid, signal number);

to send a signal to another process identified by pid. The sh command

kill -s signalnumber pid

uses the kill system call. In general, only related processes, e.g. those with the same uid, may send signals to each other. However, a superuser process (uid=0) may send signals to any process. The kill system call uses an invalid pid, to mean different ways of delivering the signal. For example, pid=0 sends the signal to all processes in the same process group, pid=-1 for all processes with pid>1, etc. The reader may consult Linux man pages on signal/kill for more details.

Source: Wang K.C. (2018), Systems Programming in Unix/Linux, Springer; 1st ed. 2018 edition.

Leave a Reply

Your email address will not be published. Required fields are marked *