Tapping a Process on the Shoulder
Signals are the simplest IPC mechanism and the one most developers encounter first without realizing it. When you press Ctrl+C in a terminal, the kernel sends a SIGINT signal to the foreground process. When you run kill 1234, you send SIGTERM to PID 1234. Signals are asynchronous notifications — the kernel interrupts the target process at an arbitrary point in its execution and invokes a signal handler function.
Signals are not designed for data transfer. Each signal carries only a signal number (an integer) and, with sigqueue(), optionally a single integer or pointer value. They are used for:
- Lifecycle control:
SIGTERM(graceful shutdown),SIGKILL(immediate kill, cannot be caught),SIGHUP(reload configuration — convention for daemons) - Error notification:
SIGSEGV(segmentation fault),SIGBUS(bus error from bad mmap access),SIGFPE(floating point exception) - Synchronization:
SIGUSR1/SIGUSR2(user-defined, used for custom notifications) - Child process events:
SIGCHLD(sent to parent when a child exits)
A common real-world example: sending SIGHUP to a running nginx process tells it to reload its configuration without dropping connections — the same signal-based convention used by many other long-running daemons.
Signal handling in practice:
A process can register a custom handler for most signals using sigaction(). When the signal arrives, the kernel suspends the main execution, runs the handler, then resumes. This creates a subtle problem: if the main code is in the middle of a malloc() call (which uses locks internally) and the signal handler also calls malloc(), you get a deadlock. Signal handlers must only call async-signal-safe functions — a restricted subset of the standard library.
The modern solution is the self-pipe trick or signalfd(): instead of handling signals in an async handler, you write a byte to a pipe (or read from a signalfd file descriptor) and handle the signal in your normal event loop. This makes signal handling safe and composable with select()/epoll().
Signals are not reliable IPC. If you send 10 SIGUSR1 signals rapidly to a process, it may only handle 1 — the kernel does not queue most signals (except real-time signals SIGRTMIN–SIGRTMAX). Never use signals to count events or transfer data.