TĐ

Unix/Linux Signal Handling – Detailed Study Notes

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) → SIGTERM
    • kill -9 → SIGKILL
    • kill -STOP → SIGSTOP
    • kill -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):
    1. Pending Signals – bits set when a signal has been delivered but not yet handled.
    2. 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

  1. Save context (program counter, general registers, stack pointer).
  2. Consult Interrupt Vector Table → find ISR.
  3. Execute handler (possibly in kernel).
  4. 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

  1. While handling SIGX, a second SIGX occurs.
  2. While handling SIGX, a different SIGY occurs.
  • Implementation policies vary:
    1. Last-In-First-Out (nested handlers).
    2. Drop duplicates (coalesce).
    3. 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).

Formulas & Data Sizes

  • 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!