10.4. Unreliable SignalsIn earlier versions of the UNIX System (such as Version 7), signals were unreliable. By this we mean that signals could get lost: a signal could occur and the process would never know about it. Also, a process had little control over a signal: a process could catch the signal or ignore it. Sometimes, we would like to tell the kernel to block a signal: don't ignore it, just remember if it occurs, and tell us later when we're ready.
One problem with these early versions is that the action for a signal was reset to its default each time the signal occurred. (In the previous example, when we ran the program in Figure 10.2, we avoided this detail by catching each signal only once.) The classic example from programming books that described these earlier systems concerns how to handle the interrupt signal. The code that was described usually looked like int sig_int(); /* my signal handling function */ ... signal(SIGINT, sig_int); /* establish handler */ ... sig_int() { signal(SIGINT, sig_int); /* reestablish handler for next time */ ... /* process the signal ... */ } (The reason the signal handler is declared as returning an integer is that these early systems didn't support the ISO C void data type.) The problem with this code fragment is that there is a window of timeafter the signal has occurred, but before the call to signal in the signal handlerwhen the interrupt signal could occur another time. This second signal would cause the default action to occur, which for this signal terminates the process. This is one of those conditions that works correctly most of the time, causing us to think that it is correct, when it isn't. Another problem with these earlier systems is that the process was unable to turn a signal off when it didn't want the signal to occur. All the process could do was ignore the signal. There are times when we would like to tell the system "prevent the following signals from occurring, but remember if they do occur." The classic example that demonstrates this flaw is shown by a piece of code that catches a signal and sets a flag for the process that indicates that the signal occurred: int sig_int_flag; /* set nonzero when signal occurs */ main() { int sig_int(); /* my signal handling function */ ... signal(SIGINT, sig_int); /* establish handler */ ... while (sig_int_flag == 0) pause(); /* go to sleep, waiting for signal */ ... } sig_int() { signal(SIGINT, sig_int); /* reestablish handler for next time */ sig_int_flag = 1; /* set flag for main loop to examine */ } Here, the process is calling the pause function to put it to sleep until a signal is caught. When the signal is caught, the signal handler just sets the flag sig_int_flag to a nonzero value. The process is automatically awakened by the kernel after the signal handler returns, notices that the flag is nonzero, and does whatever it needs to do. But there is a window of time when things can go wrong. If the signal occurs after the test of sig_int_flag, but before the call to pause, the process could go to sleep forever (assuming that the signal is never generated again). This occurrence of the signal is lost. This is another example of some code that isn't right, yet it works most of the time. Debugging this type of problem can be difficult. |