← All 131 books signals, one per page
One signal per page

signals, one per page

Twenty-eight lessons on POSIX signals, one signal or signal concept per page: the polite defaults SIGTERM, SIGKILL, SIGINT and SIGHUP; the keyboard signals Ctrl+C, Ctrl+backslash and Ctrl+Z plus the terminal's SIGTTIN and SIGTTOU; the violent ones, uncatchable SIGKILL, SIGSEGV, SIGPIPE and core dumps; the three dispositions default, ignore and catch, sigaction, SIG_IGN with SIG_DFL, and the user-defined SIGUSR1 and SIGUSR2; the command line, kill -l, pkill with killall, trap and the 128+N exit convention; the process lifecycle of SIGCHLD, zombies reaped by wait, SIGSTOP with SIGCONT and process groups; and survival with nohup, setsid, disown and async-signal-safety.


Steve Hodgkiss 7 lessons

One signal, one page, one real capture. That's the rhythm.

signals, one per page

Twenty-eight lessons on POSIX signals, one signal or signal concept per page: the polite defaults SIGTERM, SIGKILL, SIGINT and SIGHUP; the keyboard signals Ctrl+C, Ctrl+backslash and Ctrl+Z plus the terminal's SIGTTIN and SIGTTOU; the violent ones, uncatchable SIGKILL, SIGSEGV, SIGPIPE and core dumps; the three dispositions default, ignore and catch, sigaction, SIG_IGN with SIG_DFL, and the user-defined SIGUSR1 and SIGUSR2; the command line, kill -l, pkill with killall, trap and the 128+N exit convention; the process lifecycle of SIGCHLD, zombies reaped by wait, SIGSTOP with SIGCONT and process groups; and survival with nohup, setsid, disown and async-signal-safety.


Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).

Every fact in this book is as the official source states it, fetched and read during this build: the Linux man-pages man7.org pages signal(7), signal(2), kill(1), kill(2), sigaction(2), wait(2) and setpgid(2) from the man-pages project; the POSIX signal.h specification from The Open Group (IEEE Std 1003.1-2017); and the Wikipedia articles Signal (IPC), Core dump, Nohup, Kill (command), Zombie process, Exit status and Job control (Unix). No facts are sourced from Reddit, Stack Overflow or vendor blogs. Every command and output pair on every page was run on this machine (bash 5.2.21, procps-ng 4.0.4, kernel 6.8.0-147-generic) and confirmed before printing; every demo ran on scratch child processes, never on system files. Unsafe demonstrations, signalling PID 1 or the login shell, were not run and are stated from the man pages instead. An independent guide, not affiliated with or endorsed by the Linux man-pages project, The Open Group, the Wikimedia Foundation, or the Linux kernel developers.

Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.

© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.

Edition 1.0 · stevehodgkiss.net

Contents

Contents


Part 1 · The polite defaults
SIGTERM asks first4
Part 2 · The keyboard
Ctrl+C sends SIGINT5
Part 3 · The violent ones
You cannot catch 96
Part 4 · Dispositions
SIG_IGN and SIG_DFL7
Part 5 · The command line
Exit 128 plus N8
Part 6 · Process lifecycle
Zombies and reaping9
Part 7 · Survival
nohup ignores SIGHUP10

Per Linux man-pages signal(7), SIGTERM is signal 15, standard P1990, action Term, comment Termination signal. Per kill(1), if no signal is specified, the TERM signal is sent, the default action for this signal is to terminate the process, and this signal should be used in preference to the KILL signal (number 9). This machine's capture: kill -TERM to a background sleep, then wait, printed wait status=143, which is 128 plus 15.

signals companion · No. 01
SIGTERM

SIGTERM asks first

Fifteen, the polite ask

the polite ask: disposition decides$ kill -TERM $!wait $!wait status=143143 = 128 + 15TERM died, not a chosen exitSIGTERM is 15, action Term:the kernel holds the signaluntil the process answers:run the default, run ahandler, or ignore itkill(1): use TERM before KILL, it lets the process tidy up

SIGTERM is 15, action Term: terminate. kill with no number sends TERM, and kill(1) says it should be used in preference to KILL.

