Linux System Programming · intermediate · ~12 min
Pick the right API for installing a signal handler.
sigaction() is the modern replacement for the older signal() function. Both install a signal handler (a function that runs when your program receives a signal like Ctrl+C).
The difference is control:
sigaction() takes a struct with explicit flags. You opt in to behaviors such as SA_RESTART and SA_SIGINFO.signal() has portability traps ("footguns") that differ between systems.Rule: never use signal() in new code. Use sigaction().
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;
/* ... */
};
signum — the signal to handle (for example, SIGINT).act — the new handler settings to install.oldact — if not NULL, receives the previous settings (useful for restoring them later).The old way to catch a signal is signal(signo, handler).
It works, but it has a serious trap. On some older Unix systems, the handler is reset to the default action after it fires once.
To keep catching the signal, you would have to re-install the handler from inside the handler itself. That creates a race window: if a second signal arrives before you re-install, it escapes your handler and may kill the program.
POSIX (the portable Unix standard) defines a safer replacement: sigaction().
It takes a struct sigaction filled with explicit flags, so you control the behavior precisely.
SA_RESTART — automatically restart blocking system calls (read, accept, and similar) instead of returning the EINTR ("interrupted") error.SA_NOCLDSTOP — for SIGCHLD, deliver the signal only when a child exits, not when it stops or continues.SA_SIGINFO — use the 3-argument sa_sigaction handler form, which receives a siginfo_t with extra detail (the sending process ID, the faulting address, and more).Never use signal() in new code. Always use sigaction().
#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;
}
sigemptyset(&sa.sa_mask). The mask field is not reliably zero-initialized across libc versions. Always clear it explicitly before use.signal() call. Replace it with an equivalent sigaction() call.SA_RESTART flag.read no longer returns the EINTR error when a signal arrives.sigaction() is the POSIX-standard, portable, race-free way to install a signal handler.signal() can reset the handler to default after one use, opening a race window — avoid it in new code.SA_RESTART, SA_NOCLDSTOP, and SA_SIGINFO give you precise control over handler behavior.sigemptyset(&sa.sa_mask) before passing the struct.