home *** CD-ROM | disk | FTP | other *** search
- .NH
- SIGNALS \(em INTERRUPTS AND ALL THAT
- .PP
- 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:
- .IT interrupt ,
- which is sent when the
- .UC DEL
- character is typed;
- .IT quit ,
- generated by the
- .UC FS
- character;
- .IT hangup ,
- caused by hanging up the phone;
- and
- .IT terminate ,
- generated by the
- .IT kill
- command.
- When one of these events occurs,
- the signal is sent to
- .IT all
- processes which were started
- from the corresponding terminal;
- unless other arrangements have been made,
- the signal
- terminates the process.
- In the
- .IT quit
- case, a core image file is written for debugging
- purposes.
- .PP
- The routine which alters the default action
- is
- called
- .UL 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
- .UL signal.h
- gives names for the various arguments, and should always be included
- when signals are used.
- Thus
- .P1
- #include <signal.h>
- ...
- signal(SIGINT, SIG_IGN);
- .P2
- causes interrupts to be ignored, while
- .P1
- signal(SIGINT, SIG_DFL);
- .P2
- restores the default action of process termination.
- In all cases,
- .UL signal
- returns the previous value of the signal.
- The second argument to
- .UL 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:
- .P1
- #include <signal.h>
-
- main()
- {
- int onintr();
-
- if (signal(SIGINT, SIG_IGN) != SIG_IGN)
- signal(SIGINT, onintr);
-
- /* Process ... */
-
- exit(0);
- }
-
- onintr()
- {
- unlink(tempfile);
- exit(1);
- }
- .P2
- .PP
- Why the test and the double call to
- .UL 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
- .UL & ),
- 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
- .UL onintr
- routine regardless,
- that would undo the shell's effort to protect it
- when run in the background.
- .PP
- 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
- .UL 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.
- .PP
- 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:
- .P1
- #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 */
- }
- .P2
- .P1
- onintr()
- {
- printf("\nInterrupt\n");
- longjmp(sjbuf); /* return to saved state */
- }
- .P2
- The include file
- .UL setjmp.h
- declares the type
- .UL jmp_buf
- an object in which the state
- can be saved.
- .UL sjbuf
- is such an object; it is an array of some sort.
- The
- .UL setjmp
- routine then saves
- the state of things.
- When an interrupt occurs,
- a call is forced to the
- .UL onintr
- routine,
- which can print a message, set flags, or whatever.
- .UL longjmp
- takes as argument an object stored into by
- .UL setjmp ,
- and restores control
- to the location after the call to
- .UL 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.
- .PP
- 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
- .UL exit
- or
- .UL longjmp ,
- execution will continue
- at the exact point it was interrupted.
- The interrupt flag can then be tested later.
- .PP
- 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.
- .PP
- 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,
- .UL wait ,
- and
- .UL pause .)
- A program
- whose
- .UL onintr
- program just sets
- .UL intflag ,
- resets the interrupt signal, and returns,
- should usually include code like the following when it reads
- the standard input:
- .P1
- if (getchar() == EOF)
- if (intflag)
- /* EOF caused by interrupt */
- else
- /* true end-of-file */
- .P2
- .PP
- 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:
- .P1
- if (fork() == 0)
- execl(...);
- signal(SIGINT, SIG_IGN); /* ignore interrupts */
- wait(&status); /* until the child is done */
- signal(SIGINT, onintr); /* restore interrupts */
- .P2
- 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
- .UL system :
- .P1
- #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);
- }
- .P2
- .PP
- As an aside on declarations,
- the function
- .UL 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
- .UL SIG_IGN
- and
- .UL 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.
- .P1
- #define SIG_DFL (int (*)())0
- #define SIG_IGN (int (*)())1
- .P2
-