ipc · medium

IPC: Signals & sigaction — Async Notifications, Masks, and Handler Safety

POSIX signals are asynchronous notifications from the kernel or other processes. Each signal has a number, a default disposition (terminate/ignore/stop/core), and can be blocked or caught. SIGKILL (9) and SIGSTOP cannot be caught, blocked, or ignored — kernel handles them directly. Always use sigaction() over signal(): sigaction is persistent, supports sa_mask (block signals during handler), and SA_RESTART (auto-retry interrupted slow syscalls, avoiding EINTR handling). Handlers must use only async-signal-safe functions (write(), not printf()). Communicate handler results to main thread via volatile sig_atomic_t. Block signals during critical sections with sigprocmask(). Server idioms: SIG_IGN SIGPIPE (broken connections), SIGCHLD→waitpid() (reap zombies), SIGHUP→reload-config (daemon convention).

🔑 Key line

Signals: async kernel notifications; use sigaction() not signal(); SIGKILL/SIGSTOP are uncatchable; sa_mask blocks signals during handler; SA_RESTART resumes interrupted syscalls; only async-signal-safe functions in handlers; use volatile sig_atomic_t for handler→main communication.

The code

// POSIX signals — async notification between kernel/processes
#include <signal.h>
// Custom handler — ONLY async-signal-safe functions!
volatile sig_atomic_t g_shutdown = 0;
void sigint_handler(int signum) {
g_shutdown = 1; // safe: sig_atomic_t assignment
// write() is async-signal-safe; printf() is NOT
}
int main() {
// Always use sigaction, not signal()
struct sigaction sa{};
sa.sa_handler = sigint_handler;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGTERM); // block during handler
sa.sa_flags = SA_RESTART; // restart slow syscalls
sigaction(SIGINT, &sa, nullptr);
// Graceful shutdown loop
while (!g_shutdown) {
do_work(); // interrupted by signal, SA_RESTART resumes
}
cleanup(); // guaranteed to run
return 0;
}
// Block signals during critical section
sigset_t block_set;
sigemptyset(&block_set);
sigaddset(&block_set, SIGINT);
sigaddset(&block_set, SIGTERM);
sigprocmask(SIG_BLOCK, &block_set, &old_set);
// critical section — signals deferred to pending set
sigprocmask(SIG_SETMASK, &old_set, nullptr); // unblock
// Self-signal: raise(SIGALRM) — same as kill(getpid(), SIGALRM)
// Child death: signal(SIGCHLD, SIG_IGN) to auto-reap
// Ignore broken pipe: signal(SIGPIPE, SIG_IGN) in server

What this lesson walks through

  1. 01Signal table — common signals and their defaults
  2. 02SIGINT delivered — kernel interrupts the process
  3. 03sigaction() — always prefer over signal()
  4. 04Signal mask — blocking signals during critical sections
  5. 05SIGKILL — cannot be caught, blocked, or ignored
  6. 06Server idioms: SIGPIPE, SIGCHLD, SIGHUP

Each signal has a number, a default disposition (terminate, ignore, stop, core-dump), and a catchable flag. SIGKILL (9) and SIGSTOP are special: they cannot be caught or ignored — the kernel handles them unconditionally. All others can be redirected to a custom handler or ignored.

See it animated — step by step, at your own pace

Unlock the full interactive walkthrough of IPC: Signals & sigaction — Async Notifications, Masks, and Handler Safety and 100+ animated C++ interview lessons.

← Previous
IPC: POSIX Message Queues — Priority-Ordered Framed Messages
Next →
OS: POSIX Sockets — TCP Server/Client, epoll, UDP, TCP_NODELAY, TIME_WAIT