Linux System Programming · intermediate · ~12 min

signal() vs sigaction() — and why you should always use sigaction()

Pick the right API for installing a signal handler.

Overview

sigaction() supersedes signal(). It takes a struct with explicit flags so you opt into SA_RESTART, SA_SIGINFO, etc. signal() has portability footguns; never use it in new code.

Syntax notes

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    /* ... */
};

Lesson

The legacy way to catch a signal is signal(signo, handler). It works, but it has a footgun: on some old Unixes the handler is reset to the default after firing once. You'd then have to re-install it inside the handler — a race window where a second delivery escapes.

POSIX standardises a safer replacement: sigaction(). It takes a struct sigaction with explicit flags so you control behaviour precisely. Three flags you'll meet:

  • SA_RESTART — make blocking syscalls (read, accept, …) automatically restart instead of returning EINTR.
  • SA_NOCLDSTOP — for SIGCHLD, don't deliver on stop/continue, only on exit.
  • SA_SIGINFO — use the 3-arg sa_sigaction form to receive siginfo_t (signal sender pid, fault address, …).

Rule: never use signal() in new code. Always sigaction().

Code examples

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

static volatile sig_atomic_t got_int = 0;
static void on_int(int sig) { (void)sig; got_int = 1; }

int main(void) {
    struct sigaction sa = {0};
    sa.sa_handler = on_int;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;     /* don't break our read()s */
    if (sigaction(SIGINT, &sa, NULL) != 0) { perror("sigaction"); return 1; }

    while (!got_int) { pause(); }
    puts("caught SIGINT");
    return 0;
}

Common mistakes

  • Forgetting sigemptyset(&sa.sa_mask) — the mask field is not zero-initialised reliably across libc versions.
  • Doing real work inside the handler. The handler should set a flag; the main loop does the work.

Practice tasks

Replace a signal() call in some legacy code you find online with an equivalent sigaction() call. Then add SA_RESTART and observe that your blocking read no longer returns EINTR.

Summary

sigaction() is the POSIX-standard, portable, race-free way to install a signal handler. Always set sa_mask via sigemptyset() before passing the struct.

Practice with these exercises