libstddjb
skalibs
skalibs
Software
skarnet.org
The selfpipe functions are declared in the skalibs/selfpipe.h header and implemented in the libskarnet.a or libskarnet.so library.
Signal handlers suck.
They do. I don't care how experienced you are with C/Unix programming, they do. You can be Ken Thompson, if you use signal handlers as a regular part of your C programming model, you are going to screw up, and write buggy code.
Unix is tricky enough with interruptions. Even when you have a single thread, signals can make the execution flow very non-intuitive. They mess up the logic of linear and structured code, they introduce non-determinism; you always have to think "and what if I get interrupted here and the flow goes into a handler...". This is annoying.
Moreover, signal handler code is very limited in what it can
do. It can't use any non-reentrant function! If you call a non-reentrant
function, and by chance you were precisely in that non-reentrant function
code when you got interrupted by a signal... you lose. That means, no
malloc(). No bufferized IO. No globals. The list goes on and on.
If you're going to catch signals, you'll want to handle them outside
the signal handler. You actually want to spend the least possible
time inside a signal handler - just enough to notify your main
execution flow that there's a signal to take care of.
And, of course, signal handlers don't mix with event loops, which is a classic source of headaches for programmers and led to the birth of abominations such as pselect. So much for the "everything is a file" concept that Unix was built on.
A signal should be an event like any other. There should be a unified interface - receiving a signal should make some fd readable or something.
And that's exactly what the self-pipe trick, invented by DJB, does.
As long as you're in some kind of event loop, the self-pipe trick allows you to forget about signal handlers... forever. It works this way:
When you get a signal, a byte will be written to the self-pipe, and your execution flow will resume. When you next go through the event loop, p[0] will be readable; you'll then be able to read a byte from it, identify the signal, and handle it - in your unrestricted main environment (the "bottom half" of the handler).
The selfpipe library does it all for you - you don't even have to write the top half yourself. You can forget their existence and recover some peace of mind.
Note that in an asynchronous event loop, you need to protect your system calls against EINTR by using safe wrappers.
int fd = selfpipe_init() ;
selfpipe_init() sets up a selfpipe. You must use that
function first.
If fd is -1, then an error occurred. Else fd is a
non-blocking descriptor that can be used in your event loop. It will
be selected for readability when you've caught a signal.
int r = selfpipe_trap(SIGTERM) ;
selfpipe_trap() catches a signal and sends it to the selfpipe.
Uncaught signals won't trigger the selfpipe. r is 0 if
the operation succeeded, and -1 if it failed. If it succeeded, you
can forget about the trapped signal entirely.
In our example, if r is 0, then a SIGTERM will instantly
trigger readability on fd.
int r = selfpipe_untrap(SIGTERM) ;
Conversely, selfpipe_untrap() uncatches a signal; the selfpipe will not manage it anymore. r is 0 if the operation succeeded and -1 if it failed.
int r ; sigset_t set ; sigemptyset(&set) ; sigaddset(&set, SIGTERM) ; sigaddset(&set, SIGHUP) ; r = selfpipe_trapset(&set) ;
selfpipe_trap() and selfpipe_untrap() handle signals one by one. Alternatively (and often preferrably), you can use selfpipe_trapset() to directly handle signal sets. When you call selfpipe_trapset(), signals that are present in set will be caught by the selfpipe, and signals that are absent from set will be uncaught. r is 0 if the operation succeeded and -1 if it failed.
int c = selfpipe_read() ;
Call selfpipe_read() when your fd is readable.
That's where you write your real signal handler: in the
body of your event loop, in a "normal" context.
c is -1 if an error occurred - in which case chances are
it's a serious one and your system has become very unstable.
c is 0 if there are no more pending signals. If c
is positive, it is the number of the signal that was caught.
selfpipe_finish() ;
Call selfpipe_finish() when you're done using the selfpipe. Signal handlers will be restored to their previous value.
Some, as always.
Yes, the Linux team loves to gratuitously add new system calls to do things that could already be done before without much effort. This adds API complexity, which is not a sign of good engineering.
However, now that signalfd() exists, it is indeed marginally more efficient than a pipe, and it saves one fd: so the selfpipe library is implemented via signalfd() when this call is available.