Vlad Lazarenko

... making all this up as I go along

How Not to Write a Signal Handler

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:

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
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

static void my_sig_handler(int signum) {
    printf("Received interrupt signal!\n");
}

int main() {
    signal(SIGINT, my_sig_handler);
    for (;;) {
        printf("Doing useful stuff...\n");
        sleep(1); /* Sleep is not only useful, it is essential! */
    }
    return EXIT_SUCCESS;
}

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.

Async-Signal-Safe Functions

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):

Simple and safe signal handling
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
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static volatile sig_atomic_t got_signal = 0;

static void my_sig_handler(int signo)
{
    got_signal = 1;
}

int main()
{
    struct sigaction sa;

    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = &my_sig_handler;
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return EXIT_FAILURE;
    }

    for (;;) {
        if (got_signal) {
            got_signal = 0;
            printf("Received interrupt signal!\n");
        }
        printf("Doing useful stuff...\n");
        sleep(1); /* Sleep is not only useful, it is essential! */
    }
    return EXIT_SUCCESS;
}

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:

Kqueue Signal Handling
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
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
    int kq;
    int i;
    struct kevent ke;
    struct timespec timeout;
    struct sigaction sa;

    /* Block the signal we want to process with kevent first.
       This is needed because kevent has lower precedence. */
    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = SIG_IGN;
    sigaction(SIGINT, &sa, NULL);

    /* Create kqueue... */
    kq = kqueue();
    if (kq == -1) {
        return EXIT_FAILURE;
    }
    /* Add a signal event */
    EV_SET(&ke, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
    i = kevent(kq, &ke, 1, NULL, 0, NULL);
    if (i == -1)
        return EXIT_FAILURE;
    /* Set a timeout (we no longer need to use a blocking sleep(1),
       and this is great! */
    timeout.tv_sec = 1; /* To sleep for one second */
    timeout.tv_nsec = 0;

    /* Dispatch events */
    for (;;) {
        i = kevent(kq, NULL, 0, &ke, 1, &timeout);
        if (i > 0) {
            /* Got event(s) to dispatch. Note that we can do whatever the hell
               we want here because we are __not__ in the signal handler. */
            if (ke.ident == SIGINT) {
                printf("Received interrupt signal!\n");
            }
        } else if (i == 0) {
            /* Timeout elapsed */
            printf("Doing useful stuff...\n");
        } else if (i == -1) {
            if (errno == EINTR)
                continue; /* System call interrupted - just restart */
            return EXIT_FAILURE; /* Something went wrong. */
        }
    }
    return EXIT_SUCCESS;
}

Linux

Linux provides 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:

Signalfd & Epoll Signal Handling
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
#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define handle_error(msg)                               \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[])
{
    sigset_t mask;
    int sfd;
    struct signalfd_siginfo fdsi;
    ssize_t s;

    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);

    /* Block signals so that they aren't handled
       according to their default dispositions */

    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
        handle_error("sigprocmask");

    sfd = signalfd(-1, &mask, 0);
    if (sfd == -1)
        handle_error("signalfd");

    for (;;) {
        s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
        if (s != sizeof(struct signalfd_siginfo))
            handle_error("read");

        if (fdsi.ssi_signo == SIGINT) {
            printf("Got SIGINT\n");
        } else if (fdsi.ssi_signo == SIGQUIT) {
            printf("Got SIGQUIT\n");
            exit(EXIT_SUCCESS);
        } else {
            printf("Read unexpected signal\n");
        }
    }
}

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!