1. Understanding Signals
Signals are a limited form of inter-process communication in Unix, Unix-like, and other POSIX-compliant operating systems. They are an asynchronous notification mechanism used to alert a process that an event has occurred. When a signal is sent to a process, the operating system interrupts the process’s normal control flow — any non-atomic operation will be interrupted at that point. If the process has defined a signal handler, it will be executed; otherwise, the default handler is invoked.
Signals are the only asynchronous IPC mechanism — they serve as asynchronous notifications telling the receiving process that something has happened. In simple terms, signals can be thought of as a form of software interrupt.
2. Signal Sources
Generally, signals originate from three sources:
- Hardware: Hardware exceptions such as division by zero or invalid memory access generate signals. These events are typically detected by hardware (e.g., the CPU) and reported to the Linux kernel, which then generates the corresponding signal and delivers it to the process running when the event occurred.
- Software: A user running the
killcommand in a terminal to send a signal; a process callingkillorsigqueueto send a signal; or a software condition becoming true — for example, when a timer set byalarmorsettimerexpires, generatingSIGALRM. - Keyboard input: Pressing certain keys in a terminal generates signals. For example,
Ctrl+CgeneratesSIGINT, andCtrl+\generatesSIGQUIT.
3. Signal Types
Run kill -l to list the signals supported by Linux:
sl@Li:~/Works$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
Linux supports a total of 64 signals. Signals 1–31 are standard signals (also called unreliable signals), while signals 34–64 are real-time signals (reliable signals).
The difference between reliable and unreliable signals:
- Unreliable signals do not support signal queuing: when multiple signals arrive at a process faster than it can handle them, the unprocessed signals are simply dropped, leaving only one pending instance.
- Reliable signals are queued: when multiple signals arrive faster than the process can handle, unprocessed signals are placed into a queue. When the process gets a chance to handle them, it processes them one by one — no signals are lost.
Here are a few commonly used signals:
| Signal | Description |
|---|---|
| SIGHUP | Sent to all processes started from a terminal when the user logs out. Default action: terminate the process. |
| SIGINT | Program interrupt signal, sent when the user types the INTR character (usually Ctrl+C). Used to notify the foreground process group to terminate. |
| SIGQUIT | Similar to SIGINT, but triggered by the QUIT character (usually Ctrl+\). A process exiting due to SIGQUIT produces a core file, making it similar to a program error signal. |
| SIGKILL | Immediately terminates the program. This signal cannot be blocked, handled, or ignored. |
| SIGTERM | Program terminate signal. Unlike SIGKILL, this signal can be blocked and handled. Typically used to request a graceful program exit. |
| SIGSTOP | Stops (suspends) process execution. Unlike terminate and interrupt, the process is not ended — only paused. This signal cannot be blocked, handled, or ignored. |
4. Reentrant Functions
Signal handlers must be reentrant. Since a signal handler can itself be interrupted by another signal — causing the program to jump to another signal handler and then return — special care must be taken when writing custom signal handlers. Thinking of signals as “soft interrupts” makes the concept of reentrant functions easy to understand.
5. Signal Delivery
The kernel handles pending signals for a process when that process transitions from kernel mode back to user mode. Therefore, when a process is running in kernel mode, soft-interrupt signals do not take effect immediately — they are handled only when the process is about to return to user mode. A process only returns to user mode after all signals have been handled; in user mode, a process never has unhandled signals pending.
The kernel handles a process’s pending signals within that process’s context, so the process must be in the running state. When a process receives a signal it ignores, it simply discards it and continues running as if nothing happened.
When a process receives a signal it is set to catch, it executes the user-defined handler upon returning from kernel to user mode. The implementation is clever: the kernel creates a new frame on the user stack, setting the return address in that frame to the address of the user-defined handler. When the process returns from the kernel and pops the stack, it lands at the user-defined handler. When the handler returns and pops the stack again, it returns to where it was before entering the kernel. This approach is necessary because user-defined handlers cannot and must not run in kernel mode (if user-defined functions could run in kernel mode, a user could gain arbitrary privileges).
Here is a concrete example illustrating the process:
- The user program registers
sighandleras the handler forSIGQUIT. - While
mainis executing, an interrupt or exception causes a switch to kernel mode. - Before returning to
mainin user mode after handling the interrupt, the kernel detects thatSIGQUIThas been delivered. - Instead of restoring
main’s context, the kernel decides to executesighandler.sighandlerandmainuse different stack spaces — they are independent control flows, not a caller-callee relationship. - When
sighandlerreturns, it automatically executes the specialsigreturnsystem call to re-enter kernel mode. - If no new signals need to be delivered, the kernel returns to user mode by restoring
main’s context to continue execution.
6. Code Example 1
The following code catches program termination signals and invokes a user-defined handler instead of the system default.
#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
void sig_handle(int sig) {
printf("received signal: %d, quit.\n", sig);
exit(0);
}
int main () {
signal(SIGINT, sig_handle);
signal(SIGKILL, sig_handle);
signal(SIGSEGV, sig_handle);
signal(SIGTERM, sig_handle);
int i = 0;
while (1) {
printf("%d\n", ++i);
sleep(2);
}
printf("main quit.");
return 0;
}
Output:
1
2
received signal: 15, quit.
7. Code Example 2
This example is functionally similar to the one above. Signals can carry parameters, but they are primarily a notification mechanism — the amount of information that can be passed is very limited. For larger data transfers, consider other IPC methods.
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
void new_op(int, siginfo_t*, void*);
int main() {
if (NULL == freopen("sigproc.log", "w", stdout)) {
fprintf(stderr, "error redirecting stdout\n");
}
struct sigaction act;
sigemptyset(&act.sa_mask); // sa_mask specifies which signals should be blocked during handler execution. By default the current signal itself is blocked to prevent nested delivery
sigaddset(&act.sa_mask, SIGTERM);
sigaddset(&act.sa_mask, SIGINT);
act.sa_flags = SA_SIGINFO; // SA_SIGINFO: when set, signal-attached parameters can be passed to the handler
act.sa_sigaction = new_op;
if (sigaction(SIGINT, &act, NULL) < 0) {
printf("install sigal error\n");
}
if (sigaction(SIGTERM, &act, NULL) < 0) {
printf("install sigal error\n");
}
if (sigaction(SIGHUP, &act, NULL) < 0) {
printf("install sigal error\n");
}
int i = 0;
while (1) {
printf("%d\n", ++i);
sleep(1);
}
printf("end.");
return 0;
}
void new_op(int signum, siginfo_t *info, void *myact) {
printf("receive signal %d\n", signum);
for (int i = 0; i < 5; ++i) {
printf("signal processing: %d\n", i);
sleep(1);
}
printf("process quit.");
exit(0);
}
Output:
1
2
3
receive signal 15
signal processing: 0
signal processing: 1
signal processing: 2
signal processing: 3
signal processing: 4
process quit.