Signals are a mechanism used by the operating system to communicate with processes, notifying them of specific events (such as user input, hardware exceptions, or software errors). C language provides signal processing capabilities through the POSIX signal interface (signal.h), suitable for process control, error handling, user interaction, and other scenarios.
Signal Basics
The Nature and Classification of Signals
A signal is an asynchronous event notification mechanism. When a process receives a signal, it pauses its current execution and executes the corresponding signal handler (if registered). The essence of a signal is a flag bit in the process control block (PCB), which is triggered and scheduled by the kernel.
- Signal: A mechanism used by the operating system to notify a process that an event has occurred.
- Signal Types: Different signals serve different purposes, such as
SIGINTfor interrupt signals andSIGTERMfor termination signals. - Signal Handling: Custom handler functions can be registered to respond to specific signals.
Common Signal Types (Partial List):
| Signal Name | Value | Description |
|---|---|---|
SIGINT | 2 | User pressed Ctrl+C (interrupt process) |
SIGTERM | 15 | Graceful termination (default signal sent by kill) |
SIGSEGV | 11 | Segmentation fault (invalid memory access) |
SIGKILL | 9 | Force termination (cannot be caught or ignored) |
SIGALRM | 14 | Alarm clock signal (timer expired) |
SIGUSR1 | 10 | User-defined signal 1 |
SIGUSR2 | 12 | User-defined signal 2 |
Signal Generation and Delivery
- Hardware-triggered: Such as division by zero (
SIGFPE), invalid memory access (SIGSEGV). - Software-triggered: Such as user pressing
Ctrl+C(SIGINT), callingkill()to send a signal. - Kernel-triggered: Such as timer expiration (
SIGALRM), child process termination (SIGCHLD).
Signal delivery process:
- Event occurs → Kernel sets the signal flag in the process.
- During process scheduling, the kernel checks the signal flag → If there are pending signals, pause the current process.
- Execute the signal handler (if registered) → Resume process execution.
Signal Handlers: Basics and Limitations
signal() Function: Quick Handler Registration
signal() is the most basic signal registration interface, with the following prototype:
#include <signal.h>
typedef void (*sighandler_t)(int); // Signal handler function type
sighandler_t signal(int sig, sighandler_t handler);
Parameter Explanation:
sig: The signal number to handle (e.g.,SIGINT).handler: Pointer to the signal handler function (or special valuesSIG_IGNto ignore,SIG_DFLto restore default behavior).
Example: Handling SIGINT (Ctrl+C)
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handle_sigint(int sig) {
printf("\nReceived SIGINT (Ctrl+C), preparing to exit...\n");
_exit(0); // Safe exit (avoid calling non-async-safe functions)
}
int main() {
// Register SIGINT handler
if (signal(SIGINT, handle_sigint) == SIG_ERR) {
perror("signal registration failed");
return 1;
}
printf("Running... Press Ctrl+C to exit\n");
while (1) {
sleep(1); // Simulate long-running process
}
return 0;
}
Compile and Run:
gcc -o signal_demo signal_demo.c
./signal_demo
Output:
Running... Press Ctrl+C to exit
^C
Received SIGINT (Ctrl+C), preparing to exit...
Limitations of signal()
- Non-reentrant: If the same signal is received again during handler execution, it may cause recursion or state corruption.
- Signal Masking: By default, the same signal is not automatically blocked during handling (requires manual setup).
- Platform Differences: Different systems may implement
signal()differently (e.g., BSD vs. System V).
Signal Handling Example
Here is a simple signal handling example that demonstrates how to set up a handler for the SIGINT signal (typically triggered by pressing Ctrl+C):
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void signal_handler(int signum) {
if (signum == SIGINT) {
printf("Caught SIGINT. Exiting...\n");
exit(0);
}
}
int main() {
// Register signal handler
signal(SIGINT, signal_handler);
// Main loop
while (1) {
printf("Running...\n");
sleep(1); // Pause for one second
}
return 0;
}
Advanced Signal Handling: sigaction
sigaction Function: More Flexible Signal Control
sigaction is the POSIX-standard signal registration interface that addresses the limitations of signal(), offering finer control (such as signal masking and flag settings). Its prototype is:
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
struct sigaction Structure:
struct sigaction {
void (*sa_handler)(int); // Signal handler (same as signal())
void (*sa_sigaction)(int, siginfo_t *, void *); // Handler with extra info
sigset_t sa_mask; // Signals to block during handler execution
int sa_flags; // Flags (e.g., SA_RESTART, SA_NODEFER)
void (*sa_restorer)(void); // Restoration function (deprecated, use sigreturn())
};
Key Field Explanations:
sa_mask: Signals to automatically block during handler execution (prevents race conditions).sa_flags: Flags controlling signal handling behavior:SA_RESTART: Automatically restart interrupted system calls (e.g.,read,write).SA_NODEFER: Do not block the current signal during handling (blocked by default).SA_SIGINFO: Usesa_sigactioninstead ofsa_handler, passing additional signal info (e.g., sender PID, signal code).
Example: Using sigaction to Handle SIGSEGV (Segmentation Fault)
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/ucontext.h> // For ucontext_t (optional)
void handle_segv(int sig, siginfo_t *info, void *ucontext) {
printf("\nReceived SIGSEGV (segmentation fault), fault address: %p\n", info->si_addr);
printf("Attempting recovery...\n");
_exit(1); // Force exit (segmentation faults are usually unrecoverable)
}
int main() {
struct sigaction sa;
sa.sa_sigaction = handle_segv; // Use handler with extra info
sigemptyset(&sa.sa_mask); // Initially block no signals
sa.sa_flags = SA_SIGINFO; // Enable sa_sigaction
// Register SIGSEGV handler
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
perror("sigaction registration failed");
return 1;
}
printf("Running... Attempting invalid memory access\n");
int *ptr = NULL;
*ptr = 10; // Trigger segmentation fault (SIGSEGV)
return 0;
}
Compile and Run:
gcc -o segv_demo segv_demo.c
./segv_demo
Output:
Running... Attempting invalid memory access
Received SIGSEGV (segmentation fault), fault address: (nil)
Attempting recovery...
Signal Sets
Purpose of Signal Sets
A signal set (sigset_t) is a collection of signals used for batch management of signal blocking, waiting, or handling. Common operations include:
- Blocking a group of signals (to prevent interruption during critical sections).
- Waiting for any signal in a group (
sigsuspend). - Sending a group of signals to a process (
killsupports this).
Signal Set Operation Functions
| Function | Description |
|---|---|
sigemptyset(sigset_t *set) | Initialize an empty signal set |
sigfillset(sigset_t *set) | Initialize a full signal set (all signals) |
sigaddset(sigset_t *set, int sig) | Add signal sig to the set |
sigdelset(sigset_t *set, int sig) | Remove signal sig from the set |
sigismember(const sigset_t *set, int sig) | Check if sig is in the set |
Using Signal Sets
Here is an example using signal sets to manage signal masks:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void signal_handler(int signum) {
if (signum == SIGINT) {
printf("Caught SIGINT. Exiting...\n");
exit(0);
}
}
int main() {
struct sigaction sa;
sigset_t sigset;
// Initialize signal set
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
// Set signal handler
sa.sa_handler = signal_handler;
sa.sa_mask = sigset; // Set signal mask
sa.sa_flags = 0;
// Register signal handler
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
// Block SIGINT
if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
// Main loop
while (1) {
printf("Running...\n");
sleep(1); // Pause for one second
// Unblock SIGINT
if (sigprocmask(SIG_UNBLOCK, &sigset, NULL) == -1) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
}
return 0;
}
Blocking SIGINT During Critical Operations
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handle_sigint(int sig) {
printf("\nReceived SIGINT, but operation cannot be interrupted\n");
}
int main() {
struct sigaction sa;
sa.sa_handler = handle_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigset_t block_set;
sigemptyset(&block_set);
sigaddset(&block_set, SIGINT); // Block SIGINT
printf("Starting critical operation (SIGINT blocked)...\n");
sigprocmask(SIG_BLOCK, &block_set, NULL); // Block SIGINT
// Simulate critical operation (e.g., file write)
sleep(3);
sigprocmask(SIG_UNBLOCK, &block_set, NULL); // Unblock SIGINT
printf("Critical operation complete, resuming SIGINT handling\n");
while (1) {
sleep(1);
}
return 0;
}
Output:
Starting critical operation (SIGINT blocked)...
^C # Ctrl+C is blocked, no response
Critical operation complete, resuming SIGINT handling
^C
Received SIGINT, but operation cannot be interrupted
Advanced Usage of Signal Sets
Signal sets can be used to mask or handle multiple signals. The following example shows how to handle multiple signals:
Example: Handling Multiple Signals
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void signal_handler(int signum) {
switch (signum) {
case SIGINT:
printf("Caught SIGINT. Exiting...\n");
break;
case SIGTERM:
printf("Caught SIGTERM. Exiting...\n");
break;
default:
printf("Caught signal %d. Ignoring...\n", signum);
break;
}
exit(0);
}
int main() {
struct sigaction sa;
sigset_t sigset;
// Initialize signal set
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGTERM);
// Set signal handler
sa.sa_handler = signal_handler;
sa.sa_mask = sigset; // Set signal mask
sa.sa_flags = 0;
// Register signal handlers
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
// Main loop
while (1) {
printf("Running...\n");
sleep(1); // Pause for one second
}
return 0;
}
Signal Stack
Why Use a Signal Stack?
By default, signal handlers use the process’s main stack. If the main stack is corrupted (e.g., due to stack overflow or segmentation fault), the handler may fail to execute. An alternative signal stack provides a backup stack for executing handlers when the main stack is unusable.
sigaltstack Function: Set Alternative Stack
#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *old_ss);
stack_t Structure:
typedef struct {
void *ss_sp; // Stack pointer (must point to writable memory)
int ss_flags; // Flags (e.g., SS_ONSTACK: currently using alt stack)
size_t ss_size; // Stack size (at least SIGSTKSZ, typically 8MB)
} stack_t;
Example: Setting a Signal Stack
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void signal_handler(int signum) {
if (signum == SIGINT) {
printf("Caught SIGINT. Exiting...\n");
exit(0);
}
}
int main() {
struct sigaction sa;
stack_t ss;
// Initialize signal stack
ss.ss_sp = malloc(SIGSTKSZ); // Allocate stack space
ss.ss_size = SIGSTKSZ;
ss.ss_flags = 0;
// Set signal stack
if (sigaltstack(&ss, NULL) == -1) {
perror("sigaltstack");
exit(EXIT_FAILURE);
}
// Set signal handler
sa.sa_handler = signal_handler;
sa.sa_mask = sigemptyset(); // Set signal mask to empty
sa.sa_flags = SA_ONSTACK; // Use signal stack
// Register signal handler
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
// Main loop
while (1) {
printf("Running...\n");
sleep(1); // Pause for one second
}
free(ss.ss_sp); // Free signal stack
return 0;
}
Example: Using Alternative Stack for SIGSEGV
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#define STACK_SIZE (SIGSTKSZ * 2) // Alternative stack size (at least SIGSTKSZ)
void handle_segv(int sig) {
printf("\nHandling SIGSEGV on alternative stack\n");
_exit(1);
}
int main() {
// Allocate alternative stack memory (must be writable and executable)
char *alt_stack = malloc(STACK_SIZE);
if (!alt_stack) {
perror("malloc failed");
return 1;
}
stack_t ss;
ss.ss_sp = alt_stack;
ss.ss_flags = 0;
ss.ss_size = STACK_SIZE;
// Set alternative stack
if (sigaltstack(&ss, NULL) == -1) {
perror("sigaltstack setup failed");
free(alt_stack);
return 1;
}
// Register SIGSEGV handler
struct sigaction sa;
sa.sa_handler = handle_segv;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGSEGV, &sa, NULL);
printf("Running... Triggering segmentation fault (using alt stack)\n");
int *ptr = NULL;
*ptr = 10; // Trigger SIGSEGV (handler runs on alt stack)
free(alt_stack);
return 0;
}
Compile and Run:
gcc -o alt_stack_demo alt_stack_demo.c
./alt_stack_demo
Output:
Running... Triggering segmentation fault (using alt stack)
Handling SIGSEGV on alternative stack
Signal-Safe Functions and Precautions
Async-Signal-Safe Functions
Signal handlers execute asynchronously (can interrupt the main program at any time), so only async-signal-safe functions should be called (they do not modify global state or call unsafe functions). Common safe functions include:
| Function | Description |
|---|---|
write() | Write to a file descriptor (e.g., STDOUT_FILENO) |
_exit() | Terminate process (no cleanup) |
sigprocmask() | Modify signal mask |
sigsuspend() | Wait for signals |
getpid() | Get process ID |
Prohibited unsafe functions: printf, malloc, free, sleep, system, etc. (may access shared resources or modify global state).
Signal-Safe Function Example
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void signal_handler(int signum) {
if (signum == SIGINT) {
printf("Caught SIGINT. Exiting...\n");
_exit(0); // Use _exit() instead of exit()
}
}
int main() {
struct sigaction sa;
// Set signal handler
sa.sa_handler = signal_handler;
sa.sa_mask = sigemptyset(); // Set signal mask to empty
sa.sa_flags = 0;
// Register signal handler
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
// Main loop
while (1) {
printf("Running...\n");
sleep(1); // Pause for one second
}
return 0;
}
Signal Handling Precautions
- Minimize handler logic: Handlers should only perform essential operations (e.g., set flags, clean resources), avoiding complex logic.
- Avoid race conditions: Use
sa_maskto block signals that might interrupt the handler. - Proper state restoration: If the handler modifies global variables, the main program must check and restore state.
- Handling
SIGKILLandSIGSTOP: These cannot be caught or ignored (SIGKILLforces termination,SIGSTOPpauses the process). - Multithreaded signal handling: Signals are delivered to all threads by default; use
pthread_sigmaskto control per-thread signal masks.
Signal Synchronization and Delivery
sigwait: Synchronous Signal Waiting
The sigwait function synchronously waits for signals, ideal for multithreaded environments to ensure signals are handled by a specific thread. Prototype:
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);
Thread Synchronously Waiting for SIGUSR1
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
sigset_t sig_set;
void* wait_thread(void *arg) {
int sig;
while (1) {
sigwait(&sig_set, &sig); // Block and wait for signal
if (sig == SIGUSR1) {
printf("Thread %ld received SIGUSR1\n", (long)pthread_self());
}
}
return NULL;
}
int main() {
sigemptyset(&sig_set);
sigaddset(&sig_set, SIGUSR1);
// Block SIGUSR1 in main thread (prevent main thread from receiving it)
pthread_sigmask(SIG_BLOCK, &sig_set, NULL);
// Create waiting thread
pthread_t tid;
pthread_create(&tid, NULL, wait_thread, NULL);
// Main thread sends SIGUSR1 to itself
sleep(1);
kill(getpid(), SIGUSR1);
pthread_join(tid, NULL);
return 0;
}
Output:
Thread 12345 received SIGUSR1 # Thread ID may vary
Signal Synchronization in Multithreading
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
void *thread_function(void *arg) {
sigset_t *sigset = (sigset_t *)arg;
// Set signal mask
if (pthread_sigmask(SIG_SETMASK, sigset, NULL) == -1) {
perror("pthread_sigmask");
pthread_exit(NULL);
}
// Thread main loop
while (1) {
printf("Thread running...\n");
sleep(1); // Pause for one second
}
pthread_exit(NULL);
}
int main() {
struct sigaction sa;
sigset_t sigset;
// Initialize signal set
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
// Set signal handler
sa.sa_handler = signal_handler;
sa.sa_mask = sigset; // Set signal mask
sa.sa_flags = 0;
// Register signal handler
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
pthread_t thread;
if (pthread_create(&thread, NULL, thread_function, &sigset) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
// Main thread loop
while (1) {
printf("Main thread running...\n");
sleep(1); // Pause for one second
}
pthread_join(thread, NULL);
return 0;
}
Signal Delivery Mechanism
- Inter-process signals: Use
kill(pid, sig)to send a signal to a specific process. - Inter-thread signals: Signals are delivered to all threads by default, but
pthread_sigmaskcan restrict handling to specific threads. - Real-time signals (
SIGRTMINtoSIGRTMAX): Support queuing (no loss), while standard signals (e.g.,SIGINT) may be lost (only the last one is kept).
Signal Delivery Example
If a signal arrives while another is being handled, it is pended until the current handler completes. Use sigpending to check for pending signals.
Example: Checking Pending Signals
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void signal_handler(int signum) {
if (signum == SIGINT) {
printf("Caught SIGINT. Checking for pending signals...\n");
sigset_t pending;
if (sigpending(&pending) == -1) {
perror("sigpending");
exit(EXIT_FAILURE);
}
if (sigismember(&pending, SIGTERM)) {
printf("SIGTERM is pending. Exiting...\n");
exit(0);
}
printf("No pending signals. Continuing...\n");
}
}
int main() {
struct sigaction sa;
sigset_t sigset;
// Initialize signal set
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGTERM);
// Set signal handler
sa.sa_handler = signal_handler;
sa.sa_mask = sigset; // Set signal mask
sa.sa_flags = 0;
// Register signal handlers
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
// Main loop
while (1) {
printf("Running...\n");
sleep(1); // Pause for one second
}
return 0;
}
Summary and Best Practices
Key Summary
- Signal Basics: Signals are asynchronous event notifications; common ones include
SIGINT,SIGSEGV. - Handlers:
signal()is simple but limited;sigaction()is more flexible (supports masking and flags). - Signal Sets: Used for batch management of blocking and waiting (
sigprocmask,sigsuspend). - Signal Stack: Prevents handler failure when main stack is corrupted (
sigaltstack). - Safe Functions: Only call async-signal-safe functions (e.g.,
write,_exit).
Best Practices
- Minimize handler logic: Handlers should execute quickly and avoid blocking.
- Use
sigactionoversignal: Ensures portability and reliability. - Set signal masks: Block interfering signals during handling (e.g.,
SA_NODEFER). - Test signal handling: Use
killorraise()to manually trigger signals and verify logic. - Multithreaded environments: Use
pthread_sigmaskto control thread-specific signal masks.
By mastering these concepts, developers can efficiently handle signals in C, improving program robustness and reliability.



