Loading learning content...
Signal handlers appear deceptively simple. Install a function, the signal arrives, the function runs—what could go wrong? As generations of developers have discovered, almost everything.
The asynchronous nature of signals means your handler can interrupt your program at virtually any point—between two statements, in the middle of a library call, even during dynamic memory allocation. This creates opportunities for subtle bugs that are difficult to reproduce, difficult to debug, and potentially catastrophic in production.
This page arms you with the knowledge to write signal handlers that are not only functional but correct—handlers that work reliably rather than appearing to work until they suddenly don't.
By the end of this page, you will understand: how to install handlers with sigaction(), what 'async-signal-safe' means and why it matters, the minimal operations safe inside handlers, and architectural patterns that keep signal handling simple and correct.
The sigaction() system call is the POSIX-standard, reliable way to install signal handlers. It supersedes the legacy signal() function and provides complete control over signal handling behavior.
#include <signal.h>
struct sigaction {
void (*sa_handler)(int); /* Handler function (simple) */
void (*sa_sigaction)(int, siginfo_t *, void *); /* Handler (extended) */
sigset_t sa_mask; /* Signals blocked during handler */
int sa_flags; /* Behavioral flags */
};
int sigaction(int signum,
const struct sigaction *act,
struct sigaction *oldact);
Parameters:
signum: The signal to configure (e.g., SIGTERM, SIGINT)act: New action to install (NULL to query current)oldact: Where to store previous action (NULL if not needed)Returns: 0 on success, -1 on error (check errno)
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
#include <signal.h>#include <stdio.h>#include <unistd.h> volatile sig_atomic_t interrupt_count = 0; void sigint_handler(int sig) { interrupt_count++; /* Safe: Only modifying sig_atomic_t variable */} int main() { struct sigaction sa; /* * Step 1: Initialize the handler function * Use sa_handler for simple (single-argument) handlers */ sa.sa_handler = sigint_handler; /* * Step 2: Initialize the signal mask * Signals in sa_mask are blocked during handler execution. * Start with empty set - only the handled signal is auto-blocked. */ sigemptyset(&sa.sa_mask); /* * Step 3: Set flags (often 0 or SA_RESTART) * SA_RESTART: Automatically restart interrupted syscalls */ sa.sa_flags = SA_RESTART; /* * Step 4: Install the handler */ if (sigaction(SIGINT, &sa, NULL) == -1) { perror("sigaction"); return 1; } printf("Press Ctrl+C multiple times, then wait...\n"); while (1) { sleep(5); printf("Interrupt count: %d\n", interrupt_count); } return 0;}| Flag | Effect |
|---|---|
SA_RESTART | Automatically restart slow syscalls interrupted by this signal |
SA_SIGINFO | Use sa_sigaction handler instead of sa_handler (extended info) |
SA_NOCLDSTOP | Don't generate SIGCHLD when children stop (only on termination) |
SA_NOCLDWAIT | Don't create zombie children; auto-reap on exit |
SA_NODEFER | Don't block the signal during its own handler (dangerous) |
SA_RESETHAND | Reset to SIG_DFL after handler runs once (legacy behavior) |
SA_ONSTACK | Execute handler on alternate stack (set via sigaltstack) |
The extended handler form provides additional information about the signal:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h> void sigfpe_handler(int sig, siginfo_t *info, void *context) { /* * siginfo_t provides rich information about signal source. * Key fields vary by signal type. */ fprintf(stderr, "\n=== SIGFPE Details ===\n"); fprintf(stderr, "Signal number: %d\n", info->si_signo); fprintf(stderr, "Error code: %d\n", info->si_code); /* si_code values for SIGFPE */ switch (info->si_code) { case FPE_INTDIV: fprintf(stderr, "Cause: Integer divide by zero\n"); break; case FPE_INTOVF: fprintf(stderr, "Cause: Integer overflow\n"); break; case FPE_FLTDIV: fprintf(stderr, "Cause: Floating-point divide by zero\n"); break; case FPE_FLTOVF: fprintf(stderr, "Cause: Floating-point overflow\n"); break; default: fprintf(stderr, "Cause: Unknown (%d)\n", info->si_code); } fprintf(stderr, "Fault address: %p\n", info->si_addr); fprintf(stderr, "Sending PID: %d\n", info->si_pid); fprintf(stderr, "Sending UID: %d\n", info->si_uid); exit(1);} int main() { struct sigaction sa; sa.sa_sigaction = sigfpe_handler; /* Use extended handler */ sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; /* MUST set this for sa_sigaction */ if (sigaction(SIGFPE, &sa, NULL) == -1) { perror("sigaction"); return 1; } /* Trigger SIGFPE with integer division by zero */ volatile int x = 1; volatile int y = 0; volatile int z = x / y; /* SIGFPE generated here */ printf("Result: %d\n", z); /* Never reached */ return 0;}Use SA_SIGINFO when you need: (1) details about signal source (which process sent it), (2) error specifics for SIGSEGV/SIGFPE/SIGBUS (fault address, error subtype), (3) real-time signal payload data, or (4) child status details for SIGCHLD. For simple termination handling, the basic handler is sufficient.
The most important concept in signal handler programming is async-signal-safety. This concept determines what you can and cannot do inside a signal handler.
Signals can be delivered at any point during program execution. Consider what happens if:
malloc() to allocate memorymalloc() has acquired its internal lock and is manipulating the heapprintf(), which internally calls malloc()malloc() tries to acquire its lock—deadlock (or heap corruption)This isn't hypothetical—it's a real failure mode that has caused countless production incidents.
A function is async-signal-safe if it can be safely called from a signal handler, even if the signal interrupted a call to the same function (or any other function). POSIX specifies exactly which functions are safe.
POSIX.1-2017 specifies approximately 130 async-signal-safe functions. Key categories:
System Calls (most are safe):
read(), write(), close(), open(), dup(), pipe()kill(), raise(), sigaction(), sigprocmask(), sigsuspend()fork(), _exit(), execve(), waitpid()stat(), fstat(), lseek(), access()Thread/Signal Operations:
pthread_sigmask(), sem_post()String Operations (limited):
strlen(), strcpy(), strcat() (but not locale-dependent versions)Notably UNSAFE (never use in handlers):
printf(), fprintf(), sprintf() — use internal buffers, locksmalloc(), free(), realloc() — manipulate global heapexit() — runs atexit handlers (use _exit() instead)syslog() — may allocate memory, uses locksgethostbyname() — uses static buffer, not reentrant| Safe (OK to call) | UNSAFE (Never call) | Why Unsafe? |
|---|---|---|
write(STDERR_FILENO, ...) | printf(), fprintf() | Uses buffered I/O, internal locks |
_exit() | exit() | exit() runs atexit handlers |
sigaction() | sigignore() | May not be async-signal-safe |
kill(), raise() | system() | Uses many unsafe functions |
read(), write() | fread(), fwrite() | Buffered I/O, locks |
waitpid() | popen() | Allocates, uses pipes |
strlen() | sprintf() | sprintf uses buffers |
| (set volatile flag) | malloc(), free() | Heap manipulation, locks |
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
#include <signal.h>#include <unistd.h>#include <string.h> /* * CORRECT: A truly async-signal-safe handler. * Uses ONLY write() for output (to STDERR_FILENO). * Uses ONLY volatile sig_atomic_t for flag. */ volatile sig_atomic_t termination_requested = 0; void sigterm_handler(int sig) { /* * SAFE: write() is async-signal-safe. * We're writing to STDERR (unbuffered by default). * Using compile-time string length, not strlen in tight spots. */ const char msg[] = "SIGTERM received, shutting down...\n"; write(STDERR_FILENO, msg, sizeof(msg) - 1); /* * SAFE: Storing to volatile sig_atomic_t is atomic. * Main loop will check this flag and perform cleanup safely. */ termination_requested = 1;} /* UNSAFE handler example for comparison - DO NOT USE *//*void bad_sigterm_handler(int sig) { printf("SIGTERM received!\n"); // UNSAFE: printf() char *msg = malloc(100); // UNSAFE: malloc() snprintf(msg, 100, "PID %d", getpid()); // UNSAFE: snprintf() syslog(LOG_INFO, "Shutdown: %s", msg); // UNSAFE: syslog() free(msg); // UNSAFE: free() exit(0); // UNSAFE: exit()}*/ int main() { struct sigaction sa; sa.sa_handler = sigterm_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGTERM, &sa, NULL); const char startup[] = "Server running. Send SIGTERM to shutdown.\n"; write(STDOUT_FILENO, startup, sizeof(startup) - 1); while (!termination_requested) { /* Normal processing here */ sleep(1); } /* * SAFE cleanup out here, outside signal handler. * We can use printf, malloc, complex cleanup - anything we want. */ printf("Performing safe cleanup...\n"); /* ... cleanup code ... */ printf("Goodbye!\n"); return 0;}The correct signal handling pattern is: (1) Handler sets a volatile sig_atomic_t flag, (2) Main loop checks the flag, (3) Safe cleanup code runs in main context, not in handler. Keep handlers minimal—set flag, maybe write() a message, return. Do complex work outside.
When handlers need to communicate with the main program, they face a fundamental problem: what operations are atomic? If a signal interrupts a multi-byte variable write mid-operation, the main program sees garbage.
POSIX provides sig_atomic_t—a type guaranteed to be read and written atomically even in the presence of signals:
#include <signal.h>
volatile sig_atomic_t flag; /* MUST be volatile */
Important properties:
int)volatile to prevent compiler optimizationWithout volatile, the compiler may optimize away repeated reads:
while (flag == 0) { /* Compiler may read 'flag' once, cache it */
do_work(); /* Loop never exits even when handler sets flag! */
}
volatile tells the compiler: "This variable may change at any time; always read from memory."
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
#include <signal.h>#include <stdio.h>#include <unistd.h> /* * Correct use of volatile sig_atomic_t for handler-main communication. */ /* CORRECT: volatile AND sig_atomic_t */volatile sig_atomic_t shutdown_flag = 0;volatile sig_atomic_t signal_count = 0; /* WRONG examples - DO NOT USE *//*int shutdown_flag = 0; // Not sig_atomic_tsig_atomic_t shutdown_flag; // Not volatilevolatile int signal_count; // Not sig_atomic_tvolatile sig_atomic_t data[100]; // Arrays not guaranteed atomic*/ void sigint_handler(int sig) { signal_count++; /* Atomic increment (mostly - see caveats) */ shutdown_flag = 1;} int main() { struct sigaction sa; sa.sa_handler = sigint_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGINT, &sa, NULL); printf("Press Ctrl+C to increment counter and set shutdown flag\n"); while (!shutdown_flag) { /* * volatile ensures we re-read shutdown_flag each iteration. * Without volatile, compiler might hoist the read outside the loop. */ sleep(1); printf("Waiting... (count so far: %d)\n", signal_count); } printf("Final signal count: %d\n", signal_count); return 0;}sig_atomic_t guarantees atomic access, not atomic operations:
volatile sig_atomic_t count = 0;
void handler(int sig) {
count++; /* PROBLEM: Read-modify-write is not atomic! */
}
count++ is actually three operations: read count, increment, write count. If the main program also modifies count, you have a race condition. For true atomic operations, you need:
<stdatomic.h> (C11)__sync_fetch_and_add)For simple flags (0 or 1), sig_atomic_t is sufficient. For counters or complex state shared between handler and main code, use additional synchronization or redesign.
sig_atomic_t is often just an int, but POSIX only guarantees values -127 to 127. For larger values:
#include <stdint.h>
#include <stdatomic.h>
atomic_int counter; /* C11 atomic integer - full int range, atomic ops */
Use volatile sig_atomic_t for simple boolean flags only. For anything more complex (counters, state machines, shared data), either use C11 atomics or redesign to avoid shared state between handler and main code. The safest handlers only set a flag and let the main loop do everything else.
Reentrancy is the ability of a function to be safely interrupted and called again before the first invocation completes. Signal handlers create mandatory reentrancy concerns because they can interrupt any code, including library functions and your own functions.
Consider a simple logging function:
123456789101112131415161718192021222324252627282930313233
#include <string.h> static char log_buffer[256]; /* Global buffer - NOT reentrant! */static int log_position = 0; /* NON-REENTRANT function - unsafe to call from signal handler */void log_message(const char *msg) { /* * Imagine signal arrives RIGHT HERE, during log_message()... * Handler calls log_message() again. * log_buffer and log_position are corrupted! */ int len = strlen(msg); if (log_position + len < sizeof(log_buffer)) { memcpy(log_buffer + log_position, msg, len); log_position += len; /* Handler returns here, but log_position is now wrong */ }} /* * Why is this dangerous? * * 1. Main code: log_message("Starting job A") * 2. Execution is interrupted - len calculated, position read * 3. Signal handler: log_message("Signal!") * 4. Handler writes to log_buffer, updates log_position * 5. Handler returns * 6. Main code continues - but log_position changed! * 7. Main code writes "Starting job A" at wrong position * 8. Buffer is corrupted, possibly overflows */1. Global or static variables:
static char buffer[100]; /* Shared across calls - not reentrant */
2. Returning pointers to static data:
char *strtok(char *str, const char *delim); /* Uses static buffer */
struct tm *localtime(const time_t *timer); /* Returns static struct */
3. Acquiring locks:
void malloc_memory() {
pthread_mutex_lock(&heap_lock); /* If signal during lock... */
/* ... handler calls malloc ... */
/* ... deadlock! */
}
4. Modifying global state:
errno = EINVAL; /* errno is global, may be modified during syscall */
These concepts overlap but aren't identical:
| Property | Meaning | Requirement |
|---|---|---|
| Reentrant | Safe to call simultaneously from multiple contexts | No global/static state |
| Thread-safe | Safe to call from multiple threads | Uses proper locking |
| Async-signal-safe | Safe to call from signal handlers | Reentrant AND no locking |
Async-signal-safe ⊂ Reentrant: All async-signal-safe functions are reentrant, but not all reentrant functions are async-signal-safe (some use locks, which can deadlock in handlers).
Strategy 1: Avoid global state
/* Pass buffer as parameter instead of using static */
int log_message_reentrant(char *buffer, size_t bufsize, const char *msg);
Strategy 2: Use _r variants
strtok_r(str, delim, &saveptr); /* Reentrant strtok */
localtime_r(timer, &result); /* Reentrant localtime */
Strategy 3: Don't call from handlers The simplest solution—keep handlers minimal.
errno is global (or thread-local). If your handler calls a function that sets errno, it may overwrite the errno from an interrupted syscall in main code. Solution: Save and restore errno at handler start/end: int saved_errno = errno; ... errno = saved_errno;
The complexity of async-signal-safety leads to clear best practices. Following these patterns will save you from subtle bugs.
The safest and most recommended approach: handlers do almost nothing.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
#include <signal.h>#include <unistd.h>#include <stdio.h>#include <errno.h> /* The minimal safe pattern - handler only sets a flag */volatile sig_atomic_t got_sigterm = 0;volatile sig_atomic_t got_sigint = 0;volatile sig_atomic_t got_sighup = 0; void signal_handler(int sig) { int saved_errno = errno; /* Save errno in case interrupted a syscall */ switch (sig) { case SIGTERM: got_sigterm = 1; break; case SIGINT: got_sigint = 1; break; case SIGHUP: got_sighup = 1; break; } errno = saved_errno; /* Restore errno */} void install_signal_handlers() { struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); /* Block other handled signals during any handler */ sigaddset(&sa.sa_mask, SIGTERM); sigaddset(&sa.sa_mask, SIGINT); sigaddset(&sa.sa_mask, SIGHUP); sa.sa_flags = SA_RESTART; /* Auto-restart syscalls */ sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGHUP, &sa, NULL);} int main() { install_signal_handlers(); printf("Server running (PID %d)\n", getpid()); while (1) { /* Check flags and handle appropriately */ if (got_sigterm || got_sigint) { printf("Shutdown signal received, cleaning up...\n"); /* Safe cleanup here - not in handler! */ break; } if (got_sighup) { got_sighup = 0; /* Reset flag */ printf("SIGHUP received, reloading configuration...\n"); /* Reload config here - safe */ } /* Normal work */ sleep(1); } printf("Shutdown complete.\n"); return 0;}For event-driven programs using select() or poll(), the self-pipe trick integrates signals into the event loop:
This converts asynchronous signals into synchronous events you can handle safely.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
#include <signal.h>#include <unistd.h>#include <fcntl.h>#include <stdio.h>#include <sys/select.h>#include <errno.h> static int signal_pipe[2]; /* [0] = read, [1] = write */ void signal_handler(int sig) { int saved_errno = errno; char s = sig; /* Write signal number as byte */ /* write() is async-signal-safe */ write(signal_pipe[1], &s, 1); /* Ignore errors - best effort */ errno = saved_errno;} void setup_signal_pipe() { pipe(signal_pipe); /* Make non-blocking to prevent handler from blocking */ fcntl(signal_pipe[0], F_SETFL, O_NONBLOCK); fcntl(signal_pipe[1], F_SETFL, O_NONBLOCK);} void event_loop() { fd_set read_fds; int max_fd = signal_pipe[0]; while (1) { FD_ZERO(&read_fds); FD_SET(signal_pipe[0], &read_fds); /* Add other fds here: sockets, timers, etc. */ /* select() will return when signal pipe is readable */ int ready = select(max_fd + 1, &read_fds, NULL, NULL, NULL); if (ready == -1 && errno == EINTR) { continue; /* Interrupted by signal, loop will check pipe */ } if (FD_ISSET(signal_pipe[0], &read_fds)) { char sig; while (read(signal_pipe[0], &sig, 1) > 0) { /* Handle signal SAFELY here - not in handler context */ printf("Received signal %d\n", (int)sig); if (sig == SIGTERM || sig == SIGINT) { printf("Initiating shutdown...\n"); return; } } } /* Handle other fd events here */ }} int main() { struct sigaction sa; setup_signal_pipe(); sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; /* Don't use SA_RESTART - we WANT EINTR */ sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGHUP, &sa, NULL); printf("Event loop running (PID %d)\n", getpid()); event_loop(); printf("Event loop exited normally\n"); return 0;}Linux provides signalfd() which creates a file descriptor for signals—no handler needed at all:
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
/* Block signals so they're delivered via signalfd */
sigprocmask(SIG_BLOCK, &mask, NULL);
/* Create fd that receives these signals */
int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
/* Now use sfd with select/poll/epoll like any fd */
struct signalfd_siginfo info;
read(sfd, &info, sizeof(info)); /* Receive signal info */
This is the cleanest approach for event-loop programs on Linux—signals become just another event source.
There's a subtle race condition when installing handlers: if a signal arrives between setting up sa_mask and calling sigaction(), your handler may run without intended protections.
#include <signal.h>
void setup_handlers_safely() {
sigset_t block_all, old_mask;
struct sigaction sa;
/* Block ALL signals during setup */
sigfillset(&block_all);
sigprocmask(SIG_BLOCK, &block_all, &old_mask);
/* Now install handlers - signals can't interrupt us */
sa.sa_handler = my_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
/* Restore original mask - now handlers are ready */
sigprocmask(SIG_SETMASK, &old_mask, NULL);
}
The sa_mask field specifies which signals to block while this handler runs:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
#include <signal.h>#include <stdio.h>#include <unistd.h> volatile sig_atomic_t in_sigterm_handler = 0; void sigterm_handler(int sig) { in_sigterm_handler = 1; const char msg[] = "SIGTERM handler running...\n"; write(STDERR_FILENO, msg, sizeof(msg) - 1); /* Simulate slow cleanup */ sleep(5); const char done[] = "SIGTERM handler done.\n"; write(STDERR_FILENO, done, sizeof(done) - 1); in_sigterm_handler = 0;} void sigint_handler(int sig) { const char msg[] = "SIGINT received!\n"; write(STDERR_FILENO, msg, sizeof(msg) - 1); if (in_sigterm_handler) { const char intr[] = "(I interrupted SIGTERM handler!)\n"; write(STDERR_FILENO, intr, sizeof(intr) - 1); }} int main() { struct sigaction sa_term, sa_int; /* SIGTERM handler - blocks SIGINT during execution */ sa_term.sa_handler = sigterm_handler; sigemptyset(&sa_term.sa_mask); sigaddset(&sa_term.sa_mask, SIGINT); /* Block SIGINT during SIGTERM */ sa_term.sa_flags = 0; sigaction(SIGTERM, &sa_term, NULL); /* SIGINT handler - doesn't block SIGTERM */ sa_int.sa_handler = sigint_handler; sigemptyset(&sa_int.sa_mask); sa_int.sa_flags = 0; sigaction(SIGINT, &sa_int, NULL); printf("PID: %d\n", getpid()); printf("Try: kill -TERM %d, then press Ctrl+C during handler\n", getpid()); printf("SIGINT will be blocked until SIGTERM handler completes.\n"); while (1) { pause(); /* Wait for any signal */ } return 0;}A common pattern: when handling any termination signal (SIGTERM, SIGINT, SIGQUIT), block all of them in sa_mask. This ensures your cleanup logic completes without interruption from other termination requests. The pending signals will be delivered after the handler returns.
By default, signal handlers run on the same stack as the interrupted code. This creates a problem: if the stack overflows, you get SIGSEGV, but the handler for SIGSEGV also needs stack space. Infinite loop or crash.
POSIX provides alternate signal stacks—separate memory regions for handler execution:
#include <signal.h>
#include <stdlib.h>
void setup_alternate_stack() {
stack_t ss;
/* Allocate stack - SIGSTKSZ is minimum recommended size */
ss.ss_sp = malloc(SIGSTKSZ);
ss.ss_size = SIGSTKSZ;
ss.ss_flags = 0;
if (sigaltstack(&ss, NULL) == -1) {
perror("sigaltstack");
}
}
Then install handlers with SA_ONSTACK:
struct sigaction sa;
sa.sa_handler = handler;
sa.sa_flags = SA_ONSTACK; /* Use alternate stack */
sigaction(SIGSEGV, &sa, NULL);
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h> /* Handler that can run even during stack overflow */void sigsegv_handler(int sig, siginfo_t *info, void *context) { const char msg[] = "\nStack overflow detected!\n" "Handler running on alternate stack.\n" "Aborting...\n"; write(STDERR_FILENO, msg, sizeof(msg) - 1); _exit(1);} void setup_alternate_stack() { static char alt_stack[SIGSTKSZ]; stack_t ss; ss.ss_sp = alt_stack; ss.ss_size = SIGSTKSZ; ss.ss_flags = 0; if (sigaltstack(&ss, NULL) == -1) { perror("sigaltstack"); exit(1); } printf("Alternate stack configured: %zu bytes at %p\n", ss.ss_size, ss.ss_sp);} void install_sigsegv_handler() { struct sigaction sa; sa.sa_sigaction = sigsegv_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_ONSTACK | SA_SIGINFO; /* SA_ONSTACK is key */ if (sigaction(SIGSEGV, &sa, NULL) == -1) { perror("sigaction"); exit(1); }} void cause_stack_overflow() { char big_array[1024 * 1024]; /* 1MB on stack - will overflow */ volatile char x = big_array[0]; cause_stack_overflow(); /* Recursive call */ (void)x;} int main() { setup_alternate_stack(); install_sigsegv_handler(); printf("About to cause stack overflow...\n"); cause_stack_overflow(); printf("This line will never execute\n"); return 0;}Use sigaltstack() when: (1) Handling SIGSEGV to detect stack overflow, (2) Your application has deep recursion or large stack allocations, (3) You need memory debugging that survives stack corruption, (4) Writing robust system software where crash handlers must always work. Most applications don't need it, but it's essential for reliable crash reporting.
Signal handlers are powerful but dangerous. Proper implementation requires understanding async-signal-safety and following disciplined patterns. Let's consolidate the key lessons:
What's Next:
With handler installation and implementation covered, the next page explores sending signals—how to use kill(), sigqueue(), and related interfaces to communicate with and control other processes.
You now have the knowledge to implement correct signal handlers. Remember: the best handler is the simplest handler. Set a flag, return, and let synchronous code do the heavy lifting. This pattern will save you from countless debugging sessions.