Signals were introduced by the first Unix systems to allow interactions between User Mode processes; the kernel also uses them to notify processes of system events. Signals have been around for 30 years with only minor changes.
The first sections of this chapter examine in detail how signals are handled by the Linux kernel, then we discuss the system calls that allow processes to exchange signals.
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 × 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.
Table 10-1. The first 31 signals in Linux/i386
# |
Signal name |
Default action |
Comment |
POSIX |
---|---|---|---|---|
1 |
|
Terminate |
Hang up controlling terminal or process |
Yes |
2 |
|
Terminate |
Interrupt from keyboard |
Yes |
3 |
|
Dump |
Quit from keyboard |
Yes |
4 |
|
Dump |
Illegal instruction |
Yes |
5 |
|
Dump |
Breakpoint for debugging |
No |
6 |
|
Dump |
Abnormal termination |
Yes |
6 |
|
Dump |
Equivalent to |
No |
7 |
|
Dump |
Bus error |
No |
8 |
|
Dump |
Floating-point exception |
Yes |
9 |
|
Terminate |
Forced-process termination |
Yes |
10 |
|
Terminate |
Available to processes |
Yes |
11 |
|
Dump |
Invalid memory reference |
Yes |
12 |
|
Terminate |
Available to processes |
Yes |
13 |
|
Terminate |
Write to pipe with no readers |
Yes |
14 |
|
Terminate |
Real-timer clock |
Yes |
15 |
|
Terminate |
Process termination |
Yes |
16 |
|
Terminate |
Coprocessor stack error |
No |
17 |
|
Ignore |
Child process stopped or terminated |
Yes |
18 |
|
Continue |
Resume execution, if stopped |
Yes |
19 |
|
Stop |
Stop process execution |
Yes |
20 |
|
Stop |
Stop process issued from tty |
Yes |
21 |
|
Stop |
Background process requires input |
Yes |
22 |
|
Stop |
Background process requires output |
Yes |
23 |
|
Ignore |
Urgent condition on socket |
No |
24 |
|
Dump |
CPU time limit exceeded |
No |
25 |
|
Dump |
File size limit exceeded |
No |
26 |
|
Terminate |
Virtual timer clock |
No |
27 |
|
Terminate |
Profile timer clock |
No |
28 |
|
Ignore |
Window resizing |
No |
29 |
|
Terminate |
I/O now possible |
No |
29 |
|
Terminate |
Equivalent to |
No |
30 |
|
Terminate |
Power supply failure |
No |
31 |
|
Dump |
Bad system call |
No |
31 |
|
Dump |
Equivalent to |
No |
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.
Table 10-2. The most significant system calls related to signals
System call |
Description |
---|---|
|
Send a signal to a process. |
|
Change the action associated with a signal. |
|
Similar to |
|
Check whether there are pending signals. |
|
Modify the set of blocked signals. |
|
Wait for a signal. |
|
Change the action associated with a real-time signal. |
|
Check whether there are pending real-time signals. |
|
Modify the set of blocked real-time signals. |
|
Send a real-time signal to a process. |
|
Wait for a real-time signal. |
|
Similar to |
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.
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:
The destination process is not traced by another process (the
PT_PTRACED
flag in the process descriptor
ptrace
field is equal to 0).[68]
The signal is not blocked by the destination process.
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:
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:
The process is terminated (killed).
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.
The signal is ignored.
The process is stopped—i.e., put in the
TASK_STOPPED
state (see Section 3.2.1).
If the process is stopped (TASK_STOPPED
), it is
put into the TASK_RUNNING
state.
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,[69] regardless of the defenses taken by the program it is
executing.
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.
Table 10-3. Process descriptor fields related to signal handling
Type |
Name |
Description |
---|---|---|
|
|
Spin lock protecting |
|
|
Pointer to the process’s signal descriptor |
|
|
Mask of blocked signals |
|
|
Data structure storing the pending signals |
|
|
Address of alternate signal handler stack |
|
|
Size of alternate signal handler stack |
|
|
Pointer to a function used by a device driver to block some signals of the process |
|
|
Pointer to data that might be used by the notifier function (previous field of table) |
|
|
Bit mask of signals blocked by a device driver through a notifier function |
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.[70] 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.
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 × 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 |
---|---|
|
Do not send |
|
Do not mask the signal while executing the signal handler. |
|
Reset to default action after executing the signal handler. |
|
Use an alternate stack for the signal handler (see the later section Section 10.3.3). |
|
Interrupted system calls are automatically restarted (see the later section Section 10.3.4). |
|
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 |
---|---|
|
|
|
Generic kernel function |
|
Timer expiration |
|
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.
[68] 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.
[69] 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.
[70] If this is not done, about 1,300 bytes are added to the process data structures just to take care of signal handling.
3.128.171.246