Why the courtesy? A process may install a handler for TERM: flush buffers, close files, say goodbye. Our capture: wait status=143, the shell writing 128 + 15 into $?.

TERM is a request. The next three pages cover the other fundamentals: the signal that never asks, the one from your keyboard, and the modem hangup.

RUN IT THIS WEEK

Send TERM to a scratch sleep and read the wait status yourself before you reach for 9.

Per the Wikipedia article Signal (IPC), typing certain key combinations at the controlling terminal of a running process causes the system to send it certain signals, and Ctrl-C sends an INT signal. Per Linux man-pages signal(7), SIGINT's comment is Interrupt from keyboard. This machine's kernel turns the intr character into signal 2 for the foreground process group; the key mapping belongs to the tty layer, not the shell.

signals companion · No. 02
keyboard

Ctrl+C sends SIGINT

The tty makes the signal

one keypress, one whole groupCtrl+CISIGSIGINTeditorsortpagerevery member of the foreground process group gets itthe terminal driver, not the shell, converts the character into signal 2;stty -a shows intr = ^C; the mapping belongs to the tty layer.Ctrl+C interrupts a group, not a single process

Press Ctrl+C and the terminal driver, not the shell, turns the intr character into signal 2.

It is delivered to the whole foreground process group: editor, sort and pager in one pipeline all feel the same INT at once.

Wikipedia's Signal article lists the key combinations; stty -a shows yours. The next two pages give the same story for quit and stop.

RUN IT THIS WEEK

Run stty -a and find the intr line; then try to name a key you could remap it to.

Per Linux man-pages signal(7), the signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored. Per sigaction(2), it is not possible to block SIGKILL or SIGSTOP by specifying them in sa_mask, and attempts to do so are silently ignored. Per kill(2), the only signals that can be sent to process ID 1, the init process, are those for which init has explicitly installed signal handlers. This machine's capture: a script that ignored TERM survived it, then died of KILL with final status=137.

signals companion · No. 03
SIGKILL

You cannot catch 9

Caught, blocked: never

no handler, no block, no appeal$ an ignored TERM, then KILLtrap "" TERMkill -TERM $!survived TERMkill -KILL $!final status=137SIGKILL 9 and SIGSTOP 19cannot be caught, blockedor ignored, says signal(7)sigaction(2): naming themin sa_mask is ignored tooignore was a choice the process made; KILL does not negotiate

signal(7) says it once: SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

sigaction(2) adds the fine print: name them in sa_mask and the request is silently ignored. Our capture: a script ignoring TERM survived TERM, then fell to KILL, final status=137.

One exception: PID 1 may ignore what it likes, since kill(2) only delivers the signals init has handlers for.

RUN IT THIS WEEK

Write a script that ignores TERM, prove it survives, then end it with 9.

Per the POSIX signal.h specification, SIG_DFL is a request for default signal handling, SIG_IGN is a request that signal be ignored, and SIG_ERR is the return value from signal() in case of error. Per Linux man-pages signal(7), during an execve, the dispositions of handled signals are reset to the default; the dispositions of ignored signals are left unchanged. This machine's capture: a script running trap with an empty TERM body survived TERM.

signals companion · No. 04
signal.h

SIG_IGN and SIG_DFL

The two special pointers

two special pointers$ macros from signal.hmacro meaningSIG_DFL default handlingSIG_IGN ignore the signalSIG_ERR error return valuePOSIX signal.h words, verbatimthe shell spelling of IGN:trap "" TERM; our capturesurvived TERM that way;across execve caught signalsreset, ignored ones persisttrap '' is SIG_IGN wearing a shell hat

Two pointer values are not functions: SIG_DFL, default handling, and SIG_IGN, ignore. POSIX words them as requests.

The shell spelling of IGN is trap "" TERM; our capture survived TERM exactly that way. And the asymmetry that matters: across exec, caught resets, ignored persists.

That is why nohup works: it sets IGN before exec, and the child is born immune to hangups.

RUN IT THIS WEEK

Prove the exec rule to yourself: exec a child from a script that ignored TERM and check its dispositions.

