10.1 The Role of 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 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.

Table 10-1. The first 31 signals in Linux/i386

#

Signal name

Default action

Comment

POSIX

1

SIGHUP

Terminate

Hang up controlling terminal or process

Yes

2

SIGINT

Terminate

Interrupt from keyboard

Yes

3

SIGQUIT

Dump

Quit from keyboard

Yes

4

SIGILL

Dump

Illegal instruction

Yes

5

SIGTRAP

Dump

Breakpoint for debugging

No

6

SIGABRT

Dump

Abnormal termination

Yes

6

SIGIOT

Dump

Equivalent to SIGABRT

No

7

SIGBUS

Dump

Bus error

No

8

SIGFPE

Dump

Floating-point exception

Yes

9

SIGKILL

Terminate

Forced-process termination

Yes

10

SIGUSR1

Terminate

Available to processes

Yes

11

SIGSEGV

Dump

Invalid memory reference

Yes

12

SIGUSR2

Terminate

Available to processes

Yes

13

SIGPIPE

Terminate

Write to pipe with no readers

Yes

14

SIGALRM

Terminate

Real-timer clock

Yes

15

SIGTERM

Terminate

Process termination

Yes

16

SIGSTKFLT

Terminate

Coprocessor stack error

No

17

SIGCHLD

Ignore

Child process stopped or terminated

Yes

18

SIGCONT

Continue

Resume execution, if stopped

Yes

19

SIGSTOP

Stop

Stop process execution

Yes

20

SIGTSTP

Stop

Stop process issued from tty

Yes

21

SIGTTIN

Stop

Background process requires input

Yes

22

SIGTTOU

Stop

Background process requires output

Yes

23

SIGURG

Ignore

Urgent condition on socket

No

24

SIGXCPU

Dump

CPU time limit exceeded

No

25

SIGXFSZ

Dump

File size limit exceeded

No

26

SIGVTALRM

Terminate

Virtual timer clock

No

27

SIGPROF

Terminate

Profile timer clock

No

28

SIGWINCH

Ignore

Window resizing

No

29

SIGIO

Terminate

I/O now possible

No

29

SIGPOLL

Terminate

Equivalent to SIGIO

No

30

SIGPWR

Terminate

Power supply failure

No

31

SIGSYS

Dump

Bad system call

No

31

SIGUNUSED

Dump

Equivalent to SIGSYS

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

kill( )

Send a signal to a process.

sigaction( )

Change the action associated with a signal.

signal( )

Similar to sigaction( ).

sigpending( )

Check whether there are pending signals.

sigprocmask( )

Modify the set of blocked signals.

sigsuspend( )

Wait for a signal.

rt_sigaction( )

Change the action associated with a real-time signal.

rt_sigpending( )

Check whether there are pending real-time signals.

rt_sigprocmask( )

Modify the set of blocked real-time signals.

rt_sigqueueinfo( )

Send a real-time signal to a process.

rt_sigsuspend( )

Wait for a real-time signal.

rt_sigtimedwait( )

Similar to rt_sigsuspend( ).

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:

Signal generation

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.

10.1.1 Actions Performed upon Delivering a Signal

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.

10.1.2 Data Structures Associated with Signals

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.

Figure 10-1. The most significant data structures related to signal handling

figs/ULK2_1001.gif

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

spinlock_t

sigmask_lock

Spin lock protecting pending and blocked

struct signal_struct *

sig

Pointer to the process's signal descriptor

sigset_t

blocked

Mask of blocked signals

struct sigpending

pending

Data structure storing the pending signals

unsigned long

sas_ss_sp

Address of alternate signal handler stack

size_t

sas_ss_size

Size of alternate signal handler stack

int (*) (void *)

notifier

Pointer to a function used by a device driver to block some signals of the process

void *

notifier_data

Pointer to data that might be used by the notifier function (previous field of table)

sigset_t *

notifier_mask

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.[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.

10.1.3 Operations on Signal Data Structures

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.