A signal is a very short message that may be sent to a process or a group of processes. The only information given to the process is usually a number identifying the signal; there is no room in standard signals for arguments, a message, or other accompanying information.
A set of macros whose names start with the prefix SIG is used to identify signals; we have already made a few references to them in previous chapters. For instance, the SIGCHLD macro was mentioned in Section 3.4.1. This macro, which expands into the value 17 in Linux, yields the identifier of the signal that is sent to a parent process when a child stops or terminates. The SIGSEGV macro, which expands into the value 11, was mentioned in Section 8.4 ; it yields the identifier of the signal that is sent to a process when it makes an invalid memory reference.
Signals serve two main purposes:
· To make a process aware that a specific event has occurred
· To force a process to execute a signal handler function included in its code
Of course, the two purposes are not mutually exclusive, since often a process must react to some event by executing a specific routine.
Table 10-1 lists the first 31 signals handled by Linux 2.4 for the 80 x 86 architecture (some signal numbers, such those associated with SIGCHLD or SIGSTOP, are architecture-dependent; furthermore, some signals such as SIGSTKFLT are defined only for specific architectures). The meanings of the default actions are described in the next section.
Besides the regular signals described in this table, the POSIX standard has introduced a new class of signals denoted as real-time signals; their signal numbers range from 32 to 63 on Linux. They mainly differ from regular signals because they are always queued so that multiple signals sent will be received. On the other hand, regular signals of the same kind are not queued: if a regular signal is sent many times in a row, just one of them is delivered to the receiving process. Although the Linux kernel does not use real-time signals, it fully supports the POSIX standard by means of several specific system calls.
A number of system calls allow programmers to send signals and determine how their processes respond to the signals they receive. Table 10-2 summarizes these calls; their behavior is described in detail in the later section Section 10.4.
An important characteristic of signals is that they may be sent at any time to a process whose state is usually unpredictable. Signals sent to a process that is not currently executing must be saved by the kernel until that process resumes execution. Blocking a signal (described later) requires that delivery of the signal be held off until it is later unblocked, which exacerbates the problem of signals being raised before they can be delivered.
Therefore, the kernel distinguishes two different phases related to signal transmission:
The kernel updates a data structure of the destination process to represent that a new signal has been sent.
Signal delivery
The kernel forces the destination process to react to the signal by changing its execution state, by starting the execution of a specified signal handler, or both.
Each signal generated can be delivered once, at most. Signals are consumable resources: once they have been delivered, all process descriptor information that refers to their previous existence is canceled.
Signals that have been generated but not yet delivered are called pending signals. At any time, only one pending signal of a given type may exist for a process; additional pending signals of the same type to the same process are not queued but simply discarded. Real-time signals are different, though: there can be several pending signals of the same type.
In general, a signal may remain pending for an unpredictable amount of time. The following factors must be taken into consideration:
· Signals are usually delivered only to the currently running process (that is, by the current process).
· Signals of a given type may be selectively blocked by a process (see the later section Section 10.4.4). In this case, the process does not receive the signal until it removes the block.
· When a process executes a signal-handler function, it usually masks the corresponding signal—i.e., it automatically blocks the signal until the handler terminates. A signal handler therefore cannot be interrupted by another occurrence of the handled signal and the function doesn't need to be re-entrant.
Although the notion of signals is intuitive, the kernel implementation is rather complex. The kernel must:
· Remember which signals are blocked by each process.
· When switching from Kernel Mode to User Mode, check whether a signal for any process has arrived. This happens at almost every timer interrupt (roughly every 10 ms).
· Determine whether the signal can be ignored. This happens when all of the following conditions are fulfilled:
o The destination process is not traced by another process (the PT_PTRACED flag in the process descriptor ptrace field is equal to 0).[1]
[1] If a process receives a signal while it is being traced, the kernel stops the process and notifies the tracing process by sending a SIGCHLD signal to it. The tracing process may, in turn, resume execution of the traced process by means of a SIGCONT signal.
o The signal is not blocked by the destination process.
o The signal is being ignored by the destination process (either because the process explicitly ignored it or because the process did not change the default action of the signal and that action is "ignore").
· Handle the signal, which may require switching the process to a handler function at any point during its execution and restoring the original execution context after the function returns.
Moreover, Linux must take into account the different semantics for signals adopted by BSD and System V; furthermore, it must comply with the rather cumbersome POSIX requirements.
There are three ways in which a process can respond to a signal:
1. Explicitly ignore the signal.
2. Execute the default action associated with the signal (see Table 10-1). This action, which is predefined by the kernel, depends on the signal type and may be any one of the following:
Terminate
The process is terminated (killed).
Dump
The process is terminated (killed) and a core file containing its execution context is created, if possible; this file may be used for debug purposes.
Ignore
The signal is ignored.
Stop
The process is stopped—i.e., put in the TASK_STOPPED state (see Section 3.2.1).
Continue
If the process is stopped (TASK_STOPPED), it is put into the TASK_RUNNING state.
3. Catch the signal by invoking a corresponding signal-handler function.
Notice that blocking a signal is different from ignoring it. A signal is not delivered as long as it is blocked; it is delivered only after it has been unblocked. An ignored signal is always delivered, and there is no further action.
The SIGKILL and SIGSTOP signals cannot be ignored, caught, or blocked, and their default actions must always be executed. Therefore, SIGKILL and SIGSTOP allow a user with appropriate privileges to terminate and to stop, respectively, any process,[2] regardless of the defenses taken by the program it is executing.
[2] There are two exceptions: it is not possible to send a signal to process 0 (swapper), and signals sent to process 1 (init) are always discarded unless they are caught. Therefore, process 0 never dies, while process 1 dies only when the init program terminates.
For any process in the system, the kernel must keep track of what signals are currently pending or masked, as well as how to handle every signal. To do this, it uses several data structures accessible from the processor descriptor. The most significant ones are shown in Figure 10-1.
The fields of the process descriptor related to signal handling are listed in Table 10-3.
The blocked field stores the signals currently masked out by the process. It is a sigset_t array of bits, one for each signal type:
typedef struct {
unsigned long sig[2];
} sigset_t;
Since each unsigned long number consists of 32 bits, the maximum number of signals that may be declared in Linux is 64 (the _NSIG macro specifies this value). No signal can have number 0, so the signal number corresponds to the index of the corresponding bit in a sigset_t variable plus one. Numbers between 1 and 31 correspond to the signals listed in Table 10-1, while numbers between 32 and 64 correspond to real-time signals.
The sig field of the process descriptor points to a signal descriptor, which describes how each signal must be handled by the process. The descriptor is stored in a signal_struct structure, which is defined as follows:
struct signal_struct {
atomic_t count;
struct k_sigaction action[64];
spinlock_t siglock;
};
As mentioned in Section 3.4.1, this structure may be shared by several processes by invoking the clone( ) system call with the CLONE_SIGHAND flag set.[3] The count field specifies the number of processes that share the signal_struct structure, while the siglock field is used to ensure exclusive access to its fields. The action field is an array of 64 k_sigaction structures that specify how each signal must be handled.
[3] If this is not done, about 1,300 bytes are added to the process data structures just to take care of signal handling.
Some architectures assign properties to a signal that are visible only to the kernel. Thus, the properties of a signal are stored in a k_sigaction structure, which contains both the properties hidden from the User Mode process and the more familiar sigaction structure that holds all the properties a User Mode process can see. Actually, on the 80 x 86 platform, all signal properties are visible to User Mode processes. Thus the k_sigaction structure simply reduces to a single sa structure of type sigaction, which includes the following fields:
sa_handler or sa_sigaction
Both names refer to the same field of the structure, which specifies the type of action to be performed; its value can be either a pointer to the signal handler, SIG_DFL (that is, the value 0) to specify that the default action is performed, or SIG_IGN (that is, the value 1) to specify that the signal is ignored. The two different names of this field corresponds to two different types of signal handler (see Section 10.4.2 later in this chapter).
sa_flags
This set of flags specifies how the signal must be handled; some of them are listed in Table 10-4.
sa_mask
This sigset_t variable specifies the signals to be masked when running the signal handler.
Table 10-4. Flags specifying how to handle a signal |
|
Flag Name |
Description |
SA_NOCLDSTOP |
Do not send SIGCHLD to the parent when the process is stopped. |
SA_NODEFER, SA_NOMASK |
Do not mask the signal while executing the signal handler. |
SA_RESETHAND, SA_ONESHOT |
Reset to default action after executing the signal handler. |
SA_ONSTACK |
Use an alternate stack for the signal handler (see the later section Section 10.3.3). |
SA_RESTART |
Interrupted system calls are automatically restarted (see the later section Section 10.3.4). |
SA_SIGINFO |
Provide additional information to the signal handler (see the later section Section 10.4.2). |
The pending field of the process descriptor is used to keep track of what signals are currently pending. It consists of a struct sigpending data structure, which is defined as follows:
struct sigpending {
struct sigqueue * head, ** tail;
sigset_t signal;
}
The signal field is a bit mask specifying the pending signals for the process, while the head and tail fields point to the first and last items of a pending signal queue. This queue is implemented through a list of struct sigqueue data structures:
struct sigqueue {
struct sigqueue * next;
siginfo_t info;
}
The nr_queued_signals variable stores the number of items in the queue, while the max_queued_signals defines the maximum length of the queue (which is 1,024 by default, but the system administrator can change this value either by writing into the /proc/sys/kernel/rtsig-max file or by issuing a suitable sysctl( ) system call).
The siginfo_t data structure is a 128-byte data structure that stores information about an occurrence of a specific signal; it includes the following fields:
si_signo
The signal number.
si_errno
The error code of the instruction that caused the signal to be raised, or 0 if there was no error.
si_code
A code identifying who raised the signal (see Table 10-5).
Table 10-5. The most significant signal sender codes |
|
Code Name |
Sender |
SI_USER |
kill( ) and raise( ) (see the later section Section 10.4) |
SI_KERNEL |
Generic kernel function |
SI_TIMER |
Timer expiration |
SI_ASYNCIO |
Asynchronous I/O completion |
_sifields
A union storing information depending on the type of signal. For instance, the siginfo_t data structure relative to an occurrence of the SIGKILL signal records the PID and the UID of the sender process here; conversely, the data structure relative to an occurrence of the SIGSEGV signal stores the memory address whose access caused the signal to be raised.
Several functions and macros are used by the kernel to handle signals. In the following description, set is a pointer to a sigset_t variable, nsig is the number of a signal, and mask is an unsigned long bit mask.
sigemptyset(set) and sigfillset(set)
Sets the bits in the sigset_t variable to 0 or 1, respectively.
sigaddset(set,nsig) and sigdelset(set,nsig)
Sets the bit of the sigset_t variable corresponding to signal nsig to 1 or 0, respectively. In practice, sigaddset( ) reduces to:
set->sig[(nsig - 1) / 32] |= 1UL << ((nsig - 1) % 32);
and sigdelset( ) to:
set->sig[(nsig - 1) / 32] &= ~(1UL << ((nsig - 1) % 32));
sigaddsetmask(set,mask) and sigdelsetmask(set,mask)
Sets all the bits of the sigset_t variable whose corresponding bits of mask are on 1 or 0, respectively. They can be used only with signals that are between 1 and 32. The corresponding functions reduce to:
set->sig[0] |= mask;
and to:
set->sig[0] &= ~mask;
sigismember(set,nsig)
Returns the value of the bit of the sigset_t variable corresponding to the signal nsig. In practice, this function reduces to:
return 1 & (set->sig[(nsig-1) / 32] >> ((nsig-1) % 32));
sigmask(nsig)
Yields the bit index of the signal nsig. In other words, if the kernel needs to set, clear, or test a bit in an element of sigset_t that corresponds to a particular signal, it can derive the proper bit through this macro.
sigandsets(d,s1,s2), sigorsets(d,s1,s2), and signandsets(d,s1,s2)
Performs a logical AND, a logical OR, and a logical NAND, respectively, between the sigset_t variables to which s1 and s2 point; the result is stored in the sigset_t variable to which d points.
sigtestsetmask(set,mask)
Returns the value 1 if any of the bits in the sigset_t variable that correspond to the bits set to 1 in mask is set; it returns 0 otherwise. It can be used only with signals that have a number between 1 and 32.
siginitset(set,mask)
Initializes the low bits of the sigset_t variable corresponding to signals between 1 and 32 with the bits contained in mask, and clears the bits corresponding to signals between 33 and 63.
siginitsetinv(set,mask)
Initializes the low bits of the sigset_t variable corresponding to signals between 1 and 32 with the complement of the bits contained in mask, and sets the bits corresponding to signals between 33 and 63.
signal_pending(p)
Returns the value 1 (true) if the process identified by the *p process descriptor has nonblocked pending signals, and returns the value 0 (false) if it doesn't. The function is implemented as a simple check on the sigpending field of the process descriptor.
recalc_sigpending(t)
Checks whether the process identified by the process descriptor at *t has nonblocked pending signals by looking at the sig and blocked fields of the process, and then sets the sigpending field to or 1 as follows:
ready = t->pending.signal.sig[1] & ~t->blocked.sig[1];
ready |= t->pending.signal.sig[0] & ~t->blocked.sig[0];
t->sigpending = (ready != 0);
flush_signals(t)
Deletes all signals sent to the process identified by the process descriptor at *t. This is done by clearing both the t->sigpending and the t->pending.signal fields and by emptying the queue of pending signals.