Systems programming · lesson 06

Signals

A signal is an asynchronous notification sent to a process by the kernel or another process. When it arrives, the process stops whatever it's doing and runs a handler — or terminates, if no handler is installed.

in progress
10 min

What signals are

Signals predate the C standard library as a notification mechanism for exceptional events. A signal has a number and a name. When the kernel delivers signal N to a process, it interrupts the process at whatever instruction it's currently executing and jumps to the registered signal handler. When the handler returns, execution resumes where it was interrupted (usually — some syscalls return early with EINTR).

Common signals

signal default action typical cause
SIGINT (2) terminate Ctrl+C in terminal
SIGTERM (15) terminate kill command default
SIGKILL (9) terminate (uncatchable) kill -9, OOM killer
SIGSEGV (11) terminate + core dump invalid memory access
SIGCHLD (17) ignore child process exited
SIGALRM (14) terminate alarm() timer expired
SIGPIPE (13) terminate write to broken pipe
🚫
SIGKILL and SIGSTOP cannot be caught, blocked, or ignored. Every other signal can be handled or suppressed by the process. SIGKILL is the kernel's override — it terminates the process unconditionally, bypassing all handlers. This is why kill -9 always works.

Installing a handler with sigaction

signal() is the old API — its behavior is implementation-defined in several ways. Use sigaction() instead, which is fully specified by POSIX and gives more control.

c
#include <signal.h> #include <stdio.h> #include <unistd.h> static volatile sig_atomic_t running = 1; void handle_sigint(int sig) { (void)sig; running = 0; // safe: sig_atomic_t write is atomic } int main(void) { struct sigaction sa = {0}; sa.sa_handler = handle_sigint; sigemptyset(&sa.sa_mask); sigaction(SIGINT, &sa, NULL); while (running) { printf("running...\n"); sleep(1); } printf("caught SIGINT, exiting\n"); }

Async-signal safety

Signal handlers run asynchronously — they can interrupt your code at any point, including in the middle of a malloc call or a printf call. Most library functions are not async-signal-safe because they use internal locks or global state. Calling printf from a signal handler can deadlock if the signal arrived while printf held its internal lock.

The POSIX standard lists the async-signal-safe functions. The safe pattern for signal handlers: set a volatile sig_atomic_t flag, then check and act on that flag in your main loop. Keep handlers minimal.

⚠️
Do not call printf, malloc, or free from a signal handler. These functions are not async-signal-safe. The safe alternatives are write() (for output) and setting a sig_atomic_t flag (for state). Everything else should happen outside the handler.

Sending signals

c
#include <signal.h> // send SIGTERM to process with given pid kill(pid, SIGTERM); // send signal to yourself raise(SIGINT); // send SIGALRM after 5 seconds alarm(5);
one-line takeaway

Signals interrupt a process asynchronously — keep handlers minimal, use sig_atomic_t flags, and never call non-async-signal-safe functions inside them.