Overview of Unix/Linux Signals
- Signals are software interrupts used by the operating system (OS) and the CPU to notify processes that "something has happened."
kill
, kill -TERM
, kill -STOP
, kill -CONT
are user‐space commands that operate by sending a signal to the target process (PID).- Signals can lead to termination, pausing, continuation, or entirely custom behavior depending on how the process is coded.
The kill
Command Family
- Functional description: Ends, pauses, or resumes a process.
- Mechanical description: Sends a specific signal number (e.g.
kill
(default) → SIGTERMkill -9
→ SIGKILLkill -STOP
→ SIGSTOPkill -CONT
→ SIGCONT)
- Works at the OS level—not merely a terminal feature.
Terminal Shortcuts That Map to Signals
Ctrl-C
→ SIGINT (Interrupt / Terminate)Ctrl-Z
→ SIGTSTP (Terminal Stop – analogous to pause)Ctrl-\
→ SIGQUIT (Quit + core dump)- Some shortcuts, such as sending EOF (
Ctrl-D
), are not signals.
How the OS Tracks Signals
- Internally maintains two 32-bit integers (implementation dependent):
- Pending Signals – bits set when a signal has been delivered but not yet handled.
- Blocked Signals – bits set for signals the process has asked to ignore/hold.
- Each bit position corresponds to a distinct signal number.
- Every check by the scheduler looks at both bitmaps:
- If a signal is pending and not blocked → run its handler (default or user-defined).
- If a signal is blocked → stays in pending list until unblocked.
Standard (Well-Known) Signals & Meanings
- SIGINT – Interrupt from keyboard (
Ctrl-C
). - SIGHUP – Hang-up (e.g.
SSH session lost). - SIGKILL – Non-catchable, non-ignorable, immediate kill.
- SIGPIPE – Broken pipe; write on a pipe with no reader.
- SIGTSTP – Interactive stop (
Ctrl-Z
). - SIGQUIT – Quit + core dump (
Ctrl-\
). - Hardware/exception-driven examples:
- SIGFPE – Floating-point error (÷0, overflow).
- SIGSEGV – Segmentation fault.
- SIGBUS – Bus error (invalid alignment, deleted mapped file, etc.).
User-Defined Signals
- SIGUSR1 and SIGUSR2 – No default semantics; purely for IPC between processes.
Writing Signal Handlers in C
Minimal Example
#include <signal.h>
#include <unistd.h>
void handler(int num){
write(STDOUT_FILENO, "Thwarted!\n", 9);
}
int main(){
signal(SIGINT, handler); // Activate handler
while(1){
printf("Do nothing\n");
}
}
- Prototype rules:
void handler(int signum)
— returns void
, accepts an int
indicating which signal invoked it. - Multiple signals can share the same handler, switch on
signum
internally.
Why Use write
Instead of printf
?
- Only async-signal-safe functions are allowed inside handlers.
printf
is not async-signal-safe due to buffered I/O.write
, _exit
, sigatomic_t
variable assignments, etc., are safe.
Async-Signal-Safe Functions (subset)
write
, read
, waitpid
, kill
, _exit
, sigaction
, sigprocmask
, etc.- Full list: POSIX
signal(7)
man page.
Things Not to Do Inside Signal Handlers
- Do not call
malloc
, free
, realloc
. - Do not use
printf
, sprintf
, fprintf
. - Do not modify global state in a non-atomic way.
- Do not call non-reentrant functions or block waiting for I/O.
- Do not change signal dispositions (
signal()
) or masks from within the same handler unless you really understand reentrancy ramifications.
Signals That Cannot Be Caught or Ignored
- SIGKILL (
kill -9
) - SIGSTOP (
Ctrl-Z
, kill -STOP
) - Rationale: OS must retain absolute control; prevents unkillable processes.
Resetting or Ignoring Signals
- Restore default:
signal(signum, SIG_DFL);
- Ignore entirely:
signal(signum, SIG_IGN);
Synchronous vs Asynchronous Event Handling
- Synchronous (Polling): CPU repeatedly checks status → inefficient, high latency.
- Asynchronous (Interrupt): CPU continues normal tasks, gets interrupted when an event occurs → efficient, low latency.
Hardware Interrupts
- Examples: Keyboard keypress, NIC packet arrival, printer ready.
- Hardware triggers an interrupt line → CPU saves context → jumps to Interrupt Service Routine (ISR).
- If kernel cannot completely resolve, it forwards to user process via signals (e.g.
page fault leads to SIGSEGV for user code).
CPU Steps on Interrupt
- Save context (program counter, general registers, stack pointer).
- Consult Interrupt Vector Table → find ISR.
- Execute handler (possibly in kernel).
- Restore context and resume.
Example: Divide-by-Zero
- Instruction
DIV ebx
with ebx = 0
triggers CPU FPU exception. - Kernel ultimately sends SIGFPE to the offending user process.
- Default result: Process terminates + core dump.
- Custom handler possible, but generally used only for logging/debugging; fixing logic preferred.
Dangers of Catching Fault Signals
- SIGSEGV & SIGFPE handlers can create infinite fault loops if bug persists.
- Better: let program crash, analyze core dump, or pre-validate inputs.
Signal Concurrency & Race Conditions
- Signals are not queued by default in old Unix; multiple identical signals may coalesce.
- Signals can interrupt other handlers mid-execution (non-atomic).
sigprocmask()
lets a thread temporarily block signals, reducing races.
Classical Concurrency Scenarios
- While handling SIGX, a second SIGX occurs.
- While handling SIGX, a different SIGY occurs.
- Implementation policies vary:
- Last-In-First-Out (nested handlers).
- Drop duplicates (coalesce).
- Block & queue (POSIX‐realtime signals add queued semantics).
Subtle Bug Example
void handle_sigint(int s){
signal(SIGQUIT, SIG_IGN); // attempt to ignore SIGQUIT
/* lengthy calc */
signal(SIGQUIT, prev); // window exists where SIGQUIT could arrive
}
- A SIGQUIT delivery could race between the two
signal()
calls.
sigaction
– Robust API Replacement for signal()
struct sigaction sa, oldsa;
sa.sa_handler = my_handler; // or SIG_IGN / SIG_DFL
sa.sa_flags = SA_RESTART; // auto-restart certain syscalls, or 0
sigemptyset(&sa.sa_mask); // extra signals to block during handler
sigaction(SIGINT, &sa, &oldsa);
sa_handler
OR sa_sigaction
(if SA_SIGINFO
flag set).sa_mask
allows temporary blocking of specified signals while this handler runs.- Advantages: portable, atomic installation, avoids mousetrap problem where handler "works once" then disappears.
Historical “Mousetrap” Problem
- Early implementations required re‐installing handlers inside the handler (
signal(signum, handler)
). - Race: If signal arrives before reinstall, handler missing → default action (often kill).
sigaction()
solves by installing persistently in one call.
Practical / Ethical Takeaways
- Don’t create intentionally unkillable programs; SIGKILL and SIGSTOP are reserved for sysadmin control.
- Use signals for clean shutdown, IPC nudges, or device latencies—not for complex logic.
- Always respect async-signal-safety; otherwise, undefined behavior and crashes ensue.
- For timing-critical or high-rate events, consider POSIX realtime signals (
SIGRTMIN
+ n) which queue and carry payload data.
Quick Reference Cheat-Sheet
- Generate in shell:
kill -l
(lists numeric ↔ name mapping). - Read current disposition:
trap -p
(in bash) or inspect via /proc/<pid>/status
(Linux). - Block/unblock in C:
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, NULL); // block SIGINT
- Check pending signals:
sigpending(&set)
.
- Two 32-bit integers ⇒ 2 \times 32 = 64 independent classic signals per process (exact count varies).
- POSIX‐realtime provides additional SIGRTMIN \ldots SIGRTMAX signals (implementation defined, often 32).
Summary
- Signals are lightweight, asynchronous notifications.
- Generated by hardware/CPU, kernel, or user tools (
kill
). - Handlers must be minimal, async-signal-safe, and carefully manage races.
sigaction
is the modern, race-free way to manage signals.- Some signals (SIGKILL, SIGSTOP) are non-interceptable by design.
- Overuse or misuse leads to race conditions, deadlocks, or infinite fault loops—handle responsibly!