Free — no signup required

Signals — The Lightweight Notification Channel

2 min read

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.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free