xref: /original-bsd/share/doc/psd/04.uprog/p6 (revision c3e32dec)
%sccs.include.proprietary.roff%

@(#)p6 8.1 (Berkeley) 06/08/93

SIGNALS \(em INTERRUPTS AND ALL THAT

This section is concerned with how to deal gracefully with signals from the outside world (like interrupts), and with program faults. Since there's nothing very useful that can be done from within C about program faults, which arise mainly from illegal memory references or from execution of peculiar instructions, we'll discuss only the outside-world signals: T interrupt , which is sent when the C DEL character is typed; T quit , generated by the C FS character; T hangup , caused by hanging up the phone; and T terminate , generated by the T kill command. When one of these events occurs, the signal is sent to T all processes which were started from the corresponding terminal; unless other arrangements have been made, the signal terminates the process. In the T quit case, a core image file is written for debugging purposes.

The routine which alters the default action is called signal . It has two arguments: the first specifies the signal, and the second specifies how to treat it. The first argument is just a number code, but the second is the address is either a function, or a somewhat strange code that requests that the signal either be ignored, or that it be given the default action. The include file signal.h gives names for the various arguments, and should always be included when signals are used. Thus

1 #include <signal.h> ... signal(SIGINT, SIG_IGN);

2 causes interrupts to be ignored, while

1 signal(SIGINT, SIG_DFL);

2 restores the default action of process termination. In all cases, signal returns the previous value of the signal. The second argument to signal may instead be the name of a function (which has to be declared explicitly if the compiler hasn't seen it already). In this case, the named routine will be 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:

1 #include <signal.h> main() { int onintr(); if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, onintr); /* Process ... */ exit(0); } onintr() { unlink(tempfile); exit(1); }

2

Why the test and the double call to signal ? Recall that signals like interrupt are sent to .ul all processes started from a particular terminal. Accordingly, when a program is to be run non-interactively (started by & ), the shell turns off interrupts for it so it won't be stopped by interrupts intended for foreground processes. If this program began by announcing that all interrupts were to be sent to the onintr routine regardless, that would undo the shell's effort to protect it when run in the background.

The solution, shown above, is to test the state of interrupt handling, and to continue to ignore interrupts if they are already being ignored. The code as written depends on the fact that signal returns the previous state of a particular signal. If signals were already being ignored, the process should continue to ignore them; otherwise, they should be caught.

A more sophisticated program may wish to intercept an interrupt and interpret it 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 like this:

1 #include <signal.h> #include <setjmp.h> jmp_buf sjbuf; main() { int (*istat)(), onintr(); istat = signal(SIGINT, SIG_IGN); /* save original status */ setjmp(sjbuf); /* save current stack position */ if (istat != SIG_IGN) signal(SIGINT, onintr); /* main processing loop */ }

2

1 onintr() { printf("\enInterrupt\en"); longjmp(sjbuf); /* return to saved state */ }

2 The include file setjmp.h declares the type jmp_buf an object in which the state can be saved. sjbuf is such an object; it is an array of some sort. The setjmp routine then saves the state of things. When an interrupt occurs, a call is forced to the onintr routine, which can print a message, set flags, or whatever. 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) will pop back to the place in the main routine 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 can't be stopped at an arbitrary point, for example in the middle of updating a linked list. If the routine called on occurrence of a signal sets a flag and then returns instead of calling exit or longjmp , execution will continue at the exact point it was interrupted. The interrupt flag can then be tested later.

There is one difficulty associated with this approach. Suppose the program is reading the terminal when the interrupt is sent. The specified routine is duly called; it sets its flag and returns. If it were really true, as we said above, 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 that the program is reading; he presumably would prefer to have the signal take effect instantly. The method chosen to resolve this difficulty is to terminate the terminal read when execution resumes after the signal, returning an error code which indicates what happened.

Thus programs which catch and resume execution after signals should be prepared for ``errors'' which are caused by interrupted system calls. (The ones to watch out for are reads from a terminal, wait , and pause .) A program whose onintr program just sets intflag , resets the interrupt signal, and returns, should usually include code like the following when it reads the standard input:

1 if (getchar() == EOF) if (intflag) /* EOF caused by interrupt */ else /* true end-of-file */

2

A final subtlety to keep in mind becomes important when signal-catching is combined with execution of other programs. Suppose a program catches interrupts, and also includes a method (like ``!'' in the editor) whereby other programs can be executed. Then the code should look something like this:

1 if (fork() == 0) execl(...); signal(SIGINT, SIG_IGN); /* ignore interrupts */ wait(&status); /* until the child is done */ signal(SIGINT, onintr); /* restore interrupts */

2 Why is this? Again, it's not obvious but not really difficult. Suppose the program you call catches its own interrupts. If you interrupt the subprogram, it will get the signal and return to its main loop, and probably read your terminal. But the calling program will also pop out of its wait for the subprogram and read your terminal. Having two processes reading your terminal is very unfortunate, since the system figuratively flips a coin to decide who should get each line of input. A simple way out is to have the parent program ignore interrupts until the child is done. This reasoning is reflected in the standard I/O library function system :

1 #include <signal.h> system(s) /* run command string s */ char *s; { int status, pid, w; register int (*istat)(), (*qstat)(); if ((pid = fork()) == 0) { execl("/bin/sh", "sh", "-c", s, 0); _exit(127); } istat = signal(SIGINT, SIG_IGN); qstat = signal(SIGQUIT, SIG_IGN); while ((w = wait(&status)) != pid && w != -1) ; if (w == -1) status = -1; signal(SIGINT, istat); signal(SIGQUIT, qstat); return(status); }

2

As an aside on declarations, the function signal obviously has a rather strange second argument. It is in fact a pointer to a function delivering an integer, and this is also the type of the signal routine itself. The two values SIG_IGN and SIG_DFL have the right type, but are chosen so they coincide with no possible actual functions. For the enthusiast, here is how they are defined for the PDP-11; the definitions should be sufficiently ugly and nonportable to encourage use of the include file.

1 #define SIG_DFL (int (*)())0 #define SIG_IGN (int (*)())1

2