Understanding Unix Signals: A Deep Dive into Process Communication
Unix signals are fundamental to process management and inter-process communication in Linux and Unix-like operating systems. They serve as software interrupts that notify processes of asynchronous events, making them essential for system administration and software development.
Understanding Unix Signals
What Are Unix Signals?
Signals are software interrupts delivered to processes to announce asynchronous events. They can be generated by the kernel, other processes, or the process itself. Modern Unix systems typically support 64 different signals, each identified by a symbolic name beginning with “SIG” and a corresponding number.
When a signal is delivered to a process, it interrupts the normal flow of execution. The process can then:
- Ignore the signal
- Catch and handle the signal with a custom function
- Allow the default action to occur
Common Signal Types and Their Purpose
Termination Signals
SIGTERM (15) - The polite termination request. This signal asks a process to terminate gracefully, allowing it to clean up resources, save state, and exit cleanly.
SIGKILL (9) - The forceful termination. This signal cannot be caught, blocked, or ignored. It immediately terminates the process without cleanup.
SIGINT (2) - The interrupt signal, typically generated when a user presses Ctrl+C. It’s catchable, allowing programs to perform cleanup before termination.
Process Control Signals
SIGSTOP (19) - Pauses a process. Like SIGKILL, this signal cannot be caught or ignored.
SIGCONT (18) - Resumes a paused process. Used in conjunction with SIGSTOP for job control.
SIGTSTP (20) - Terminal stop signal, usually triggered by Ctrl+Z. Unlike SIGSTOP, this can be caught and handled.
Error Signals
SIGSEGV (11) - Segmentation violation. Sent when a process attempts to access memory it shouldn’t, typically resulting in a core dump.
SIGFPE (8) - Floating-point exception. Generated on arithmetic errors like division by zero.
SIGILL (4) - Illegal instruction. Sent when a process attempts to execute an invalid machine instruction.
Timing and Notification Signals
SIGALRM (14) - Alarm clock signal. Generated when a timer set by alarm() expires.
SIGCHLD (17) - Child status change. Sent to a parent process when a child process terminates or stops.
SIGHUP (1) - Hangup. Originally meant the terminal disconnected, now often used to trigger configuration reloads.
Signal Handling in Practice
Basic Signal Handler Implementation
Here’s a simple example of catching and handling SIGINT:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int signo) {
if (signo == SIGINT) {
printf("\nReceived SIGINT! Cleaning up...\n");
// Perform cleanup operations here
exit(0);
}
}
int main(void) {
// Register signal handler
if (signal(SIGINT, sig_handler) == SIG_ERR) {
printf("Can't catch SIGINT\n");
return 1;
}
printf("Running... Press Ctrl+C to interrupt\n");
while(1) {
sleep(1);
}
return 0;
}
Advanced Signal Handling with sigaction()
For more robust signal handling, use sigaction() instead of signal():
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
void handle_signal(int sig, siginfo_t *siginfo, void *context) {
printf("Received signal %d from PID %d\n", sig, siginfo->si_pid);
}
int main() {
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = &handle_signal;
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGUSR1, &act, NULL) < 0) {
perror("sigaction");
return 1;
}
printf("PID: %d - Waiting for SIGUSR1...\n", getpid());
while(1) {
pause();
}
return 0;
}
Sending Signals
From the Command Line
# Send SIGTERM to process 1234
kill 1234
# Send SIGKILL to process 1234
kill -9 1234
# Send SIGUSR1 to process 1234
kill -USR1 1234
# Send signal to all processes in a process group
kill -TERM -1234
Programmatically
#include <signal.h>
#include <unistd.h>
// Send signal to specific process
kill(pid, SIGUSR1);
// Send signal to current process
raise(SIGTERM);
// Send signal to process group
killpg(pgrp, SIGHUP);
Signal Masks and Blocking
Processes can temporarily block signals using signal masks:
sigset_t mask, oldmask;
// Initialize signal set
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
// Block signals
sigprocmask(SIG_BLOCK, &mask, &oldmask);
// Critical section - SIGINT and SIGTERM are blocked
do_critical_work();
// Restore original mask
sigprocmask(SIG_SETMASK, &oldmask, NULL);
Real-World Applications
Graceful Daemon Shutdown
volatile sig_atomic_t shutdown_requested = 0;
void handle_shutdown(int sig) {
shutdown_requested = 1;
}
int main() {
signal(SIGTERM, handle_shutdown);
signal(SIGINT, handle_shutdown);
while (!shutdown_requested) {
// Main daemon work
process_requests();
}
// Cleanup
close_connections();
save_state();
return 0;
}
Configuration Reload without Restart
Many daemons use SIGHUP to reload configuration:
void handle_sighup(int sig) {
syslog(LOG_INFO, "Received SIGHUP, reloading configuration");
reload_config();
}
Best Practices
Keep Signal Handlers Simple: Signal handlers should do minimal work. Set a flag and handle the event in the main program flow.
Use Async-Signal-Safe Functions: Only call functions guaranteed to be safe in signal handlers. Check signal-safety(7) for a complete list.
Handle EINTR: System calls can be interrupted by signals. Always check for EINTR and retry if appropriate.
Avoid Race Conditions: Use atomic operations and proper synchronization when accessing shared data from signal handlers.
Document Signal Usage: Clearly document which signals your application handles and their effects.
Debugging Signal Issues
# Monitor signals sent to a process
strace -e signal -p <pid>
# List pending signals
cat /proc/<pid>/status | grep Sig
# Send test signals
kill -l # List all signals
kill -0 <pid> # Test if process exists
Conclusion
Understanding Unix signals is crucial for robust system programming and effective system administration. They provide a powerful mechanism for process communication and control, enabling graceful shutdowns, dynamic reconfiguration, and proper error handling. By mastering signal handling, developers can create more resilient and responsive applications that integrate seamlessly with the Unix philosophy of process management.
Whether you’re building system daemons, debugging application crashes, or managing production services, a solid understanding of signals will serve you well in your journey through Unix and Linux systems.