Loading content...
When you press Ctrl+C to stop a runaway process, you're sending SIGINT. When the kill command terminates an unresponsive daemon, it's sending SIGTERM. When a segmentation fault crashes your program, the kernel delivers SIGSEGV. These aren't arbitrary names—they're the vocabulary of process communication in UNIX-like systems.
Just as a doctor must know the names and functions of human organs, a systems programmer must know the names and purposes of standard signals. This knowledge enables you to:
This page catalogs the signals you'll encounter most frequently, explaining not just what they are but when you'll see them and why they exist.
By the end of this page, you will have a working knowledge of 25+ standard signals, organized by function. You'll understand their default behaviors, common generation sources, and best practices for handling them in production systems.
The most common use of signals is to terminate processes—gracefully or forcefully. Understanding the hierarchy of termination signals is essential for proper process management.
Default action: Terminate
SIGTERM is the "gentleman's request" to terminate. It says: "Please shut down cleanly at your earliest convenience." Key characteristics:
kill command: Running kill <pid> sends SIGTERM by defaultBest practice: Always handle SIGTERM for long-running processes. Your handler should:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h> volatile sig_atomic_t shutdown_requested = 0; void sigterm_handler(int sig) { shutdown_requested = 1; /* * Do NOT call printf, malloc, exit, or other non-async-signal-safe * functions here. Only set flags and let main loop handle cleanup. */} void cleanup() { printf("Performing graceful shutdown..."); /* Flush buffers, close connections, release locks */ printf("Cleanup complete. Exiting.");} int main() { struct sigaction sa; sa.sa_handler = sigterm_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); /* Also handle Ctrl+C gracefully */ printf("Server running (PID %d). Send SIGTERM to shutdown gracefully.", getpid()); /* Main work loop */ while (!shutdown_requested) { printf("Working..."); sleep(2); /* Simulate work */ } cleanup(); return 0;}Default action: Terminate (cannot be caught, blocked, or ignored)
SIGKILL is the nuclear option. The kernel terminates the process immediately—no handler runs, no cleanup occurs. Use cases:
Warning: SIGKILL should be a last resort. Using it routinely causes:
Convention: Send SIGTERM first, wait a grace period (e.g., 10 seconds), then SIGKILL if necessary.
Default action: Terminate
SIGINT is generated by Ctrl+C in the terminal. It's intended for interactive interrupt—user wants to stop what's happening NOW. Characteristics:
Handler strategy: SIGINT handlers should be quick. The user is waiting. If cleanup is lengthy, acknowledge receipt immediately and timeout if needed.
Default action: Terminate + core dump
SIGQUIT is generated by Ctrl+\ (backslash). It's a stronger interrupt request with diagnostic intent—"terminate, but leave a core dump for debugging." Use when SIGINT doesn't help and you want to investigate why.
Default action: Terminate
SIGHUP originally meant "terminal hung up" (modem disconnected). Modern uses:
# Common daemon pattern: reload config without restart
kill -HUP $(cat /var/run/nginx.pid)
This dual meaning (terminate vs. reload) depends on context—daemons that detach from terminals typically interpret SIGHUP as reload.
| Signal | Number | Default | Generated By | Typical Use |
|---|---|---|---|---|
| SIGTERM | 15 | Terminate | kill <pid>, system shutdown | Graceful termination request |
| SIGKILL | 9 | Terminate | kill -9 <pid> | Forced termination (last resort) |
| SIGINT | 2 | Terminate | Ctrl+C | Interactive interrupt |
| SIGQUIT | 3 | Terminate + core | Ctrl+\ | Debug termination |
| SIGHUP | 1 | Terminate | Terminal close, shell logout | Reload config (daemons) |
Never use kill -9 as your first attempt. Always SIGTERM first, wait, then SIGKILL. Container orchestrators (Kubernetes, Docker) follow this pattern: SIGTERM with a grace period (default 30s), then SIGKILL. Applications that ignore SIGTERM cause unnecessary forced terminations and potential data loss.
Error signals indicate that something went wrong during program execution. They are typically synchronous—generated by the process's own faulty actions—and often indicate bugs that need fixing rather than conditions to handle gracefully.
Default action: Terminate + core dump
SIGSEGV is the most famous (infamous?) signal—the dreaded "segmentation fault." It indicates invalid memory access:
Can you catch SIGSEGV? Technically yes, but recovery is difficult:
siglongjmp) may work but leaves state unclearPractical approach: Install SIGSEGV handler for logging/diagnostics only, then terminate. Use address sanitizers (ASan) during development to catch these bugs early.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <execinfo.h> /* For backtrace - Linux/glibc */ void sigsegv_handler(int sig, siginfo_t *info, void *context) { void *array[50]; int size; fprintf(stderr, "=== SEGMENTATION FAULT ==="); fprintf(stderr, "Signal: %d", sig); fprintf(stderr, "Fault address: %p", info->si_addr); fprintf(stderr, "Backtrace:"); /* Get backtrace - note: not async-signal-safe, but we're crashing anyway */ size = backtrace(array, 50); backtrace_symbols_fd(array, size, STDERR_FILENO); fprintf(stderr, "Core dump will be generated if enabled."); /* * Re-raise with default handler to generate core dump. * This is the recommended pattern for diagnostic handlers. */ signal(SIGSEGV, SIG_DFL); raise(SIGSEGV);} int main() { struct sigaction sa; sa.sa_sigaction = sigsegv_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; /* Use sa_sigaction, not sa_handler */ sigaction(SIGSEGV, &sa, NULL); /* Trigger SIGSEGV */ int *ptr = NULL; *ptr = 42; /* Crash here */ return 0;} /* * Compile: gcc -rdynamic sigsegv_diagnostic.c -o segv_demo * The -rdynamic flag includes symbol names in backtrace. */Default action: Terminate + core dump
Despite the name, SIGFPE covers all arithmetic exceptions, not just floating-point:
Note: By default, IEEE 754 floating-point operations do NOT raise SIGFPE—they produce special values (Inf, NaN). SIGFPE for FP requires enabling traps via feenableexcept() (Linux/glibc).
Default action: Terminate + core dump
SIGBUS indicates a bus error—hardware-level memory access problem:
mmap)Difference from SIGSEGV: SIGSEGV = valid address, no permission. SIGBUS = physical access failure.
Default action: Terminate + core dump
SIGILL indicates the CPU encountered an instruction it cannot execute:
This signal is rare in normal operation—it usually indicates severe corruption or hacking attempts.
Default action: Terminate + core dump
SIGABRT is generated programmatically when a process calls abort(). Uses:
assert() failure—the assertion macro calls abort()Unlike hardware exception signals, SIGABRT is intentional—the program is saying "I detected something wrong and cannot continue."
| Signal | Number | Cause | Common Triggering Bugs |
|---|---|---|---|
| SIGSEGV | 11 | Invalid memory access | NULL dereference, buffer overflow, stack overflow |
| SIGFPE | 8 | Arithmetic exception | Division by zero, integer overflow |
| SIGBUS | 7 | Bus/alignment error | Misaligned access, mmap beyond EOF |
| SIGILL | 4 | Illegal instruction | Binary corruption, wrong architecture |
| SIGABRT | 6 | Abort called | assert() failure, double-free |
| SIGTRAP | 5 | Trace/breakpoint trap | Debugger breakpoint, debug trap instruction |
| SIGSYS | 31 | Bad system call | Invalid syscall number, seccomp violation |
When a process is killed by a signal, its exit status encodes the signal number. The convention is: exit_status = 128 + signal_number. So SIGSEGV (11) gives exit code 139, SIGKILL (9) gives 137. Shell scripts can detect this: if [ $? -gt 128 ]; then echo 'killed by signal'; fi
Job control is the shell's mechanism for managing multiple processes—running jobs in background, bringing them to foreground, suspending and resuming. This mechanism is built on a set of dedicated signals.
Default action: Stop (cannot be caught, blocked, or ignored)
SIGSTOP unconditionally stops (pauses) a process. Like SIGKILL for termination, SIGSTOP cannot be overridden—it guarantees the process will stop. The process remains in memory, in stopped state, until resumed with SIGCONT.
Use cases:
kill -STOP <pid> to freeze a processDefault action: Stop
SIGTSTP is the catchable version of SIGSTOP, generated by Ctrl+Z. Unlike SIGSTOP:
Handler pattern for SIGTSTP:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
#include <signal.h>#include <stdio.h>#include <unistd.h>#include <termios.h> struct termios original_termios; void sigtstp_handler(int sig) { /* Restore terminal settings before stopping */ tcsetattr(STDIN_FILENO, TCSANOW, &original_termios); /* * To actually stop, we must: * 1. Reset SIGTSTP to default handler * 2. Raise SIGTSTP to stop ourselves * 3. When continued, re-install our handler */ signal(SIGTSTP, SIG_DFL); raise(SIGTSTP); /* Now we actually stop */ /* Execution resumes here after SIGCONT */ signal(SIGTSTP, sigtstp_handler); /* Re-install handler */ /* Restore our custom terminal settings */ /* (would restore custom settings here) */ printf("Resumed!");} int main() { tcgetattr(STDIN_FILENO, &original_termios); struct sigaction sa; sa.sa_handler = sigtstp_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGTSTP, &sa, NULL); printf("Press Ctrl+Z to suspend..."); while (1) { printf("Working..."); sleep(1); } return 0;}Default action: Continue (if stopped); ignore (if running)
SIGCONT resumes a stopped process. Special characteristics:
Shell job control:
fg %1 # Bring job 1 to foreground (sends SIGCONT)
bg %1 # Resume job 1 in background (sends SIGCONT)
Default action: Stop
These signals prevent background processes from interfering with terminal I/O:
stty tostop is set)This prevents confusing scenarios where multiple processes fight for terminal input/output.
Default action: Ignore
SIGCHLD notifies parent processes when child state changes:
This signal is critical for proper process management. Without handling SIGCHLD properly, terminated children become zombies.
| Signal | Number | Action | Catchable? | Purpose |
|---|---|---|---|---|
| SIGSTOP | 19 | Stop | No | Unconditional stop |
| SIGTSTP | 20 | Stop | Yes | Terminal stop (Ctrl+Z) |
| SIGCONT | 18 | Continue | Yes | Resume stopped process |
| SIGTTIN | 21 | Stop | Yes | Background read from terminal |
| SIGTTOU | 22 | Stop | Yes | Background write to terminal |
| SIGCHLD | 17 | Ignore | Yes | Child process status changed |
To prevent zombie children: either (1) call wait()/waitpid() periodically, (2) install SIGCHLD handler that calls waitpid() with WNOHANG in a loop, or (3) set SIGCHLD disposition to SIG_IGN (on systems that support this—children are auto-reaped). The third option is simplest but loses exit status information.
Timer signals provide a mechanism for time-based notifications. They're used for implementing timeouts, periodic tasks, and monitoring resource consumption.
Default action: Terminate
SIGALRM is delivered when a timer set by alarm() or the ITIMER_REAL interval timer expires. It measures real (wall-clock) time.
Use cases:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <setjmp.h> static sigjmp_buf timeout_jmp;volatile sig_atomic_t timed_out = 0; void alarm_handler(int sig) { timed_out = 1; siglongjmp(timeout_jmp, 1);} /* * Read with timeout using SIGALRM. * Returns: bytes read, 0 on timeout, -1 on error. */ssize_t read_with_timeout(int fd, void *buf, size_t count, unsigned int seconds) { struct sigaction sa, old_sa; ssize_t result; timed_out = 0; /* Install alarm handler */ sa.sa_handler = alarm_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; /* No SA_RESTART - we want EINTR */ sigaction(SIGALRM, &sa, &old_sa); if (sigsetjmp(timeout_jmp, 1) == 0) { /* Normal path */ alarm(seconds); result = read(fd, buf, count); alarm(0); /* Cancel alarm */ } else { /* Returned via longjmp from alarm handler */ result = 0; /* Indicate timeout */ } /* Restore original handler */ sigaction(SIGALRM, &old_sa, NULL); return result;} int main() { char buffer[256]; printf("Enter input within 5 seconds: "); fflush(stdout); ssize_t bytes = read_with_timeout(STDIN_FILENO, buffer, sizeof(buffer) - 1, 5); if (bytes > 0) { buffer[bytes] = '\0'; printf("You entered: %s", buffer); } else if (timed_out) { printf("Timeout! No input received."); } else { perror("read"); } return 0;}Default action: Terminate
SIGVTALRM measures virtual time—only the time the process spends in user-mode execution. Time spent blocked or in kernel mode doesn't count. Used for profiling CPU-intensive code.
struct itimerval timer;
timer.it_value.tv_sec = 1; /* First alarm in 1 second of CPU time */
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0; /* No repeat */
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_VIRTUAL, &timer, NULL);
Default action: Terminate
SIGPROF measures profiling time—user plus kernel time spent executing on behalf of the process. More comprehensive than SIGVTALRM for profiling because it includes system calls.
| Timer | Signal | Measures | Use Case |
|---|---|---|---|
| ITIMER_REAL | SIGALRM | Wall-clock time | Timeouts, scheduling |
| ITIMER_VIRTUAL | SIGVTALRM | User CPU time | User-mode profiling |
| ITIMER_PROF | SIGPROF | User + Kernel time | Full CPU profiling |
The classic alarm() and setitimer() interfaces have limitations:
Modern Linux provides better alternatives:
timerfd (timerfd_create, timerfd_settime):
select(), poll(), epoll()POSIX Timers (timer_create, timer_settime):
For new code, prefer timerfd (Linux) or kqueue (BSD/macOS) over signal-based timers. File-descriptor-based timers integrate cleanly with event loops, avoid signal handler complexity, and scale to many timers. Use SIGALRM only for simple timeout scenarios or legacy compatibility.
Several signals relate to I/O operations, particularly pipes and sockets. These signals simplify error handling in process communication.
Default action: Terminate
SIGPIPE is delivered when a process writes to a pipe (or socket) with no readers. Without this signal, writes would fail with EPIPE, but the process might not notice until later. SIGPIPE provides immediate notification.
The problem: Default termination is often undesirable. A web server shouldn't crash just because a client disconnected mid-response.
Solutions:
signal(SIGPIPE, SIG_IGN) then handle EPIPE on writesend() with MSG_NOSIGNAL flag (socket-specific)Most production network servers ignore SIGPIPE.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>#include <string.h> /* * Demonstrates proper SIGPIPE handling for network/pipe code. * Ignore SIGPIPE and handle EPIPE explicitly. */ ssize_t safe_write(int fd, const void *buf, size_t count) { ssize_t result; result = write(fd, buf, count); if (result == -1) { if (errno == EPIPE) { /* Peer closed connection - handle gracefully */ fprintf(stderr, "Warning: write to closed pipe/socket"); return -1; } /* Other errors */ perror("write"); return -1; } return result;} int main() { int pipefd[2]; /* * CRITICAL: Ignore SIGPIPE globally for any process doing * network or pipe I/O where peer may disconnect. */ signal(SIGPIPE, SIG_IGN); if (pipe(pipefd) == -1) { perror("pipe"); exit(1); } /* Close read end - creates "broken pipe" condition */ close(pipefd[0]); /* Without SIG_IGN, this would terminate the process */ const char *msg = "Hello, broken pipe!"; ssize_t written = safe_write(pipefd[1], msg, strlen(msg)); if (written == -1) { printf("Write failed as expected (EPIPE), but process survived!"); } close(pipefd[1]); return 0;}Default action: Terminate (historically) or Ignore (modern Linux)
SIGIO provides asynchronous I/O notification—"data is available on this file descriptor." To use:
Caveat: SIGIO is largely obsoleted by select(), poll(), and especially epoll(). These interfaces scale better and don't have signal handling complexity. SIGIO is mainly of historical interest.
Default action: Ignore
SIGURG indicates out-of-band (OOB) data on a socket. OOB data is rarely used—primarily in telnet for interrupt sequences. Most applications ignore this signal entirely.
Default action: Terminate
SIGPOLL (same as SIGIO on Linux) is the System V equivalent. It's generated when an event occurs on a file descriptor marked for async notification. Like SIGIO, it's obsoleted by modern multiplexing APIs.
| Signal | Number | Default | Condition | Practical Handling |
|---|---|---|---|---|
| SIGPIPE | 13 | Terminate | Write to closed pipe/socket | Ignore globally, check EPIPE |
| SIGIO | 29 | Ignore | I/O possible on async fd | Use poll/epoll instead |
| SIGURG | 23 | Ignore | Out-of-band socket data | Rarely needed, ignore |
SIGPIPE exists because pipes were originally used interactively (shell pipelines). If you run cat file | head -1, head exits after one line, and SIGPIPE terminates cat immediately—efficient, no wasted work. But for network servers, this behavior is counterproductive. Always ignore SIGPIPE in daemons and network code.
POSIX reserves specific signals for application-defined purposes and provides additional signals for special conditions.
Default action: Terminate
These signals have no predefined meaning—applications define their semantics. Common uses:
# Common patterns in production
kill -USR1 $(cat /var/run/nginx.pid) # Reopen logs
kill -USR2 $(cat /var/run/app.pid) # Dump stats
Default action: Ignore
SIGWINCH is sent when terminal window dimensions change. Interactive programs (vim, less, htop) handle this to redraw their interface. Non-interactive programs ignore it.
Default action: Terminate (+core for SIGXCPU)
setrlimit(RLIMIT_CPU, ...))setrlimit(RLIMIT_FSIZE, ...))These signals enforce resource quotas set by the process or administrator.
Default action: Terminate
Historically sent when UPS signals imminent power loss. Rarely seen on modern systems with proper power management.
Default action: Terminate
Historically used for math coprocessor stack faults. Obsolete on modern hardware.
As discussed previously, real-time signals (typically 34-64 on Linux) offer:
sigqueue()They're user-definable like SIGUSR1/2 but with richer semantics.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
#include <signal.h>#include <stdio.h>#include <unistd.h> volatile sig_atomic_t debug_mode = 0;volatile sig_atomic_t stats_requested = 0; void sigusr1_handler(int sig) { stats_requested = 1;} void sigusr2_handler(int sig) { debug_mode = !debug_mode; /* Toggle debug mode */} void print_stats() { printf("=== Application Statistics ==="); printf("Debug mode: %s", debug_mode ? "ON" : "OFF"); printf("Uptime: (would show uptime)"); printf("Requests handled: (would show count)"); printf("============================== ");} int main() { struct sigaction sa1, sa2; sa1.sa_handler = sigusr1_handler; sigemptyset(&sa1.sa_mask); sa1.sa_flags = SA_RESTART; sigaction(SIGUSR1, &sa1, NULL); sa2.sa_handler = sigusr2_handler; sigemptyset(&sa2.sa_mask); sa2.sa_flags = SA_RESTART; sigaction(SIGUSR2, &sa2, NULL); printf("Daemon started (PID %d)", getpid()); printf(" SIGUSR1: Print stats"); printf(" SIGUSR2: Toggle debug mode"); while (1) { if (stats_requested) { stats_requested = 0; print_stats(); } if (debug_mode) { printf("[DEBUG] Worker loop iteration"); } sleep(2); /* Simulate work */ } return 0;} /* * Usage: * ./daemon & * kill -USR1 $(pgrep daemon) # Print stats * kill -USR2 $(pgrep daemon) # Toggle debug */Document your signal usage! Users of your daemon should know what SIGUSR1/2 do. Popular conventions: SIGUSR1 for log reopen (works with logrotate), SIGHUP for config reload, SIGUSR2 for debug dump. Real-time signals offer more options if you need many distinct notifications.
For quick reference, here's a comprehensive table of standard POSIX signals. Signal numbers may vary by platform—use symbolic names (SIGTERM, not 15) in code.
| Signal | Default Action | Description | |
|---|---|---|---|
| SIGHUP | 1 | Terminate | Hangup / reload config |
| SIGINT | 2 | Terminate | Interrupt (Ctrl+C) |
| SIGQUIT | 3 | Core dump | Quit (Ctrl+\) |
| SIGILL | 4 | Core dump | Illegal instruction |
| SIGTRAP | 5 | Core dump | Trace/breakpoint trap |
| SIGABRT | 6 | Core dump | Abort (from abort()) |
| SIGBUS | 7 | Core dump | Bus error |
| SIGFPE | 8 | Core dump | Floating-point exception |
| SIGKILL | 9 | Terminate | Kill (uncatchable) |
| SIGUSR1 | 10 | Terminate | User-defined 1 |
| SIGSEGV | 11 | Core dump | Segmentation fault |
| SIGUSR2 | 12 | Terminate | User-defined 2 |
| SIGPIPE | 13 | Terminate | Broken pipe |
| SIGALRM | 14 | Terminate | Alarm clock |
| SIGTERM | 15 | Terminate | Termination request |
| SIGSTKFLT | 16 | Terminate | Stack fault (obsolete) |
| SIGCHLD | 17 | Ignore | Child status changed |
| SIGCONT | 18 | Continue | Continue if stopped |
| SIGSTOP | 19 | Stop | Stop (uncatchable) |
| SIGTSTP | 20 | Stop | Terminal stop (Ctrl+Z) |
| SIGTTIN | 21 | Stop | Background read |
| SIGTTOU | 22 | Stop | Background write |
| SIGURG | 23 | Ignore | Urgent socket data |
| SIGXCPU | 24 | Core dump | CPU time limit |
| SIGXFSZ | 25 | Core dump | File size limit |
| SIGVTALRM | 26 | Terminate | Virtual timer |
| SIGPROF | 27 | Terminate | Profiling timer |
| SIGWINCH | 28 | Ignore | Window size change |
| SIGIO | 29 | Terminate | I/O possible |
| SIGPWR | 30 | Terminate | Power failure |
| SIGSYS | 31 | Core dump | Bad system call |
Signal numbers differ between platforms! SIGUSR1 is 10 on Linux but 30 on macOS. SIGBUS is 7 on Linux but 10 on macOS. Always use symbolic names in code and check with kill -l on your target platform.
Understanding the vocabulary of signals is essential for UNIX systems programming. Let's consolidate the key categories and practices:
What's Next:
Now that you know the signal vocabulary, the next page covers signal handlers—how to install them properly, what you can safely do inside them, and how to avoid the subtle bugs that plague signal-handling code.
You now have a working vocabulary of UNIX/POSIX signals. You can identify signals in error messages, choose appropriate signals for process management, and understand system behavior in failure scenarios. Next, we'll learn how to handle these signals correctly.