Signal

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.

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.

Signals serve two main purposes:
• To make a process aware that a specific event has occurred
• To cause a process to execute a signal handler function included in its code
Of course, the two purposes are not mutually exclusive, because often a process
must react to some event by executing a specific routine.

Table 11-1. The first 31 signals in Linux/i386
# Signal nameDefault action Comment POSIX
1 SIGHUP TerminateHang up controlling terminal or processYes
2 SIGINT TerminateInterrupt from keyboardYes
3 SIGQUIT DumpQuit from keyboardYes
4 SIGILL DumpIllegal instructionYes
5 SIGTRAP DumpBreakpoint for debuggingNo
6 SIGABRT DumpAbnormal terminationYes
6 SIGIOT DumpEquivalent to SIGABRTNo
7 SIGBUS DumpBus errorNo
8 SIGFPE DumpFloating-point exceptionYes
9 SIGKILL TerminateForced-process terminationYes
10 SIGUSR1 TerminateAvailable to processesYes
11 SIGSEGV DumpInvalid memory referenceYes
12 SIGUSR2 TerminateAvailable to processesYes
13 SIGPIPE TerminateWrite to pipe with no readersYes
14 SIGALRM TerminateReal-timerclockYes
15 SIGTERM TerminateProcess terminationYes
16 SIGSTKFLT TerminateCoprocessor stack errorNo
17 SIGCHLD IgnoreChild process stopped or terminated, 
or got signal if tracedYes
18 SIGCONT ContinueResume execution, if stoppedYes
19 SIGSTOP StopStopprocess executionYes
20 SIGTSTP StopStopprocess issued from ttyYes
21 SIGTTIN StopBackground process requires inputYes
22 SIGTTOU StopBackground process requires outputYes
23 SIGURG IgnoreUrgent condition on socketNo
24 SIGXCPU DumpCPU time limit exceededNo
25 SIGXFSZ DumpFile size limit exceededNo
26 SIGVTALRM TerminateVirtual timer clockNo
27 SIGPROF TerminateProfile timer clockNo
28 SIGWINCH Ignore Window resizingNo
29 SIGIO TerminateI/O now possibleNo
29 SIGPOLL TerminateEquivalent to SIGIONo
30 SIGPWR TerminatePower supply failureNo
31 SIGSYS DumpBad system callNo
31 SIGUNUSED DumpEquivalent to SIGSYSNo

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

A number of system calls allow programmers to send signals and determine how
their processes respond to the signals they receive.

Table 11-2. The most significant system calls related to signals
System call Description
kill( ) Send a signal to a thread group
tkill( ) Send a signal to a process
tgkill() Send a signal to a process in a specific thread group
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 thread group
rt_sigsuspend( ) Wait for a real-time signal
rt_sigtimedwait( ) imilar 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 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, to the
current process).
• Signals of a given type may be selectively blocked by a process. 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 reentrant.

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 a
process has arrived. This happens at almost every timer interrupt (roughly every
millisecond).
• 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).
— 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.

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 11-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.
Continue
If the process was 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, every process,*
regardless of the defenses taken by the program it is executing.

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.

A signal is fatal for a given process if delivering the signal causes the kernel to kill the
process. The SIGKILL signal is always fatal; moreover, each signal whose default
action is “Terminate” and which is not caught by a process is also fatal for that process.
Notice, however, that a signal caught by a process and whose corresponding
signal-handler function terminates the process is not fatal, because the process chose
to terminate itself rather than being killed by the kernel.

The POSIX 1003.1 standard has some stringent requirements for signal handling of
multithreaded applications:
• Signal handlers must be shared among all threads of a multithreaded application;
however, each thread must have its own mask of pending and blocked signals.
• The kill() and sigqueue() POSIX library functions must send signals to whole multithreaded
applications, not to a specific thread. The same holds for all signals
(such as SIGCHLD, SIGINT, or SIGQUIT) generated by the kernel.
• Each signal sent to a multithreaded application will be delivered to just one
thread, which is arbitrarily chosen by the kernel among the threads that are not
blocking that signal.
• If a fatal signal is sent to a multithreaded application, the kernel will kill all
threads of the application—not just the thread to which the signal has been
delivered.

In order to comply with the POSIX standard, the Linux 2.6 kernel implements a
multithreaded application as a set of lightweight processes belonging to the same
thread group.

Thread groups were added to support the POSIX threads notion of a set of threads that share a single PID.  
Internally, this shared PID is the so-called thread group identifier (TGID) for the thread group.  
Since Linux 2.4, calls to getpid(2) return the TGID of the caller.
The threads within a group can be distinguished by their(system-wide) unique thread IDs (TID).  
A thread can obtain its own TID using gettid(2).

Furthermore, a pending signal is private if it has been sent to a specific process; it is
shared if it has been sent to a whole thread group.

For each process in the system, the kernel must keep track of what signals are currently
pending or masked; the kernel must also keep track of how every thread group
is supposed to handle every signal. To do this, the kernel uses several data structures
accessible from the process descriptor. 

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

The sigaction data structure:

sa_handler
This field specifies the type of action to be performed; its value can be 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.
sa_flags
This set of flags specifies how the signal must be handled; some of them are
listed in Table 11-6.
sa_mask
This sigset_t variable specifies the signals to be masked when running the signal
handler.

Table 11-6. Flags specifying how to handle a signal
Flag Name Description
SA_NOCLDSTOP Applies only to SIGCHLD; do not send SIGCHLD to the parent when the process is stopped
SA_NOCLDWAIT Applies only to SIGCHLD; do not create a zombie when the process terminates
SA_SIGINFO Provide additional information to the signal handler
SA_ONSTACK Use an alternative stack for the signal handler
SA_RESTART Interrupted system calls are automatically restarted
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

A child that terminates, but has not been waited for becomes a "zombie".  
The kernel maintains a minimal set of information about the zombie process(PID, termina-tion status, 
resource usage information) in order to allow the parent to later perform a wait to obtain information 
about the child. As long as a zombie is not removed from the system via a wait, it will consume a slot 
in the kernel process table, and if this table fills, it will not be possible to create further processes. 
If a parent process terminates, then its "zombie" children (if any) are adopted by init(8), which 
automatically performs a wait to remove the zombies.

POSIX.1-2001 specifies that if the disposition of SIGCHLD is set to SIG_IGN or the SA_NOCLDWAIT flag 
is set for SIGCHLD (see sigaction(2)), then children that terminate do not become zombies and a call to 
wait() or waitpid() will block until all children have terminated, and then fail with errno set to ECHILD. 
(The original POSIX standard left the behaviour of setting SIGCHLD to SIG_IGN unspecified.) Linux 2.6 
conforms to this specification.  However, Linux 2.4 (and earlier) does not: if a wait() or waitpid() call 
is made while SIGCHLD is being ignored, the call behaves just as though SIGCHLD were not being ignored, 
that is, the call blocks until the next child terminates and then returns the process ID and status of that child.


来源:Understanding The Linuxkernel 3rd.Edition
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值