Introduction to Signal Handling
Signals are software interrupts sent to a program to indicate that an important event has occurred. They provide a way to handle asynchronous events such as user interrupts (Ctrl+C), illegal memory access, or timer expiration. Understanding signal handling is crucial for writing robust and responsive C programs.
Signal Architecture Overview
Signal Handling Architecture ├── Signal Sources │ ├── User (Ctrl+C, Ctrl+Z) │ ├── Kernel (SIGSEGV, SIGFPE) │ ├── Process (kill(), raise()) │ └── Timer (alarm(), setitimer()) ├── Signal Delivery │ ├── Pending Signals │ ├── Blocked Signals │ ├── Signal Mask │ └── Signal Disposition └── Signal Handling ├── Default Action ├── Ignore Signal ├── User-defined Handler └── Signal-safe Functions
Common Signals in C
| Signal | Value | Default Action | Description |
|---|---|---|---|
| SIGINT | 2 | Terminate | Interrupt from keyboard (Ctrl+C) |
| SIGQUIT | 3 | Core dump | Quit from keyboard (Ctrl+) |
| SIGILL | 4 | Core dump | Illegal instruction |
| SIGABRT | 6 | Core dump | Abort signal from abort() |
| SIGFPE | 8 | Core dump | Floating point exception |
| SIGSEGV | 11 | Core dump | Invalid memory reference |
| SIGTERM | 15 | Terminate | Termination signal |
| SIGALRM | 14 | Terminate | Timer signal from alarm() |
| SIGUSR1 | 10 | Terminate | User-defined signal 1 |
| SIGUSR2 | 12 | Terminate | User-defined signal 2 |
| SIGCHLD | 17 | Ignore | Child process stopped or terminated |
| SIGCONT | 18 | Continue | Continue if stopped |
| SIGSTOP | 19 | Stop | Stop process (cannot be caught) |
| SIGTSTP | 20 | Stop | Stop from terminal (Ctrl+Z) |
Basic Signal Handling
1. Using signal() Function
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// Signal handler function
void signalHandler(int signum) {
printf("\nSignal %d received!\n", signum);
if (signum == SIGINT) {
printf(" Caught SIGINT (Ctrl+C)\n");
printf(" Press Ctrl+C again to exit\n");
} else if (signum == SIGTERM) {
printf(" Caught SIGTERM\n");
printf(" Exiting gracefully...\n");
}
}
int main() {
// Register signal handlers
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);
printf("Signal handling example. PID: %d\n", getpid());
printf("Try pressing Ctrl+C or send SIGTERM\n");
// Infinite loop - wait for signals
while (1) {
printf("Working...\n");
sleep(2);
}
return 0;
}
2. Ignoring Signals
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main() {
printf("Ignoring SIGINT (Ctrl+C) and SIGQUIT (Ctrl+\\)\n");
printf("Press Ctrl+C or Ctrl+\\ - nothing will happen\n");
printf("Press Ctrl+Z to stop (SIGTSTP)\n\n");
// Ignore these signals
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
int count = 0;
while (count < 10) {
printf("Working... %d\n", ++count);
sleep(1);
}
// Restore default behavior
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
printf("\nDefault behavior restored\n");
printf("Next Ctrl+C will terminate\n");
while (1) {
printf("Working...\n");
sleep(1);
}
return 0;
}
Advanced Signal Handling with sigaction()
1. Basic sigaction() Usage
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
// Signal handler
void handler(int signum) {
printf("Signal %d received\n", signum);
}
int main() {
struct sigaction sa;
// Set up sigaction structure
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask); // No additional signals blocked
sa.sa_flags = 0; // No flags
// Register handler for SIGINT
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("SIGINT handler installed with sigaction\n");
printf("Press Ctrl+C to trigger\n");
pause(); // Wait for signal
return 0;
}
2. Getting Signal Information
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
// Advanced handler with signal information
void advancedHandler(int signum, siginfo_t *info, void *context) {
printf("\n=== Signal Information ===\n");
printf("Signal number: %d\n", signum);
printf("Signal code: %d\n", info->si_code);
printf("Sender PID: %d\n", info->si_pid);
printf("Sender UID: %d\n", info->si_uid);
printf("Signal value: %d\n", info->si_value.sival_int);
switch (info->si_code) {
case SI_USER:
printf("Sent by: kill() or raise()\n");
break;
case SI_QUEUE:
printf("Sent by: sigqueue()\n");
break;
case SI_TIMER:
printf("Sent by: timer expiration\n");
break;
case SI_ASYNCIO:
printf("Sent by: asynchronous I/O completion\n");
break;
case SI_MESGQ:
printf("Sent by: message queue state change\n");
break;
}
}
int main() {
struct sigaction sa;
// Set up sigaction with extended information
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = advancedHandler;
sa.sa_flags = SA_SIGINFO; // Get extended info
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("Advanced handler for SIGUSR1 installed\n");
printf("PID: %d\n", getpid());
printf("Send SIGUSR1 from another terminal with: kill -USR1 %d\n", getpid());
printf("Or send with value: kill -USR1 %d\n", getpid());
printf("\nWaiting for signal...\n");
pause();
return 0;
}
3. Blocking Signals with Signal Masks
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int signalReceived = 0;
void handler(int signum) {
signalReceived = 1;
printf("Signal %d received and handled\n", signum);
}
int main() {
sigset_t newmask, oldmask, pendmask;
// Set up signal handler
signal(SIGINT, handler);
// Block SIGINT
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
// Block the signal
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) == -1) {
perror("sigprocmask");
return 1;
}
printf("SIGINT is now blocked\n");
printf("Try pressing Ctrl+C - signal will be pending\n");
sleep(5);
// Check pending signals
sigpending(&pendmask);
if (sigismember(&pendmask, SIGINT)) {
printf("\nSIGINT is pending\n");
} else {
printf("\nNo pending signals\n");
}
// Unblock the signal
printf("Unblocking SIGINT...\n");
sigprocmask(SIG_SETMASK, &oldmask, NULL);
printf("Signal should be delivered now\n");
sleep(1);
if (signalReceived) {
printf("Signal was handled successfully\n");
}
return 0;
}
Practical Signal Handling Examples
1. Graceful Shutdown
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
volatile sig_atomic_t shutdownRequested = 0;
void shutdownHandler(int signum) {
shutdownRequested = 1;
}
int main() {
// Set up handlers for graceful shutdown
struct sigaction sa;
sa.sa_handler = shutdownHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
printf("Server started. PID: %d\n", getpid());
printf("Press Ctrl+C to initiate graceful shutdown\n");
int iteration = 0;
while (!shutdownRequested) {
printf("Processing iteration %d...\n", ++iteration);
// Simulate work with cleanup points
for (int i = 0; i < 10; i++) {
if (shutdownRequested) break;
printf(" Sub-task %d\n", i);
sleep(1);
}
if (shutdownRequested) {
printf("\nShutdown requested. Cleaning up...\n");
// Perform cleanup
sleep(2); // Simulate cleanup
printf("Cleanup complete. Exiting.\n");
break;
}
}
printf("Server stopped gracefully\n");
return 0;
}
2. Timer Signals
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
int timerCount = 0;
void timerHandler(int signum) {
printf("Timer tick %d\n", ++timerCount);
}
int main() {
struct itimerval timer;
// Set up signal handler
signal(SIGALRM, timerHandler);
// Configure timer
timer.it_value.tv_sec = 1; // First expiration after 1 second
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 2; // Then every 2 seconds
timer.it_interval.tv_usec = 0;
// Start timer
if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
perror("setitimer");
return 1;
}
printf("Timer started. Will fire every 2 seconds.\n");
printf("Press Ctrl+C to stop\n");
// Wait for 10 seconds
sleep(10);
// Stop timer
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &timer, NULL);
printf("\nTimer stopped. Total ticks: %d\n", timerCount);
return 0;
}
3. Child Process Monitoring
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void childHandler(int signum) {
int status;
pid_t pid;
// Wait for all terminated children
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
printf("Child %d exited with status %d\n",
pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child %d killed by signal %d\n",
pid, WTERMSIG(status));
}
}
}
int main() {
struct sigaction sa;
// Set up SIGCHLD handler
sa.sa_handler = childHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa, NULL);
// Create child processes
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
// Child process
printf("Child %d started\n", getpid());
sleep(2 + i);
printf("Child %d exiting\n", getpid());
exit(i);
}
}
printf("Parent waiting for children...\n");
// Parent continues working
for (int i = 0; i < 10; i++) {
printf("Parent working... %d\n", i);
sleep(1);
}
printf("Parent exiting\n");
return 0;
}
4. Signal Queue (POSIX Real-time Signals)
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#define MAX_SIGNALS 5
void rtHandler(int signum, siginfo_t *info, void *context) {
printf("Received signal %d with value %d\n",
signum, info->si_value.sival_int);
}
int main() {
struct sigaction sa;
union sigval value;
// Set up handler for real-time signal
sa.sa_sigaction = rtHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("Real-time signal handler installed\n");
printf("PID: %d\n", getpid());
printf("Sending signals to self...\n");
// Queue multiple signals
for (int i = 0; i < MAX_SIGNALS; i++) {
value.sival_int = i * 10;
if (sigqueue(getpid(), SIGRTMIN, value) == -1) {
perror("sigqueue");
}
printf("Queued signal with value %d\n", value.sival_int);
}
printf("\nWaiting for signals to be delivered...\n");
sleep(2);
return 0;
}
Signal-Safe Functions
1. Async-Signal-Safe Functions
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
// Signal-safe function list (POSIX)
// write(), read(), open(), close(), stat(), etc.
// NOT safe: printf(), malloc(), free(), etc.
volatile sig_atomic_t flag = 0;
void safeHandler(int signum) {
// Using write() which is async-signal-safe
const char *msg = "Signal caught!\n";
write(STDOUT_FILENO, msg, strlen(msg));
flag = 1;
}
int main() {
signal(SIGINT, safeHandler);
printf("Signal handler uses async-signal-safe write()\n");
printf("Press Ctrl+C\n");
while (!flag) {
pause();
}
printf("Program continuing after signal\n");
return 0;
}
2. Unsafe vs Safe Operations
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
// UNSAFE handler - uses printf
void unsafeHandler(int signum) {
// printf is NOT async-signal-safe!
printf("Signal %d received\n", signum); // DANGER!
}
// SAFE handler - uses write
void safeHandler(int signum) {
const char *msg = "Signal received\n";
write(STDOUT_FILENO, msg, strlen(msg)); // Safe
}
int main() {
printf("Compare safe vs unsafe handlers\n");
// Test unsafe handler
signal(SIGUSR1, unsafeHandler);
printf("\nUnsafe handler (using printf):\n");
raise(SIGUSR1);
// Test safe handler
signal(SIGUSR2, safeHandler);
printf("\nSafe handler (using write):\n");
raise(SIGUSR2);
return 0;
}
3. Self-Pipe Trick
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
int pipefd[2];
void signalHandler(int signum) {
// Write to pipe (async-signal-safe)
int saved_errno = errno;
write(pipefd[1], "x", 1);
errno = saved_errno;
}
int main() {
// Create pipe
if (pipe(pipefd) == -1) {
perror("pipe");
return 1;
}
// Set non-blocking on read end
int flags = fcntl(pipefd[0], F_GETFL);
fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
// Set up signal handler
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);
printf("Self-pipe trick example. PID: %d\n", getpid());
printf("Signals will be handled via pipe\n");
char buf[1024];
int running = 1;
while (running) {
// Do some work
printf("Working...\n");
// Check for signals using select
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(pipefd[0], &readfds);
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
int ret = select(pipefd[0] + 1, &readfds, NULL, NULL, &tv);
if (ret > 0 && FD_ISSET(pipefd[0], &readfds)) {
// Read from pipe to clear it
int n = read(pipefd[0], buf, sizeof(buf));
if (n > 0) {
printf("\nReceived %d signal(s)\n", n);
if (n >= 3) running = 0; // Exit after 3 signals
}
}
}
printf("Exiting...\n");
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
Advanced Signal Techniques
1. Signal Sets Operations
#include <stdio.h>
#include <signal.h>
void printSignalSet(const char *name, sigset_t *set) {
printf("%s contains: ", name);
for (int i = 1; i <= 31; i++) {
if (sigismember(set, i)) {
printf("%d ", i);
}
}
printf("\n");
}
int main() {
sigset_t set1, set2, set3;
// Initialize empty set
sigemptyset(&set1);
printf("After sigemptyset: ");
printSignalSet("set1", &set1);
// Add signals
sigaddset(&set1, SIGINT);
sigaddset(&set1, SIGTERM);
sigaddset(&set1, SIGUSR1);
printf("After adding SIGINT, SIGTERM, SIGUSR1: ");
printSignalSet("set1", &set1);
// Fill set
sigfillset(&set2);
printf("\nAfter sigfillset: ");
printSignalSet("set2", &set2);
// Remove some signals
sigdelset(&set2, SIGINT);
sigdelset(&set2, SIGTERM);
printf("After removing SIGINT, SIGTERM: ");
printSignalSet("set2", &set2);
// Set operations
sigemptyset(&set3);
sigaddset(&set3, SIGINT);
sigaddset(&set3, SIGQUIT);
// Check membership
printf("\nMembership checks:\n");
printf("SIGINT in set1? %s\n",
sigismember(&set1, SIGINT) ? "yes" : "no");
printf("SIGQUIT in set1? %s\n",
sigismember(&set1, SIGQUIT) ? "yes" : "no");
return 0;
}
2. sigsuspend() for Atomic Waiting
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int signalReceived = 0;
void handler(int signum) {
signalReceived = 1;
}
int main() {
sigset_t newmask, oldmask;
signal(SIGUSR1, handler);
// Block SIGUSR1
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
printf("Waiting for SIGUSR1 with sigsuspend\n");
printf("Send signal from another terminal: kill -USR1 %d\n", getpid());
// Critical section - signals are blocked
// Do some preparation work
printf("Preparing...\n");
sleep(1);
// Atomically unblock signals and wait
sigsuspend(&oldmask);
// After signal, signals are blocked again
printf("Signal received! Continuing...\n");
// Restore original mask
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return 0;
}
3. sigaltstack() - Alternate Signal Stack
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define STACK_SIZE SIGSTKSZ * 2
// Handler using alternate stack
void stackHandler(int signum) {
char buffer[1000]; // Large stack allocation
printf("Handler running on alternate stack\n");
printf("Stack pointer difference: %ld\n",
(long)&buffer - (long)__builtin_frame_address(0));
}
int main() {
stack_t ss;
struct sigaction sa;
// Allocate alternate stack
ss.ss_sp = malloc(STACK_SIZE);
if (ss.ss_sp == NULL) {
perror("malloc");
return 1;
}
ss.ss_size = STACK_SIZE;
ss.ss_flags = 0;
if (sigaltstack(&ss, NULL) == -1) {
perror("sigaltstack");
free(ss.ss_sp);
return 1;
}
// Set up handler to use alternate stack
sa.sa_handler = stackHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;
sigaction(SIGUSR1, &sa, NULL);
printf("Handler will use alternate stack\n");
printf("Send SIGUSR1: kill -USR1 %d\n", getpid());
pause();
free(ss.ss_sp);
return 0;
}
Error Handling with Signals
1. Segmentation Fault Handling
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
jmp_buf env;
void segfaultHandler(int signum) {
printf("\nSegmentation fault caught!\n");
printf("Signal %d (SIGSEGV) occurred\n", signum);
printf("Jumping back to safe point...\n");
longjmp(env, 1);
}
int main() {
// Set up segfault handler
signal(SIGSEGV, segfaultHandler);
if (setjmp(env) == 0) {
// Normal execution
printf("Attempting dangerous operation...\n");
// Cause a segfault
int *p = NULL;
*p = 42; // This will crash
printf("This line won't be reached\n");
} else {
// After longjmp from handler
printf("Recovered from segmentation fault!\n");
printf("Program continuing safely\n");
}
printf("Program completed normally\n");
return 0;
}
2. Floating Point Exception Handling
#include <stdio.h>
#include <signal.h>
#include <fenv.h>
void fpeHandler(int signum) {
printf("\nFloating point exception caught!\n");
printf("Signal %d (SIGFPE) occurred\n", signum);
// Clear the exception
feclearexcept(FE_ALL_EXCEPT);
}
int main() {
// Set up FPE handler
signal(SIGFPE, fpeHandler);
printf("Demonstrating floating point exceptions\n");
// Enable FPE exceptions
feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
// Division by zero
printf("\nAttempting division by zero...\n");
float x = 1.0;
float y = 0.0;
float z = x / y;
printf("Result: %f\n", z); // This may not execute
printf("Program continuing...\n");
return 0;
}
Best Practices and Pitfalls
1. Race Conditions
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
volatile sig_atomic_t flag = 0;
void handler(int signum) {
flag = 1; // Safe atomic assignment
}
int main() {
signal(SIGUSR1, handler);
// Correct pattern - no race
printf("Waiting for signal (correct pattern)...\n");
while (!flag) {
pause(); // Wait for signal
}
printf("Signal received!\n");
return 0;
}
2. Reentrancy Issues
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
int globalVar = 0;
void handler(int signum) {
// BAD: Non-reentrant function
// globalVar++; // Could be interrupted
// GOOD: Atomic operation
static volatile sig_atomic_t safe = 0;
safe = 1; // Safe assignment
}
int main() {
signal(SIGUSR1, handler);
printf("Always use sig_atomic_t for shared variables\n");
return 0;
}
3. Signal Handler Checklist
// ✅ DO: Use only async-signal-safe functions // ✅ DO: Use volatile sig_atomic_t for shared variables // ✅ DO: Keep handlers simple and fast // ✅ DO: Reset handlers if needed // ✅ DO: Save and restore errno // ❌ DON'T: Call non-reentrant functions (printf, malloc, free) // ❌ DON'T: Use global data structures without protection // ❌ DON'T: Assume signal ordering // ❌ DON'T: Ignore return values from signal functions // ❌ DON'T: Use longjmp in handlers (unless you know what you're doing)
Complete Practical Example
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#define MAX_HANDLERS 10
typedef struct {
int signum;
int count;
time_t lastTime;
} SignalStats;
SignalStats stats[MAX_HANDLERS];
int handlerCount = 0;
// Signal-safe integer to string conversion
void itoa_safe(int n, char *buf) {
int i = 0;
int isNegative = 0;
if (n < 0) {
isNegative = 1;
n = -n;
}
do {
buf[i++] = n % 10 + '0';
n /= 10;
} while (n > 0);
if (isNegative) {
buf[i++] = '-';
}
// Reverse string
for (int j = 0; j < i / 2; j++) {
char temp = buf[j];
buf[j] = buf[i - j - 1];
buf[i - j - 1] = temp;
}
buf[i] = '\0';
}
// Generic signal handler
void genericHandler(int signum) {
int saved_errno = errno;
// Update statistics
for (int i = 0; i < handlerCount; i++) {
if (stats[i].signum == signum) {
stats[i].count++;
stats[i].lastTime = time(NULL);
break;
}
}
// Write message using write() (async-signal-safe)
const char *msg = "Signal received: ";
write(STDOUT_FILENO, msg, strlen(msg));
char numBuf[16];
itoa_safe(signum, numBuf);
write(STDOUT_FILENO, numBuf, strlen(numBuf));
write(STDOUT_FILENO, "\n", 1);
errno = saved_errno;
}
// Register signal handler
void registerSignal(int signum) {
if (handlerCount < MAX_HANDLERS) {
stats[handlerCount].signum = signum;
stats[handlerCount].count = 0;
stats[handlerCount].lastTime = 0;
handlerCount++;
signal(signum, genericHandler);
printf("Registered handler for signal %d\n", signum);
}
}
// Print statistics (not async-signal-safe - use in main loop)
void printStats() {
printf("\n=== Signal Statistics ===\n");
for (int i = 0; i < handlerCount; i++) {
printf("Signal %d: %d times",
stats[i].signum, stats[i].count);
if (stats[i].lastTime > 0) {
printf(" (last: %s", ctime(&stats[i].lastTime));
} else {
printf("\n");
}
}
printf("========================\n");
}
int main() {
// Register various signals
registerSignal(SIGINT);
registerSignal(SIGTERM);
registerSignal(SIGUSR1);
registerSignal(SIGUSR2);
registerSignal(SIGALRM);
printf("\nSignal handler demo started. PID: %d\n", getpid());
printf("Available signals:\n");
printf(" Ctrl+C - SIGINT\n");
printf(" kill -TERM %d - SIGTERM\n", getpid());
printf(" kill -USR1 %d - SIGUSR1\n", getpid());
printf(" kill -USR2 %d - SIGUSR2\n", getpid());
printf(" kill -ALRM %d - SIGALRM\n\n", getpid());
// Set up timer
alarm(5); // SIGALRM in 5 seconds
// Main loop
int running = 1;
int iterations = 0;
while (running) {
printf("Working... (%d)\n", ++iterations);
sleep(1);
if (iterations >= 15) {
running = 0;
}
// Print stats every 5 iterations
if (iterations % 5 == 0) {
printStats();
}
}
printStats();
printf("Program exiting normally\n");
return 0;
}
Signal Handling Comparison
| Function | Thread Safety | Information | Portability | Use Case |
|---|---|---|---|---|
| signal() | No | Basic | POSIX/Unix | Simple handlers |
| sigaction() | Yes | Extended | POSIX | Advanced control |
| sigqueue() | Yes | With data | POSIX | Real-time signals |
| sigsuspend() | N/A | N/A | POSIX | Atomic waiting |
| sigaltstack() | N/A | N/A | POSIX | Alternate stack |
| sigprocmask() | No (process) | N/A | POSIX | Signal blocking |
Conclusion
Signal handling is a powerful feature in C that allows programs to respond to asynchronous events:
Key Concepts
- Signals are software interrupts for event notification
- Default actions include terminate, ignore, core dump, stop
- Custom handlers can override default behavior
- sigaction() provides more control than signal()
- Signal masks allow blocking/unblocking signals
- Async-signal-safe functions are limited but essential
Best Practices
- Keep signal handlers simple and fast
- Use only async-signal-safe functions in handlers
- Use volatile sig_atomic_t for shared variables
- Block signals during critical sections
- Consider using sigaction() instead of signal()
- Test signal handling thoroughly
Common Applications
- Graceful shutdown (SIGINT, SIGTERM)
- Timer events (SIGALRM)
- Child process monitoring (SIGCHLD)
- User-defined communication (SIGUSR1, SIGUSR2)
- Error recovery (SIGSEGV, SIGFPE)
Mastering signal handling is essential for writing robust system software, daemons, and applications that need to respond to external events or recover from errors gracefully.