Linux System Programming · advanced · ~12 min
Handle SIGINT/SIGTERM gracefully.
A signal is a kernel-delivered, asynchronous notification to a process: 'someone pressed Ctrl-C' (SIGINT), 'a child exited' (SIGCHLD), 'you tried to write to a closed pipe' (SIGPIPE), 'someone wants to kill you' (SIGTERM, SIGKILL). You install handlers with sigaction or signal.
Signals are how the kernel and other processes get your attention urgently. Every Ctrl-C is a signal; every kill command is a signal; every segfault you've ever seen was a signal (SIGSEGV) reaching its default handler.
Signal-safe functions. Only a small list (in signal-safety(7)) is safe to call from a handler — write, _exit, sig_atomic_t reads. printf, malloc, anything stdio: forbidden. Disposition. Each signal has a default (ignore, terminate, dump core); install your own handler to override. Async-signal-safe communication. Set a volatile sig_atomic_t flag = 1; in the handler and let the main loop notice it.
#include <signal.h>
volatile sig_atomic_t stop = 0;
static void on_sigint(int sig) { (void)sig; stop = 1; }
int main(void) {
struct sigaction sa = {.sa_handler = on_sigint};
sigaction(SIGINT, &sa, NULL);
while (!stop) { /* work */ }
}
A signal is an asynchronous notification (Ctrl-C → SIGINT; kill <pid> → SIGTERM). Install a handler with signal() (simple) or sigaction() (modern, recommended).
Inside a handler you may only call async-signal-safe functions and only touch volatile sig_atomic_t variables. Set a flag in the handler; do the actual work in the main loop.
static volatile sig_atomic_t g_stop = 0;
static void on_term(int s) { (void)s; g_stop = 1; }
/* ... */
signal(SIGTERM, on_term);
while (!g_stop) { /* work */ }
printf from a signal handler — not async-signal-safe.kill -l lists every signal. gdb's handle SIGPIPE nostop noprint lets you keep debugging through expected signals. If your handler does nothing visible, double-check you used sigaction (not the old signal(), which has historically had unreliable semantics).
Calling non-async-signal-safe functions from a handler can corrupt the heap or stdio buffers. The safe pattern: just set a flag in the handler and react in the main loop. SIGKILL and SIGSTOP cannot be caught — that's by design.
Graceful shutdown of servers (SIGTERM), reloading configuration (SIGHUP), child cleanup (SIGCHLD), broken-pipe handling (SIGPIPE for writing to a closed socket), Ctrl-C to abort.
sigaction with SA_RESTART to make sleeping reads survive an EINTR.Signals are async notifications from the kernel. Install handlers with sigaction, do only async-signal-safe work in them (set a flag, write a byte), and let the main loop react. Master these patterns and your programs handle Ctrl-C, child exits, and shutdowns gracefully.