Per the Wikipedia article Exit status, when a command is terminated by a signal whose number is N, a shell sets the variable $? to a value greater than 128, most shells use 128+N, while ksh93 uses 256+N. Per kill(1), kill itself has exit status values 0 and beyond for its own failures. This machine's captures: HUP printed 129, INT 130, KILL 137, PIPE 141, TERM 143, USR1 138, SEGV 139.

signals companion · No. 05
exit status

Exit 128 plus N

How shells report death

how the shell reports a death$ this book's capturesdied $?HUP 129INT 130KILL 137TERM 143128 plus the signal number:HUP is 1 so 129, KILL is 9so 137, TERM is 15 so 143;per the Wikipedia Exitstatus article, ksh93 uses 256137 is always 128 plus 9, never a chosen exit code

A process cannot choose its own death status, so the shell invents one: 128 + the signal number.

Our table is all capture: HUP 129, INT 130, KILL 137, PIPE 141, TERM 143. Subtract 128 and you have the signal that did it.

137 is never a coincidence: it is always 9. ksh93 disagrees with 256 + N; everywhere else this arithmetic is law.

RUN IT THIS WEEK

Kill a job with three different signals and verify each status against 128 plus the number.

Per Linux man-pages wait(2), if a wait is not performed, then the terminated child remains in a zombie state, and the kernel maintains a minimal set of information about the zombie process, the PID, termination status, and resource usage, to allow the parent to obtain information about the child. Per the Wikipedia article Zombie process, a zombie is a process that has completed execution but still has an entry in the process table, and the kill command has no effect on a zombie process. This machine's capture: before wait ps showed Z python3; after wait, the entry was gone.

signals companion · No. 06
zombies

Zombies and reaping

Dead until wait

dead but not deleted$ zombie, then reapingfork; the child exitsparent sleeps, no waitbefore wait: Z python3after wait: goneone process table entry, helda zombie keeps its pid andexit status until wait(2),says wait(2); if the parentnever asks, the entry leaks;kill has no effect on itWikipedia: already dead, waiting to be reaped

A zombie finished executing but keeps its table entry: pid, exit status, resource usage. Dead, not deleted.

wait(2) is the reaper: one call and the entry goes. Our capture: before wait Z, after wait gone. And kill does nothing: per Wikipedia, you cannot kill what is already dead.

SIGCHLD set to SIG_IGN stops zombies on Linux, but sigaction(2) calls a wait loop the only fully portable way.

RUN IT THIS WEEK

Leave a zombie on purpose for thirty seconds, then reap it; feel how unhurried reaping is.

Per the Wikipedia article Nohup, nohup means no hang up; its purpose is to execute a command such that it ignores the HUP (hangup) signal and therefore does not stop when the user logs out, and output that would normally go to the terminal goes to a file called nohup.out, if it has not already been redirected. This machine's capture, run under a real pseudo-terminal so nohup.out would be created: nohup printed ignoring input and appending output to nohup.out, and the file held hello-from-nohup.

signals companion · No. 07
nohup

nohup ignores SIGHUP

Outlive the terminal

survive the logout$ nohup echo hellonohup: ignoring input andappending output to nohup.outhello-from-nohuprun under a real terminal here,so nohup.out was creatednohup runs a command withSIGHUP ignored, so logoutcannot kill it; if stdout isa terminal, output goes tonohup.out, per Wikipediaimmune to hangup, not to kill -9

nohup runs a command with SIGHUP already ignored, so the closing terminal cannot take it down.

Per Wikipedia, terminal-bound output goes to nohup.out. Our capture, under a real pseudo-terminal: nohup announced appending output to nohup.out, and the file held hello-from-nohup.

No hangup immunity, not immortality: KILL still ends the job, and stdout redirected elsewhere never meets nohup.out.

RUN IT THIS WEEK

Run a short job under nohup with stdout already redirected, and confirm no nohup.out appears.

Index

Index


Ctrl+C sends SIGINT5
Exit 128 plus N8
nohup ignores SIGHUP10
SIG_IGN and SIG_DFL7
SIGTERM asks first4
You cannot catch 96
Zombies and reaping9