Linux & Bash · Part I · Module 7 · Processes, Jobs and Signals
Signals, and What Ctrl-C Actually Sends
Pressing Ctrl-C, suspending a job, and ending a process are all the same operation underneath: delivering a one-word message to something that is running. What the program does about that message is almost entirely up to the program — which explains a great deal.
Intermediate18 min readLinuxPart IFundamentalsProcessesSignals
Part I · Module 7 · Chapter 7.6 · Signals, and What Ctrl-C Actually Sends
Chapter 7.5 used four commands — kill -STOP, kill -CONT, Ctrl-Z, Ctrl-C — without ever saying what they had in common. They are all the same mechanism, and it is worth meeting properly, because the mechanism has a property that surprises people at exactly the wrong moment.
1. A Signal Is a One-Word Message
A signal is a short, fixed message the kernel delivers to a running process. There is a small set of them, each with a name, and a signal carries no data beyond its own identity. SIGINT means SIGINT and nothing more.
That is the entire data model. What makes signals interesting is what happens when one arrives.
For each signal, a process can:
- Let the default happen. Every signal has a default action — usually "terminate", sometimes "stop", sometimes "ignore".
- Handle it. The program registers a function; the kernel runs that function when the signal arrives, and the program carries on afterwards.
- Ignore it. The program says it does not care; the kernel drops the signal silently.
- Block it. The program defers delivery until it is ready — during a critical section, say.
2. Ctrl-C Is Not Magic
When you press Ctrl-C, nothing about your keyboard reaches the program. The terminal driver recognises the keystroke and sends SIGINT to the foreground process group.
The same goes for the rest of the control characters:
| Keystroke | Sends | Default action |
|---|---|---|
| Ctrl-C | SIGINT | Terminate |
| Ctrl-Z | SIGTSTP | Stop (suspend) |
| Ctrl-\ | SIGQUIT | Terminate, and write a core dump |
So "Ctrl-C kills it" is shorthand for "Ctrl-C sends SIGINT, and most programs have not changed SIGINT's default, which is to terminate".
Which means the inverse is equally possible, and it is why this chapter exists: a program that handles SIGINT does not die when you press Ctrl-C, and there is nothing wrong with your keyboard.
3. The Ones Worth Knowing
There are around thirty signals. You need six.
| Name | Sent by | Default | Can a program refuse it? |
|---|---|---|---|
SIGINT | Ctrl-C | Terminate | Yes |
SIGTERM | kill with no option | Terminate | Yes |
SIGHUP | The terminal closing | Terminate | Yes |
SIGQUIT | Ctrl-\ | Terminate + core dump | Yes |
SIGKILL | kill -9 | Terminate | No — never |
SIGSTOP | kill -STOP | Suspend | No — never |
Two more appear constantly in jobs and ps output:
| Name | Means |
|---|---|
SIGTSTP | What Ctrl-Z sends. Suspend — but refusable, unlike SIGSTOP |
SIGCONT | Resume a stopped process. What fg and bg send |
And the rest of the list exists mostly to be read in a crash report: SIGSEGV (the program touched memory it does not own), SIGPIPE (it wrote to a pipe whose reader has gone — Module 12), SIGCHLD (one of its children ended — which is how a shell notices a background job finishing), SIGUSR1 and SIGUSR2 (reserved for programs to define themselves).
Your system's full list:
$ kill -lIt prints thirty-odd names in a grid. Read it once, for the shape of the thing, and then go back to the six.
4. The Two That Cannot Be Refused
SIGKILL and SIGSTOP are different in kind from all the others. A program cannot handle, ignore or block them — the ability is simply not offered. The kernel acts on them without ever involving the process.
SIGKILL— the process is removed. It does not run another instruction. No handler fires, no file is closed tidily, no temporary file is cleaned up, no message is logged.SIGSTOP— the process is suspended. Also unconditional, and also uninterested in the program's opinion.
They are the exceptions that make the rule usable: when every polite option has failed, something is guaranteed to work.
5. Watching a Program Refuse a Signal
This is worth doing rather than reading about, because "a program can ignore SIGTERM" sounds theoretical until you watch one do it.
Here is a script that handles both SIGINT and SIGTERM by printing a line and carrying on. trap is Bash's way of registering a handler — Chapter 20.5 covers it properly; all that matters here is that it is four lines:
#!/bin/bash
trap 'echo "[caught INT - still here]"' INT
trap 'echo "[caught TERM - still here]"' TERM
echo "running as PID $$"
while true; do sleep 1; doneRun it in the background and attack it:
$ ./trapper.sh &
[1] 12109
running as PID 12109
$ kill -INT 12109
[caught INT - still here]
$ kill -TERM 12109
[caught TERM - still here]
$ ps -o pid,stat,comm -p 12109
PID STAT COMM
12109 S bashBoth signals were delivered. Both were handled. The process is still running — ps says so.
Now the one it cannot refuse:
$ kill -KILL 12109
$ jobs
[1]+ Killed: 9 ./trapper.sh
$ ps -o pid,stat,comm -p 12109
$Gone, instantly, with no message from the program — because no code of the program's ran.
Why a program would do this
Ignoring SIGTERM sounds like bad behaviour. Usually it is the opposite — a well-written program catches it precisely so it can shut down properly:
- A simulator writing a waveform database flushes and closes it, so the file is readable rather than truncated.
- A regression manager records which tests had finished before stopping.
- A tool holding a licence returns it, instead of leaving it checked out until a timeout expires.
- Anything with a temporary directory removes it.
All of that takes a moment, which is why the correct answer to "it did not die instantly" is usually wait a few seconds, not escalate.
6. Names, Numbers and Portability
Every signal has a number as well as a name, and you can send either:
kill -TERM 4821 # by name
kill -15 4821 # by number
kill -SIGTERM 4821 # by full nameAll three are the same command. But there is a trap in the numbers.
You can convert between them with kill -l:
$ kill -l 9
KILL
$ kill -l TERM
157. Signal Zero
Chapter 7.5 used kill -0 as an existence check without explaining the name. There is no signal 0; it is a convention.
kill -0 PID asks the kernel to perform everything it would do before delivering a signal — does this process exist, and am I allowed to signal it? — and then stop without delivering anything:
$ sleep 60 &
[1] 11407
$ kill -0 11407 && echo "alive"
alive
$ kill 11407
$ kill -0 11407
bash: kill: (11407) - No such processIt is the standard way for a script to poll something it started. Note the two ways it can fail: the process does not exist, or it exists and belongs to somebody else. Both report an error, so a success means "exists and is mine to signal".
8. How a Signal Shows Up in the Exit Status
When a signal does end a process, that fact is recorded in its exit status, by a fixed rule.
$ sleep 60 & Q=$!; kill -TERM $Q; wait $Q; echo "TERM gave status $?"
TERM gave status 143
$ sleep 60 & R=$!; kill -KILL $R; wait $R; echo "KILL gave status $?"
KILL gave status 137A process ended by signal N reports an exit status of 128 + N.
SIGTERMis 15, so 143.SIGKILLis 9, so 137.SIGINTis 2, so 130 — the status you get from Ctrl-C.
Those three numbers appear constantly in regression summaries and continuous-integration logs, and they mean something quite specific: the program did not choose its exit status — something ended it. A tool reporting 137 was almost certainly killed by the out-of-memory killer from Chapter 7.3, because that is what the OOM killer sends.
(Exit status in general is Module 11's subject. The 128 + N rule is the part that belongs here.)
9. Why One Ctrl-C Is Sometimes Not Enough
With all of that in place, a familiar annoyance becomes a diagnosis. You press Ctrl-C, nothing happens, you press it again, and on the third try it stops. Three possibilities, and they are distinguishable:
The program is handling SIGINT and doing something first. Flushing a file, closing a connection, releasing a licence. Each press delivers another SIGINT; the program ends when its cleanup finishes, not when you stop pressing. Pressing harder does nothing. Waiting does.
The program ignores SIGINT deliberately. Some tools do this to protect a long operation from a stray keystroke. No number of presses will help; you need kill.
The process is in uninterruptible sleep. State D from Chapter 7.2 — blocked in the kernel waiting on a disk or a network filesystem. The signal is recorded and delivered when the I/O completes, which may be a long time. Not even SIGKILL works here, and that is the one circumstance where it does not.
Check which case you are in before deciding what to do:
$ ps -o pid,stat,etime,cmd -p 12109
PID STAT ELAPSED CMD
12109 S 00:04:31 ./long_job.shS means it is sleeping normally and signals will reach it. D means they will not, yet.
Practice
1 — Read your system's list. Run kill -l. Count the entries, then find TERM, KILL, INT and HUP.
2 — Convert in both directions. kill -l 9, kill -l 15, kill -l TERM, kill -l INT.
3 — Build the trapper. Write the four-line script from §5, run it in the background, and send it -INT and -TERM. Confirm with ps that it is still running. Then -KILL it.
4 — Watch a signal arrive but not act. Add a sleep to the handler so the cleanup is visible:
trap 'echo "[cleaning up...]"; sleep 3; echo "[done]"; exit 0' TERMSend it kill -TERM and watch the three-second pause. That pause is what "give it a moment before escalating" means in practice.
5 — Prove the exit-status rule. Run the two wait lines from §8 and confirm you get 143 and 137. Then run sleep 60 in the foreground, press Ctrl-C, and run echo $? — you should see 130.
6 — Stop and continue. Start sleep 300 &, then kill -STOP it, confirm with ps -o stat that it shows T, then kill -CONT it and confirm it is back to S.
7 — Try to refuse the unrefusable. Add trap 'echo "nope"' KILL to your trapper script. Bash will not complain. Send -KILL anyway and watch the handler never run.
Check Your Understanding
1. You press Ctrl-C on a running simulation and it keeps going. Name two different explanations and say how you would tell them apart.
2. Why can a program handle SIGTERM but not SIGKILL?
3. A regression log says a test exited with status 137. What happened to it?
4. Your script does kill -17 "$PID" and behaves differently on two machines. Why, and what should it say instead?
5. What does kill -0 12345 do, and what do its two possible failures mean?
6. A process will not die even to kill -9. What is the first thing to check?
Chapter 30.4 measures what each of these costs in evidence: a hard kill destroyed exactly 88 log lines and left the waveform intact.
10. Remember
- A signal is a one-word message delivered to a process. It carries no data beyond its own identity.
- A signal is a request, not an order. A program may take the default, handle it, ignore it or block it.
- Ctrl-C sends
SIGINT; Ctrl-Z sendsSIGTSTP; Ctrl-\ sendsSIGQUIT— all to the terminal's foreground process group, never to a background job. - Six signals matter:
INT,TERM,HUP,QUIT,KILL,STOP. PlusTSTPandCONT, which you have been using since Chapter 7.5. SIGKILLandSIGSTOPcannot be handled, ignored or blocked. Every other signal can.- A program that catches
SIGTERMis usually being well-behaved — flushing files, releasing licences, removing temporary directories. Give it a moment. - Write signal names, not numbers. Numbering diverges between systems beyond the first few; only
1,2,3,9and15are reliable. kill -0 PIDchecks existence and permission without sending anything.- A process ended by signal
Nexits with status128 + N— so 130 for Ctrl-C, 143 for TERM, 137 for KILL.137in a log usually means the out-of-memory killer. - A process in state
Dignores every signal,SIGKILLincluded, until its I/O completes. That is a storage problem, not a program problem. - A tool that cannot load a library dies on
SIGABRT, so the status is 134 — Chapter 25.3 measures it, and the message comes from the loader rather than the tool.
You now know what you are sending. The next chapter is about sending it deliberately: which signal to use first, how long to wait, what -9 actually costs you, and how to act on many processes at once without acting on the wrong ones.
Where this fits
Part of the Linux & Bash curriculum.
