Back in the day, I was reading a book about UNIX® programming and have learned how to write a signal handler. It was a long time ago and I don’t remember the book, but to this day the way described in that book is something that shows up in Google’s top results when you search for “How to write a signal handler”. Here it is — a simple, elegant solution to the world’s toughest problem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Now imagine for a second that instead of a simple invocation of
sleep() in the body of the
main() function, just after the
printf(), there was some very important code running a life support system and someone’s life was really dependent on that. What would have happened? Someone would not have been alive for long — that’s for sure. And the terrible signal handling is to blame and here is why…
You Have Been Warned
Let’s take a quick look at Linux® manual page for a
signal() call. You can find by typing
man 2 signal in the command line, or by checking it out online, here — http://man7.org/linux/man-pages/man2/signal.2.html. The first sentence in the manual page introduces you to the
signal() call. The second sentence, now in bold, warns you, and I quote:
Avoid its use: use sigaction(2) instead.
Your Computer Might Take a Trip to a Grocery Store
Continuing reading the manual page, the very first note under the NOTES section is this:
The effects of signal() in a multithreaded process are unspecified.
This is called Unspecified Behavior. What it means is that standard does not say anything as for how the function should behave in a multi-threaded environment. Therefore, it may exhibit a different behavior on different systems including different versions of the same system, at discretion of those who implement it. Your mileage may vary.
Even if your code does not explicitly use multiple threads, you are still in danger — it might be used as part of a bigger program in multi-threaded environment. But even if that’s not that case — hey, tomorrow you might want to do that. Do you really want to screw yourself upfront by using this bad
signal() function? Think twice.
Asynchronicity & Reentrancy
Signals are asynchronous by their nature. Another signal may be delivered to the process while the previous signal is still being “processed”. Therefore, signal handler must not introduce unwanted side effects, must be fully reentrant and cannot use any non-reentrant code — neither explicitly nor implicitly. Now take a quick look at the famous example mentioned at the beginning — there is a nice
printf() right in the signal handler code. This is a life threatening piece of code because
printf() is non-reentrant. In other words, it is possible that
printf() function will get interrupted before it finishes and get called again as part of another signal handler. If that case the program will simply deadlock.
The section 7 of the manual page about signal (which you can see by typing
man 7 signal in your terminal or read online here — http://man7.org/linux/man-pages/man7/signal.7.html) states the following:
Async-signal-safe functions A signal handler function must be very careful, since processing elsewhere may be interrupted at some arbitrary point in the execution of the program. POSIX has the concept of “safe function”. If a signal interrupts the execution of an unsafe function, and handler calls an unsafe function, then the behavior of the program is undefined.
And then it lists all async-signal-safe functions. So if you don’t know what functions are safe to use inside a signal handler and what functions are not — refer to that list and make sure you are not doing anything dangerous.
Better Safe Than Sorry
What is the best way not to become a drag addict? Not to take drugs in the first place. What is the best way to write a safe signal handler? Not to write it all. OK, I am just kidding. Seriously though — if you really want your signal handler to be safe and portable, consider not doing anything inside it except modifying a global volatile variable of
sig_atomic_t type. Taking all of the above into account, below is an example of how a safe signal handler may look like in real life (note the absence of
signal() function in the code):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
Modern Signal Handling
It is the 2013th year in the Common Era at the moment of this writing and you might think that people should have came up with something better in terms of signal handling at this time. The truth is that they did. It is just not that well known yet due to a huge momentum of outdated information still overflowing the Internet.
FreeBSD, NetBSD, OS X, iOS…
In the above family of operating systems, the modern way of handling signals is to use Kqueue — a scalable event notification mechanism. Here is an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
signalfd() for handling signals. The beauty of it is that it may be used by itself, or may be combined with event notification mechanisms such as
epoll() and friends. The manual page for
signalfd() comes with a good example, so I don’t even have to write one. Here it is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
Make the world a better place!
Please make the world a better place by writing correct and safe signal handling code.
Thank you very much for reading!