Signals
This summary represents my study of Chapter 20 of The Linux Programming Interface by Michael Kerrisk. It is intentionally brief and is not a replacement for the original work, which I encourage reading.
Introduction
A signal is a notification to a process that an event has happened. Signals are also described as “software interrupts” Any process with sufficient permissions can send signals to another process.
Signals are defined in <signal.h>
Signals stages: generated -> pending -> delivered
Some default process reactions to a signal, depending on which signal is sent:
- discarded by the kernel, process never even knows
- abnormal process termination
- process terminated with a core dump
- process stopped (but can later be resumed)
- process resumed (after being stopped)
On Linux, /proc/<PID>/status
describes how a process handles signals.
Some noteworthy signals:
See signal(7) for detailed information on each signal. Here is a shortlist of some interesting ones:
- SIGABRT: terminates the process with a core dump
- SIGALRM: expiration of a realtime timer set by a call to alarm() or setitimer()
- SIGCHLD: a child process was terminated, stopped, or resumed
- SIGCONT: resumes a stopped process
- SIGHUP: terminal disconnect, or tells a daemon to reload its config and reinitialise.
- SIGINT: Usually Ctrl-C, gracefully terminates a process
- SIGKILL: terminate the process without notifying it
- SIGPIPE: a process tried to write to somewhere that is no longer reading (eg. a closed file descriptor)
- SIGPROF: a profiling timer (ie. one that counts cpu time) expired
- SIGQUIT: Usually Ctrl-\, terminates the process with a core dump
- SIGSEGV: Segmentation violation
- SIGSTOP: stop a process without notifying it
- SIGTERM: gracefully terminate a process. default signal sent by the kill command
- SIGTRAP: used to implement debugger breakpoints and syscall tracing. see ptrace(2)
- SIGSTP: Usually Ctrl-Z, stops a process
- SIGVTALRM: virtual timer (counts user mode cpu time) expired
- SIGWINCH: generated when a terminal’s window was resized. Used by programs like
vi
andless
Signal Disposition
A Process can set its signal disposition per signal. This defined how a process reacts to specific signals. Signal disposition can be set to:
- ignore the signal
- execute a programmer defined signal handler
- default behaviour (if a different signal disposition was set previously)
Signal disposition cannot directly be set to terminate the process or generate a core dump unless this is the default behaviour. To achieve this, a signal handler may be installed that then executes the required syscall to exit() or abort().
Signal handlers are installed or established. Signals are said to be handled or caught.
signal()
is an older nonportable API to set signal disposition. Rather use sigaction()
.
sigaction()
is implemented in glibc as a layer on top of signal()
.
It takes the signal to handle and a pointer to a function that handles it.
It returns the previous signal disposition or SIG_ERR
.
This allows you to temporarily set the signal disposition and reset it back afterwards.
Signal Handlers
If a process receives a signal for which a signal handler has been installed, then the kernel interrupts the process, calls the signal handler, and then resumes execution at the point where it was interrupted.
Sending Signals
Signals can be sent using the kill syscall, which takes a pid as argument.
For detailed behaviour, see man(2) kill
.
To send a signal using kill, the following permissions are required:
- A process with
CAP_KILL
can send a signal to any process. - The init process can only be sent signals for which it has handlers.
- A process can send signals to any other processes if:
- real or effective UID of sending process == real UID of receiving process
- real or effective UID of sending process == set-user-ID of receiving process
- SIGCONT may be sent to any other process in the same session, bypassing UID checks
raise()
allows a thread to send a signal to itself.
It is equivalent to pthread_kill(pthread_self(), sig)
.
The simpler call kill(getpid(), sig)
, might send the signal to another thread in the same process.
The signal sent by raise()
is handled even before raise()
returns.
killpg
sends a signal to the entire process group.
sigaction()
, sigprocmask()
and sigpending()
support signal sets (sigset_t
), which is usually a bit mask.
Signal sets are initialised and manipulated using specific functions that I won’t explore here.
Blocking Signals
A process can block certain signals by settings its signal mask. Signals that are blocked will remain pending until the signal mask opens, then they will be delivered. Signal blocking is meant as a mechanism to ensure that critical code is not interrupted at the wrong time.
Signals can be blocked using sigprocmask()
, but sigaction()
also allows signals to be blocked.
SIGKILL
and SIGSTOP
cannot be blocked.
Pending or blocked signals are not queued. Signals that have been sent are a mask. No matther how many of a signal were sent since it was last handled, it will only be handled once.
Conclusion
Signals help facilitate process management in the Kernel and enable a kind of inter process event driven programming. They can terminate or stop processes either gracefully or immediately. They can generate core dumps or pause processes to facilitate debugging. Default behaviour is defined for how processes handle most signals. Programmers can override this behaviour so that their processes ignore, block or handle most signals.
Chapters 21 and 22 further elaborate on signals. I will not post summaries for these. Please support Michael Kerrisk’s original work.