linux-sysprog · beginner · ~10 min
Install a sigaction-based handler that sets an async-signal-safe flag — the cornerstone of graceful shutdown.
When a user presses Ctrl+C in your terminal, the kernel sends SIGINT to the foreground program. The default response is "die immediately." For a real program — one with open files, in-flight writes, or a server with connected clients — that's a recipe for corrupted state. You want to catch the signal, set a flag, finish what you're doing, then exit cleanly.
Implement two pieces:
volatile sig_atomic_t g_caught_sigint = 0;
void install_sigint_handler(void);
install_sigint_handler must install a sigaction-based handler for SIGINT. When SIGINT is delivered (now or later), the handler sets g_caught_sigint = 1. The handler must do only that — no printf, no malloc.
void install_sigint_handler(void);
install_sigint_handler once arms the handler.SIGINT delivery, g_caught_sigint is 1.SIGINT delivery, the handler still fires (it's not a one-shot).sigaction(), not signal() — signal()'s portability footguns are exactly why POSIX added the replacement.volatile sig_atomic_t flag is the canonical primitive.g_caught_sigint's declaration — the harness reads it.| Sequence | After |
|---|---|
install_sigint_handler() |
g_caught_sigint == 0 |
raise(SIGINT) |
g_caught_sigint == 1 |
reset to 0; raise(SIGINT) again |
g_caught_sigint == 1 (handler stays installed) |
sigaction() (unlike legacy signal() on some BSDs) does this for free; don't reinstall inside the handler.struct sigaction sa = {0};, set sa.sa_handler, sigemptyset(&sa.sa_mask), then call sigaction(SIGINT, &sa, NULL).sigemptyset on sa.sa_mask. The struct's sa_mask field is not reliably zero-initialised across libc versions.signal() — works in tests, breaks on the next platform.printf inside the handler — not async-signal-safe. Use write() if you must print, but for this exercise just set the flag.sigaction() doesn't need it.This is the foundational pattern for every long-running C program: graceful shutdown. The same flag-then-react pattern shows up in nginx, systemd, every TCP server you'll write.
If you only learn one signal pattern, learn this one. Every long-running C program — daemon, server, batch job — needs it.
(no runtime input — the harness calls the function and raises SIGINT)
(no return; observable effect is the g_caught_sigint flag flipping to 1)
Use sigaction, not signal. The handler must be tiny — set the flag, nothing else.
#include <signal.h>
#include <stdio.h>
volatile sig_atomic_t g_caught_sigint = 0;
void install_sigint_handler(void) {
/* TODO: install a handler that sets g_caught_sigint = 1 */
}
Using legacy signal(). Forgetting sigemptyset(&sa.sa_mask). Doing real work (printf, malloc) inside the handler.
Multiple SIGINTs in rapid succession — the handler stays installed; the flag stays set until your code clears it.
Solve this exercise in the browser editor — compile and run against the test harness, no setup required.