Linux Signals: Fundamental Concepts
An intro to Singals in unix.
Signals: Fundamental Concepts
Signals are asynchronous notifications delivered to a process to indicate that an event has occurred. They provide a mechanism for communication and process control.
Examples include SIGINT (terminal interrupt), SIGTERM (termination request), SIGCHLD (child state change), and SIGSEGV (invalid memory access).
Signal Dispositions
A process can define how it responds to a signal:
- Default: Perform the signal’s default action, such as terminating or stopping the process.
- Ignore: Discard the signal.
- Catch: Execute a signal handler when the signal is delivered.
SIGKILL and SIGSTOP cannot be caught or ignored.
Sending Signals
Signals can be sent using functions such as:
kill(pid, sig)— send a signal to a process or process group, depending onpid.raise(sig)— send a signal to the calling process.killpg(pgrp, sig)— send a signal to a process group.
The kill() system call can also be used to check whether a process exists and whether the caller has permission to signal it, by passing signal 0.
Signal Sets and Masks
A signal set represents a collection of signals. Functions such as sigemptyset(), sigfillset(), sigaddset(), and sigdelset() manipulate these sets.
A process’s signal mask determines which signals are temporarily blocked.
- Blocking a signal delays its delivery.
- Unblocking a pending signal makes it eligible for delivery.
- Blocking a signal does not discard it.
SIGKILLandSIGSTOPcannot be blocked.
sigprocmask() changes the calling thread’s signal mask in a single-threaded program. In multithreaded programs, use pthread_sigmask().
Pending Signals
A signal is pending after it has been generated but before it has been delivered.
Standard signals are not queued individually. If the same standard signal is generated multiple times while blocked, those occurrences may be represented by a single pending signal.
Signal notifications and the events they represent are distinct. For example, collecting a child’s exit status with waitpid() does not necessarily remove an already-pending SIGCHLD.
Installing Signal Handlers
sigaction() provides explicit control over signal dispositions and is generally preferred over the older signal() interface.
A signal handler runs when a caught signal is delivered. Since it can interrupt normal program execution, handlers must be careful about which operations they perform.
Only async-signal-safe functions should generally be called from a signal handler. For example, write() is async-signal-safe, whereas printf() is not.
pause()
pause() suspends the calling process until a signal is caught and its handler returns, or the process terminates due to a signal.
However, using pause() alone to wait for a particular signal can introduce a race: the signal might arrive just before the process calls pause(). Chapter 22 covers safer synchronization techniques such as sigsuspend().
Signals and Child Processes
SIGCHLD notifies a parent when a child changes state, such as terminating or stopping.
The parent uses wait() or waitpid() to collect a terminated child’s status and reap it.
These are different responsibilities:
SIGCHLDprovides notification.wait()/waitpid()collects status and reaps terminated children.
A signal handler must not assume that every SIGCHLD notification corresponds to a child that still needs reaping. Robust handlers commonly use waitpid(-1, &status, WNOHANG) in a loop to collect all available terminated children.
Summary
The main signal lifecycle is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Signal generated
|
v
Is it blocked?
/ \
Yes No
| |
Pending Delivered
|
Unblocked
|
v
Delivered
|
v
Default action, ignore, or handler
Understanding signal dispositions, masks, pending signals, and SIGCHLD is essential for reliable Unix process management and shell implementation.