Linux System Programming · intermediate · ~12 min

Writing safe signal handlers — async-signal-safe rules

Learn which functions are safe to call from inside a signal handler, and understand why common functions like `printf` are not on that list.

Lesson

What a signal handler interrupts

A signal handler can run at any moment. It interrupts your normal code at an arbitrary point.

That point might be in the middle of:

  • malloc() — allocating memory
  • printf() — writing buffered output
  • pthread_mutex_lock() — holding a lock

If your handler then calls one of those same functions, you can hit serious problems:

  • Deadlock — the lock is already held by the interrupted thread, so the handler waits forever.
  • Corruption — you re-enter code that was halfway through updating its internal state.

Async-signal-safe functions

The Linux manual page signal-safety(7) lists the async-signal-safe functions. These are the functions you are allowed to call from a handler. "Async-signal-safe" means a function is safe to call even when it interrupts other code.

The main safe functions include:

  • write, read
  • _exit
  • kill, signal, sigaction, sigprocmask
  • most simple system calls

Functions that are not safe include:

  • printf and anything else that uses stdio buffers
  • malloc, free, and anything that allocates memory
  • anything that takes a libc lock

The defensive pattern

Keep the handler tiny. Let the main program do the real work.

  • The handler sets a flag.
  • The main loop checks the flag and does the work.

Use a volatile sig_atomic_t for the flag. This is the only type the C standard guarantees can be read and written atomically with respect to signal delivery, so the value is never seen half-updated.

Code examples

#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;
}

Common mistakes

  • Calling printf, fprintf, puts, or sprintf from a handler. None of these are async-signal-safe.
  • Calling malloc or free, or anything that allocates. This includes most logging libraries.
  • Reading a non-atomic global. Use a volatile sig_atomic_t for handler flags.

Summary

  • A handler may only call async-signal-safe functions, such as write, _exit, kill, and sigaction.
  • printf, malloc, and free are not safe inside a handler.
  • The safe pattern: set a volatile sig_atomic_t flag in the handler, then react to it in the main loop.

Practice with these exercises