'Kill the process' is really 'send it a message asking it to stop' — and the fact that a program can choose how to respond to that message is exactly why some processes shut down cleanly and others need to be forced.
3 min read
A signal is the kernel's mechanism for asynchronously notifying a
process that something happened — press Ctrl+C in a terminal, and the
kernel delivers a specific signal to the running program; run kill <pid>,
and the kernel delivers a different one. Crucially, a signal is a
notification, not a forced action performed on the process from outside —
what happens next depends on how (or whether) the process has chosen to
handle that particular signal.
$ kill -l | head -3
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERMkill's default) — a polite request to
terminate. Like SIGINT, a well-behaved program can catch it and shut down
gracefully — finish the current request, close database connections,
flush any buffered writes — before exiting.kill -9 is a last resort, not a
default: any in-progress write or cleanup a program was mid-way through
simply never happens.fg and bg send after you press Ctrl+Z.
A paused process isn't dead: it keeps its memory and open files, it just
isn't scheduled to run until it's continued.Reaching for kill -9 when a process seems stuck skips past the graceful
shutdown path entirely — which is sometimes genuinely necessary (a truly
hung process that isn't responding to SIGTERM at all), but always at the
cost of whatever cleanup that process would otherwise have done. A database
process killed with -9 mid-write, for instance, may leave data in an
inconsistent state that a graceful SIGTERM shutdown would have avoided —
this is exactly why production tooling (container orchestrators, systemd)
sends SIGTERM first and only escalates to SIGKILL after giving the process
a real window of time to exit cleanly on its own.
import signal
import sys
def handle_sigterm(signum, frame):
print("Received SIGTERM, cleaning up...")
# close connections, flush buffers, etc.
sys.exit(0)
signal.signal(signal.SIGTERM, handle_sigterm)Registering a handler like this is what lets a real service — a web server, a background worker — finish its current request and shut down cleanly instead of being cut off mid-task whenever it's asked to stop.
Whether a deployment or a container restart is "clean" almost always comes down to whether the running process actually handles SIGTERM properly before the orchestrator's patience runs out and escalates to SIGKILL. A service that ignores SIGTERM (or takes too long to react to it) is a concrete, common cause of dropped requests and corrupted in-progress work during otherwise routine deploys.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What is a signal, fundamentally?
2. Why can't SIGKILL be caught or handled by a process?
3. Why do production tools like systemd or container orchestrators send SIGTERM before escalating to SIGKILL?
Operating Systems Fundamentals