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.