POSIX Async-Signal-Safe Functions in C
When handling asynchronous signals in C, execution can be interrupted at virtually any instruction, meaning signal handlers must only call functions that are guaranteed not to corrupt program state or trigger deadlocks. POSIX standards define a strict list of "async-signal-safe" functions that are reentrant or interrupt-safe and therefore permitted inside signal handlers. This article explains the requirements of async-signal safety, outlines the most commonly used safe functions, highlights dangerous operations to avoid, and details best practices for safe signal handling.
What Makes a Function Async-Signal-Safe?
An async-signal-safe function is one that can be safely invoked from a signal handler context without interfering with the interrupted thread's execution. A signal may interrupt a process while it is in the middle of executing an internal routine, modifying global variables, or holding a mutex lock. If the signal handler invokes a function that attempts to acquire that same lock or modify the same shared state, a deadlock or memory corruption occurs.
To be async-signal-safe under POSIX, a function must satisfy at least one of the following criteria:
- It is entirely reentrant, relying solely on local variables and arguments passed on the stack.
- It does not access shared global data without atomic protections.
- It disables signals during any critical internal execution that alters global state.
- It does not allocate heap memory or use internal static buffers.
Common POSIX Async-Signal-Safe Functions
POSIX.1-2008 and POSIX.1-2017 define roughly 180-200 specific functions as async-signal-safe. The most frequently used functions include:
File and Low-Level I/O
open(),creat(),close()read(),write(),pread(),pwrite()lseek(),dup(),dup2()fstat(),stat(),lstat()fcntl()
Process Management
_exit(),_Exit()(Note: Standardexit()is not safe as it flushes standard I/O buffers and runs cleanup handlers)fork()execve()wait(),waitpid()kill(),raise()getpid(),getppid(),getuid(),geteuid(),getgid(),getegid()
Signal Operations
sigaction()sigprocmask()sigpending()sigsuspend()sigaddset(),sigdelset(),sigemptyset(),sigfillset(),sigismember()
Time and Clocks
clock_gettime()time()alarm()timer_getoverrun(),timer_gettime(),timer_settime()
String and Memory Routines (POSIX.1-2024 additions)
Recent updates to POSIX standards have formally designated several stateless memory and string functions as safe, including:
memchr(),memcmp(),memcpy(),memmove(),memset()strcat(),strchr(),strcmp(),strcpy(),strlen(),strncmp(),strncpy()
Functions That Are Strictly Unsafe
Calling non-async-signal-safe functions inside a handler leads to undefined behavior. Common unsafe operations include:
- Dynamic Memory Allocation:
malloc(),calloc(),realloc(), andfree()rely on internal heap locks and shared control blocks. - Standard I/O Operations:
printf(),puts(),fopen(),fclose(),fread(), andfprintf()use internal buffers protected by locks. - Synchronization Primitives: Mutex locks
(
pthread_mutex_lock()), condition variables, and semaphores can deadlock the interrupted thread. - Process Exit Routines: Standard
exit()andabort()(thoughabort()can be used to terminate viaSIGABRT, invoking cleanup hooks is unsafe).
Best Practices for Handling Signals in C
The most robust way to write signal handlers is to avoid executing complex logic inside them altogether:
- Set a Flag: Declare a global flag using
volatile sig_atomic_t(e.g.,volatile sig_atomic_t shutdown_requested = 0;). The signal handler simply sets this variable to1, and the main execution loop checks it periodically. - The Self-Pipe Trick: If using an event loop
(
select(),poll(), orepoll()), write a single byte to an internal pipe oreventfdusing the async-signal-safewrite()system call inside the handler. This cleanly wakes the main loop. - Synchronous Signal Handling: In multithreaded
programs, block signals in all threads using
pthread_sigmask()and dedicate a single thread to wait synchronously for signals usingsigwait()orsigwaitinfo(). This eliminates the asynchronous signal handler context entirely, allowing you to safely use non-reentrant functions likeprintf()ormalloc()upon receiving a signal.