|
|
A process calls sigaction(2) to set the disposition for a signal. If the signal is to be caught, a process specifies the ``handler'' function, which will be called when the signal occurs. Alternatively, a process can specify that the signal is to be ``blocked'' or ``ignored'', or it may specify that the ``default'' action is to be taken when the signal occurs.
For each signal, the <signal.h> header file establishes the default signal action to be one of the following:
#include <signal.h>assigns the address of the interrupt handler specified by sa to the signal specified by signo. If osa is non-zero, sigaction stores the previous signal action at that address.sigaction(signo, sa, osa) int signo; struct sigaction *sa; struct sigaction *osa;
The first argument to the function sigaction is just an integer code number that represents a signal. The <signal.h> header file defines symbolic names for the signal numbers and must always be included when signals are used.
The second and third arguments to sigaction are pointers to the sigaction structure defined by the <signal.h> header file to contain the following members (see signal(2)):
void (*sa_handler)(); sigset_t sa_mask; int sa_flags;The member
sa_handler
specifies what action to take on receipt of the signal.
Assigning one of the following values to sa_handler
establishes the signal action as follows:
The sa_handler routine is called by a C call of the form
(*sa_handler)(signo, infop, ucp); int signo; siginfo_t *infop; ucontext_t *ucp;signo gives the number of the signal that occurred. infop is either equal to 0, or points to a structure that contains information detailing the reason why the signal was generated. This information must be explicitly asked for when the signal's action is specified. The ucp parameter is a pointer to a structure containing the process's context prior to the delivery of the signal, and will be used to restore the process's context upon return from the signal handler.
sa_mask specifies the set of signals to be masked when the signal handler is invoked; it implicitly includes the signal which invoked the signal handler. When a signal condition arises for a process, the signal is added to a set of signals pending for the process. If the signal is not currently blocked by the process then it is delivered. The process of signal delivery adds the signal to be delivered and those signals specified in the sa_mask for the associated signal handler to a set of those masked for the process, saves the current process context, and places the process in the context of the signal-handling routine. The call is arranged so that if the signal-handling routine exits normally, the signal mask is restored and the process resumes execution in the original context. Should the process wish to resume in a different context, it must arrange to restore the signal mask itself.
Signal masks are usually constructed with the following routines (see sigsetops(3C)):
The mask of blocked signals is independent of signal handlers for delays. It delays the delivery of signals much as a raised hardware interrupt priority level delays hardware interrupts. Preventing an interrupt from occurring by changing the handler is analogous to disabling a device from further interrupts.
The member sa_flags specifies special properties of the signal, such as whether system calls should be restarted if the signal handler returns, if the signal action should be reset to SIG_DFL when it is caught, and whether the signal handler should operate on the normal run-time stack or a special signal stack (see ``Signal stacks'' and sigaction(2)).
Initially, all signals are set to SIG_DFL or SIG_IGN prior to entry of the function main (see exec(2)). Once an action is established for a specific signal, it usually remains established until another action is explicitly established by a call to either signal(2), sigset(2), sigignore(2), or sigaction(2) explicitly establishes another action, or until the process calls fork(2) or an exec(2) function. A child process inherits the actions of the parent for the defaulted and ignored signals. Caught signals are reset to the default action in the child process. This is necessary since the address linkage for signal-handling routines specified in the parent are no longer appropriate in the child. When a process execs, all signals set to catch the signal are reset to SIG_DFL. Alternatively, a process may request that the action for a signal automatically be reset to SIG_DFL after catching it (see signal(2) and sigaction(2)).
In the following example, the first call to sigaction causes interrupts to be ignored; while the second call to sigaction restores the default action for interrupts, which is to terminate the process. In both cases, sigaction returns the previous signal action in the final argument old_act.
#include <signal.h>Instead of the special values SIG_IGN or SIG_DFL, the second argument to sigaction may specify a signal-handling routine; in which case, the specified function is called when the signal occurs. Most commonly this facility is used to allow the program to clean up unfinished business before terminating, for example to delete a temporary file, as in the following example:main() { struct sigaction new_act, old_act;
new_act.sa_handler = SIG_IGN; sigaction(SIGINT, &new_act, &old_act);
/* do processing */
new_act.sa_handler = SIG_DFL; sigaction(SIGINT, &new_act, &old_act); }
#include <signal.h>main() { struct sigaction new_act, old_act; void on_intr();
new_act.sa_handler = SIG_IGN; sigaction(SIGINT, &new_act, &old_act);
if (old_act.sa_handler != SIG_IGN) { new_act.sa_handler = on_intr; sigaction(SIGINT, &new_act, &old_act); }
/* do processing */
exit(0); /* exit with normal status */ }
void on_intr() {
unlink(tempfile);
exit(1); /* exit with interrupted status */ }
Signal programming example
Before establishing on_intr as the signal handler for SIGINT, the program tests the state of interrupt handling, and continues to ignore interrupts if they are already being ignored. This is needed because SIGINT is sent to all processes started from a specific terminal. Accordingly, when a program is initiated with ``&'' to run without any interaction in the background, the shell turns off interrupts for it so it will not be stopped by interrupts intended for foreground processes. If this program began by setting on_intr to catch all interrupts regardless, that would undo the shell's efforts to protect it when run in the background. The solution, shown above, is to call sigaction for SIGINT first to get the signal action currently established for the interrupt signal, which is returned in the third argument to sigaction. If interrupt signals were already being ignored, the process should continue to ignore them; otherwise, they should be caught. In that case, the second call to sigaction for SIGINT establishes a new signal action which specifies on_intr as the signal handler.
A more sophisticated program may wish to intercept and interpret SIGINT as a request to stop what it is doing and return to its own command processing loop. Think of a text editor: interrupting a long printout should not cause it to terminate and lose the work already done. The outline of the code for this case is probably best written as follows:
#include <signal.h> #include <setjmp.h> jmp_buf sjbuf;main() { struct sigaction new_act, old_act; void on_intr();
new_act.sa_handler = SIG_IGN; sigaction(SIGINT, &new_act, &old_act);
setjmp(sjbuf); /* save current stack position */
if (old_act.sa_handler != SIG_IGN) { new_act.sa_handler = on_intr; sigaction(SIGINT, &new_act, &old_act); } /* * main command processing loop */ exit(0) }
void on_intr() {
printf("\nInterrupt\n"); /* print message */
longjmp(sjbuf); /* return to saved state */ }
Signal programming example
The <setjmp.h> header file declares the type jmp_buf for a buffer in which the state can be saved, and the program above declares sjbuf to be of type jmp_buf which is an array of some type. The function setjmp saves the current context of the user process in sjbuf. When an interrupt occurs, a call to the function on_intr is forced, which prints a message and could set flags or do something else. The function longjmp takes as argument an object stored into by setjmp, and restores control to the location after the call to setjmp, so control (and the stack level) pops back to place in the program main where the signal is set up and the main loop entered. Notice, by the way, that the signal gets set again after an interrupt occurs. This is necessary; most signals are automatically reset to their default action when they occur.
Some programs that want to detect signals simply cannot be stopped at an arbitrary point, for example in the middle of updating a linked list. If the function called on occurrences of a signal sets a flag and then returns instead of calling exit or longjmp, execution resumes at the exact point it was interrupted. The interrupt flag can then be tested later.
This approach has the following difficulty. Suppose the program is reading the terminal when the interrupt is sent. The specified function is duly called; it sets its flag and returns. If it were really true, as said earlier, that ``execution resumes at the exact point it was interrupted,'' the program would continue reading the terminal until the user typed another line. This behavior might well be confusing, since the user might not know the program is reading, and presumably would prefer to have the signal take effect instantly. The method chosen to resolve this difficulty is to terminate the read from the terminal when execution resumes after the signal, with read returning an error code (EINTR) which indicates the interruption.
As a consequence, programs which catch and resume execution after signals should be prepared for ``errors'' caused by interrupted system calls. (The ones to watch out for in particular are wait and pause as well as any read from the terminal).
A program whose on_intr function just sets intflag, resets the interrupt signal, and returns, should usually include code like the following when it reads the standard input or directly from a terminal device.
if (getchar() == EOF) if (intflag) /* EOF caused by interrupt */ else /* actual end-of-file */A final subtlety to keep in mind becomes important when signal handling is combined with execution of other programs. Suppose a program handles interrupts, and also includes a method (like ``!'' in the editor) whereby other programs can be executed. Then the code should look something like this:
if (fork() == 0) exec( ... );Why is this? Again, its not obvious but not really difficult. Suppose the program called catches its own interrupts. When this subprogram gets interrupted, it receives the signal, returns to its main loop and probably tries to read the terminal. But the calling program also pops out of its wait for the subprogram and tries to read the terminal. Two processes trying to read the terminal is very unfortunate, since the system randomly decides which should get each line of input. A simple solution is for the parent to ignore interrupts until the child completes.new_act.sa_handler = SIG_IGN; /* ignore interrupts */ sigaction(SIGINT, &new_act, &old_act);
wait(&status); /* until the child completes */
new_act.sa_handler = on_intr; /* restore interrupts */ sigaction(SIGINT, &new_act, &old_act);
This reasoning is reflected in the function system as follows:
#include <signal.h>system(cmd_str) /* run command string */ char *cmd_str; { int status; pid_t wpid, xpid; struct sigaction sig_act, i_stat, q_stat;
if ((xpid=fork()) == 0) { execl("/bin/sh", "sh", "-c", cmd_str, 0); _exit(127); }
sig_act.sa_handler = SIG_IGN; sigaction(SIGINT, &sig_act, &i_stat);
sig_act.sa_handler = SIG_IGN; sigaction(SIGQUIT, &sig_act, &q_stat);
while ( ((wpid=wait(&status)) != xpid) && (wpid != -1) ) ; if (wpid == -1) status = -1;
sigaction(SIGINT, &i_stat, &sig_act); sigaction(SIGQUIT, &q_stat, &sig_act);
return(status); }
system - signal programming example