Course outline · 0% complete

0/28 lessons0%

Course overview →

Signals: how the OS interrupts a process

lesson 3-4 · ~10 min · 10/28

Signals: how the OS interrupts a process

Exit codes (lesson 3-3) are a dying process's last message to its parent. Signals go the other direction: they are how the OS, or another process, interrupts a running process right now. Every Ctrl-C you have ever pressed to stop a runaway script, every clean server shutdown during a deploy, and the kill command from lesson 3-2 all work through signals — this is the standard mechanism for stopping anything on macOS or Linux, and you will use it within your first week of running real software.

A signal is a small numbered message the kernel delivers to a process. The process does not check for it; the kernel interrupts the process mid-run. The process can register a handler — a function of its own that runs when a given signal arrives — or accept the default effect. The signals you will actually meet:

SignalNumberUsually sent byDefault effect
SIGINT2Ctrl-C in the terminalterminate
SIGTERM15kill <pid>, deploy toolsterminate, but a handler may clean up first
SIGKILL9kill -9 <pid>terminate immediately; cannot be caught or ignored

Now the two kill commands from lesson 3-2 read precisely: kill 4021 sends SIGTERM, a request the program may handle to finish cleanly. kill -9 4021 sends SIGKILL, and the kernel simply erases the process — no handler, no cleanup, no goodbye.

Code exercise · python

Run this. signal.signal registers a handler function for SIGTERM, and os.kill sends a signal (despite the name, it can send any signal, not just fatal ones). Here the process signals itself: the handler runs, and because SIGTERM was handled, the process survives.

Code exercise · python

Run this. The parent starts a child that would sleep for 60 seconds, then terminates it with proc.terminate(), which sends SIGTERM. When a signal kills a child, subprocess reports the exit code as MINUS the signal number, so the parent can tell "exited with an error" apart from "was killed".

The graceful-shutdown pattern

Why does SIGTERM allow a handler while SIGKILL does not? Because both needs are real. Production servers register a SIGTERM handler that stops accepting new work, finishes the requests already in flight, and saves state before exiting — killing a database mid-write without this is how data gets corrupted. But if a process is stuck or malicious, the system still needs a way to remove it that the process cannot veto: that is SIGKILL, enforced by the kernel alone.

Deploy systems bake this in. Docker and Kubernetes stop a container by sending SIGTERM, waiting a grace period (10-30 seconds), and only then sending SIGKILL. If a service you ship ignores SIGTERM, every deploy ends with it being shot mid-request. Handling SIGTERM is not optional polish; it is expected of real services.

Code exercise · python

Your turn: build the graceful-shutdown skeleton. Register a handler for SIGTERM that sets shutting_down to True (use the global keyword). The signal is then sent, and the final if-block should print the shutdown message. Match the expected output exactly.

Quiz

A deploy tool stops your service with SIGTERM, waits 15 seconds, then sends SIGKILL if it is still alive. Why this two-step dance instead of SIGKILL immediately?