Suppose you have a program with a main loop like the following pseudo-code:
void gotsignal() {
caught_signal=1;
}
void main() {
signal(SIGwhatever, gotsignal);
while(1) {
/* do stuff */
if(caught_signal) {
/* do stuff */
} else {
/* do something else */
}
select();
/* do more stuff */
}
At first glance, everything is okay. We know that a signal caught
in select() will cause select()
to return immediately.
caught_signal is evaluated just before select()
is called. It would appear that signals occuring between
select() are also handled properly.
But there exists an interval, after caught_signal
is evaluated but
before select() is called, that a signal can come
in. caught_signal is then set, but select() doesn't
return because select() wasn't called when the signal came in.
The now slightly less inexperienced programmer may "solve" this problem like so:
...
while(1) {
/* do stuff */
if(caught_signal) {
/* do stuff */
} else {
/* do something else */
}
sigprocmask(SIG_UNBLOCK, ...);
select();
sigprocmask(SIG_BLOCK, ...);
/* do more stuff */
}
By keeping the signal blocked until the last possible moment, then
reblocking it at the first opportunity, the programmer hopes to hold
signals at bay until the process is in select(). Sadly, there
remains an interval between sigprocmask(SIG_UNBLOCK, ...) and
select() where a signal can occur.
We are on the right track. It is clear that our strategy for
handling signals should center on making sure that select()
cannot be called after a signal that occured after
the test to determine if a signal arrived!
pselect()
pselect() is much like select(), except it takes
a list of signals as an additional argument. Functionally, it is like
sigprocmask(SIG_UNBLOCK); select(); sigprocmask(SIG_BLOCK);,
except signals have no opportunity to occure between
sigprocmask(SIG_UNBLOCK) and select(). This is a
guarantee made by the kernel.
Sadly, pselect() is not available on many
systems. Until recently, most Linux kernel/GNU libc stacks did not
support pselect(), or used an imperfect emulation.
The self-pipe trick involves an anonymous pipe, created with
pipe(), a signal handler, and select(). The signal
handler writes a byte to the writing end of the pipe, and
select() waits for the reading end of the pipe to become readable.
The self-pipe trick is reliable, but uses additional system resources (ofiles, memory, and so forth). Bernstein, D. J. "The self-pipe trick." van Bergen, Emile. "Avoiding races with Unix signals and select()."
sigsetjmp/siglongjmp
Our third strategy is using sigsetjmp() and
siglongjmp() to jump to a specific spot in our main loop:
void gotsignal() {
caught_signal=1;
if(oktojmp) siglongjmp();
}
void main() {
signal(SIGwhatever, gotsignal);
while(1) {
/* do stuff */
sigsetjmp(); oktojmp=1;
if(caught_signal) {
/* do stuff */
} else {
/* do something else */
}
select();
/* do more stuff */
}
Regardless of where the process is, even if it is in select(),
the
siglongjmp() from the signal handler will cause program flow to be
redirected to sigsetjmp(). This is the strategy I use in my code.