Linux System Programming · advanced · ~12 min

Signals

Handle SIGINT/SIGTERM gracefully.

Overview

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.

Why it matters

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.

Core concepts

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.

Syntax notes

#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 */ }
}

Lesson

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.

Code examples

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 */ }

Common mistakes

  • Calling printf from a signal handler — not async-signal-safe.

Debugging tips

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

Memory safety

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.

Real-world uses

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.

Practice tasks

  1. Install a SIGINT handler that prints a message and exits cleanly. 2. Run an infinite loop and Ctrl-C it; observe the handler runs. 3. Use sigaction with SA_RESTART to make sleeping reads survive an EINTR.

Summary

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.

Practice with these exercises