Handling signals with signalfd

July 29, 2013

Handling UNIX/POSIX signals is hard. If you don’t know that they’re hard, then you aren’t paying attention. In Linux there’s a system call signalfd() that makes handling them much less error-prone and fits right in to a poll()-based event-loop. This article demonstrates why signals are hard, and how to use signalfd(2) with poll(2).[1]

The Problem

Why are signals hard? Suppose that a thread has mutex `m‘ locked. When your process receives the signal, the thread is interrupted to handle the signal. In the signal handler, you try to lock `m‘. Your program just deadlocked. The signal handler waits until `m‘ unlocked. But `m‘ is never unlocked because the thread holding the lock was suspended to handle the signal. In contrast, a multi-threaded program wouldn’t usually deadlock because both threads are still active (running).

Thus, signals programming is harder than multi-threaded programming.

If you read the man page for signal(7), it contains a list of functions that POSIX requires be “async-safe.” In other words, reentrant functions that don’t lock mutexes or semaphores. Functions that are safe to call from from a signal handler. If a function is not on the list, it could lead to deadlocks and undefined behavior. Note that functions like fprintf(), snprintf(), syslog(), etc. are not on the list.

Also, since you can’t lock a mutex, manipulating any global variables in your program is hard. However, it’s common for programmers to ignore this and illicitly do dangerous things in signal handlers (lock mutexes, use an un-safe function, write to variables without regard for atomicity). Usually they get away with it because signals are usually for exceptional (rare) situations — so they usually don’t hit the error space.

But after a developer learns how dangerous it is to do anything useful in a signal handler,[2] it’s not uncommon to see something like this:

volatile sig_atomic_t shutdown_program;

void signal_handler(int sig)
        if (sig == SIGTERM)
                shutdown_program = 1;

…and then let your program’s main loop poll the variable regularly to catch the shutdown signal. (See glibc’s recommendation)

Using signalfd(2) as a solution

The man page for signal(7) lists some synchronous solutions to this problem:

  1. sigwaitinfo(2), sigtimedwait(2), sigwait(3) – These suspend execution until a signal is received.  This is useful (for instance) if you have real work being done on threads, and you use your main() function exclusively to handle signals.
  2. signalfd(2) – Provides a file descriptor that will provide information about the signals when they occur.

The reason why I think signalfd(2) is cool is that you can make the file descriptor generate events for your main event loop. For example, if you use poll(2), ppoll(2), select(2), pselect(2), epoll(7), or the GLib GMainLoop… it’s easy to add file-descriptor-bound events to those loops.

Example using poll(2)

The code that follows is a simple example of how to use signalfd(2) with poll(2).  (Note: the man page for signalfd usually includes an example where you use read(2) to block until you get a signal.)  The procedure is as follows:

  1. Block the signals that you’re interested in. (Yes, block them.)
  2. Create the signalfd for the signals that you’re interested in.  (If you don’t block them, then signalfd won’t receive them.)
  3. When the file descriptor is ready to be read, read a `signalfd_siginfo’ structure from the file descriptor to get info about the signal.

Here’s an example:

/* Simple program using signalfd to watch for SIGINT
 * Compile: gcc -o main main.c
 * Execute: ./main
 * Warning: You must use kill(1) to terminate the program.
 *     Ctrl+C won't terminate it.
#include <assert.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <sys/signalfd.h>
#include <unistd.h>

#define NFDS 1

int main(int argc, char* argv[])
        int err;
        sigset_t sigset;
        int fd;

        /* Create a sigset of all the signals that we're interested in */
        err = sigemptyset(&sigset);
        assert(err == 0);
        err = sigaddset(&sigset, SIGINT);
        assert(err == 0);
        err = sigaddset(&sigset, SIGHUP);
        assert(err == 0);

        /* We must block the signals in order for signalfd to receive them */
        err = sigprocmask(SIG_BLOCK, &sigset, NULL);
        assert(err == 0);

        /* Create the signalfd */
        fd = signalfd(-1, &sigset, 0);
        assert(fd != -1);

        /* This is the main loop */
        struct pollfd pfd[NFDS];
        int ret;
        ssize_t bytes;

        pfd[0].fd = fd;
        pfd[0].events = POLLIN | POLLERR | POLLHUP;

        for (;;) {
                ret = poll(pfd, NFDS, -1);
                printf("I'm awake!\n");

                /* Bail on errors (for simplicity) */
                assert(ret > 0);
                assert(pfd[0].revents & POLLIN);

                /* We have a valid signal, read the info from the fd */
                struct signalfd_siginfo info;
                bytes = read(fd, &info, sizeof(info));
                assert(bytes == sizeof(info));

                unsigned sig = info.ssi_signo;
                unsigned user = info.ssi_uid;

                if (sig == SIGINT)
                        printf("Got SIGINT from user %u\n", user);
                if (sig == SIGHUP)
                        printf("Got SIGHUP from user %u\n", user);

        return 0;

Try it out by running it and hitting `Ctrl+C’ or sending it a signal using kill(1). Because all our handling is happening in the main thread’s context, we don’t have hidden gotchas with respect to handling the signal.

[1] – Whenever you see `foo(2)’, that’s a reference to a man page that you can view by typing `man 2 foo’. The number is like saying “I want to read about `foo’ from chapter 2.” This is important because some man pages appear in more than one chapter (e.g. signal(2), signal(7)).

[2] – More precisely: “…in a signal handler that returns.”


4 Responses to “Handling signals with signalfd”

  1. I think the line
    assert(pfd[0].events & POLLIN);

    should read
    assert(pfd[0].revents & POLLIN);

  2. RayM said

    Your explanation and example is very clear. In my case, I call “kill(myPID, SIGUSR1)” from a child thread and “poll” in my main thread does indeed “catch” it. If instead my child thread calls “raise(SIGUSR1)”, it returns 0 but poll doesn’t get it . Any idea why not? According to the man page, “raise” should send the signal to the calling process, which in my case is my main thread.

    The performance of this approach is reasonable. On a Beaglebone Black, it typically takes 100 usec (up to 500 usec depending on ???) from the time the child thread calls “kill” until “poll” in the main thread catches the signal.

    Could you compare your signalfd approach to sigtimedwait? It seems sigtimedwait would accomplish the same thing but without having to deal with a “file” which would make it a little faster.

    • gbeddingfield said

      Sorry for the delayed reply. I assume that “myPID” is the PID of the parent process. However, when you call raise(3) in the child process, it’s equivalent to kill(getpid(), SIGUSR1). getpid() will return the PID of the child process, which I presume is different from myPID>

      As for sigtimedwait(2), if all you care about is signals then that’s the way to go. However, I find myself in situations where I want a single-threaded program that can handle inputs from lots of sources (e.g. stdin, a socket, an audio driver, signals, etc.). You can use poll() to multiplex these and stay single-threaded.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: