Linux System Programming · intermediate · ~12 min
Know which functions you can call from a signal handler — and why printf isn't on the list.
A signal handler interrupts your code at an arbitrary point — possibly in the middle of malloc(), printf(), or pthread_mutex_lock(). If you then call those same functions from the handler, you can deadlock (the mutex is already held by the interrupted thread) or corrupt internal state.
The Linux man page signal-safety(7) lists the async-signal-safe functions — the ones you may legally call. The headline set: write, read, _exit, kill, signal, sigaction, sigprocmask, plus most simple syscalls. Notably not safe: printf, malloc, free, anything using stdio buffers, anything that grabs a libc lock.
The defensive pattern is: the handler sets a flag; the main loop does the work. Use a volatile sig_atomic_t for the flag — it's the only type the C standard guarantees atomic against signal delivery.
#include <signal.h>
#include <unistd.h>
#include <string.h>
static volatile sig_atomic_t want_quit = 0;
static void on_term(int sig) {
(void)sig;
want_quit = 1;
/* write() IS async-signal-safe; printf() is not. */
const char msg[] = "got SIGTERM, will exit soon\n";
write(STDERR_FILENO, msg, sizeof msg - 1);
}
int main(void) {
struct sigaction sa = {0};
sa.sa_handler = on_term;
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, NULL);
while (!want_quit) { sleep(1); }
/* All the real cleanup happens out here. */
return 0;
}
volatile sig_atomic_t for flags.Handlers must only call async-signal-safe functions (write, _exit, kill, sigaction, …). Pattern: set a volatile sig_atomic_t flag in the handler, react in the main loop.