Chapter 62: Signal Handling: Introduction to Signals and Default Actions
Chapter Objectives
Upon completing this chapter, you will be able to:
- Understand the fundamental role of signals as an asynchronous inter-process communication mechanism in Linux.
- Identify common signals such as
SIGINT
,SIGTERM
, andSIGCHLD
, and describe their default actions and typical use cases. - Implement custom signal handlers in C to gracefully manage events like user interruptions and process termination.
- Configure a parent process to correctly handle the
SIGCHLD
signal to manage child process lifecycle and prevent zombie processes. - Debug common issues related to signal handling, including the use of non-reentrant functions and race conditions.
- Apply signal handling concepts to build more robust and reliable embedded applications on a Raspberry Pi 5.
Introduction
In the world of embedded systems, events do not always follow a predictable, synchronous schedule. A user might press an emergency stop button, a sensor might detect a critical failure, or a remote command might request a graceful shutdown. These asynchronous events require an immediate and reliable response from the software. This is where the Linux signal mechanism becomes indispensable. Signals are one of the oldest and most fundamental forms of Inter-Process Communication (IPC) on UNIX-like operating systems, serving as a software notification to a process that an event has occurred.
Understanding signals is not merely an academic exercise; it is a critical skill for any embedded Linux developer. A properly handled signal can be the difference between a device that corrupts its filesystem during a power loss and one that saves its state and shuts down cleanly. It can distinguish a system that recovers gracefully from a fault from one that requires a hard reboot. For example, a SIGTERM
signal allows an application controlling a robotic arm to return to a safe home position before exiting, while an unhandled signal might leave the hardware in an unpredictable and potentially dangerous state. In this chapter, we will explore the theory behind signals, their default behaviors, and how to harness their power to create resilient embedded applications. Using the Raspberry Pi 5, you will move from theory to practice, writing code that catches user input, manages child processes, and lays the foundation for building truly robust systems.
Technical Background
The Nature of Asynchronous Events
To appreciate the design of signals, one must first understand the problem they solve: handling asynchronous events. A running program, or process, executes instructions sequentially, following a logical path defined by its code. However, the world outside the process is not always so orderly. Events can originate from various sources at any time, irrespective of the process’s current state. The operating system kernel is the primary originator of many signals, often in response to hardware events or exceptional conditions. For instance, if a process attempts to access an invalid memory address, the Memory Management Unit (MMU) in the CPU triggers a hardware exception. The kernel catches this exception and notifies the offending process by sending it a SIGSEGV
(Segmentation Fault) signal.
Another common source is the user. When you press Ctrl+C
in a terminal, the terminal driver recognizes this key combination and, through the kernel, sends a SIGINT
(Interrupt) signal to the foreground process. The process is interrupted from whatever it was doing to handle this event. Other processes can also explicitly send signals to one another using system calls like kill()
, providing a direct mechanism for inter-process communication and control.
This asynchronous nature presents a significant challenge. The process’s main line of execution is paused without warning, and control is transferred to a different piece of code—the signal handler. This is conceptually similar to a hardware interrupt, where the CPU stops its current task to execute an interrupt service routine (ISR). Just as an ISR must be carefully written to be fast and avoid disrupting the system, a signal handler must be crafted with similar care to maintain program consistency and stability.
graph TD subgraph Main Program Execution A[Start Process] --> B{"Executing Main Code...<br><i>(e.g., data logging, motor control)</i>"}; B --> C{Continue Main Code...}; C --> B; end subgraph Signal Handling D{"Signal Handler Execution<br><b>(Minimal & Safe Code)</b><br>e.g., set volatile flag"} --> E[Return from Handler]; end F(Asynchronous Event<br><i>e.g., User presses Ctrl+C</i>) -- Triggers --> G[Kernel Delivers SIGINT]; G -- Interrupts Process --> D; E -- Resumes Execution --> B; %% Styling classDef default fill:#f8fafc,stroke:#64748b,stroke-width:1px,color:#1f2937; classDef startNode fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff; classDef processNode fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; classDef eventNode fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff; classDef kernelNode fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff; classDef handlerNode fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff; class A startNode; class B,C processNode; class F eventNode; class G kernelNode; class D,E handlerNode;
Signal Lifecycle: Generation, Delivery, and Disposition
Every signal follows a distinct lifecycle consisting of three phases: generation, delivery, and disposition.
graph TD A[Start: Event Occurs] --> B(<b>1. Generation</b><br>Signal is generated and marked as 'pending' for the process.<br><i>Sources: Kernel, other processes, user input.</i>); B --> C{<b>2. Delivery</b><br>Kernel checks for pending signals before resuming process in user mode.}; C --> D{<b>3. Disposition</b><br>What does the process do?}; D -- "Choice 1" --> E(<b>Default Action</b><br>System-defined behavior<br>e.g., Terminate, Ignore, Core Dump); D -- "Choice 2" --> F(<b>Catch Signal</b><br>Execute a custom signal handler function.<br><i>Most flexible option.</i>); D -- "Choice 3" --> G(<b>Ignore Signal</b><br>The signal is discarded; the process is unaffected.); subgraph Unavoidable Signals H(<b>SIGKILL & SIGSTOP</b><br>Cannot be Caught or Ignored); end F --> I[End: Handler Completes]; E --> J[End: Action Performed]; G --> K[End: Signal Discarded]; %% Styling classDef default fill:#f8fafc,stroke:#64748b,stroke-width:1px,color:#1f2937,font-family:'Open Sans'; classDef startNode fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff; classDef processNode fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; classDef decisionNode fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff; classDef endNode fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff; classDef warningNode fill:#eab308,stroke:#eab308,stroke-width:1px,color:#1f2937; class A startNode; class B,E,F,G processNode; class C,D decisionNode; class I,J,K endNode; class H warningNode;
- Generation: A signal is generated (or sent) when the event that causes it occurs. As discussed, this can be a hardware exception, a user action, or a direct system call from another process. When a signal is generated for a process, it is typically marked as pending. A process can have multiple signals pending at once, but generally, there is only one pending signal for each signal type. For example, if a process receives ten
SIGINT
signals in rapid succession before it has a chance to run, only one of them is guaranteed to be delivered. Real-time signals are an exception to this, as they can be queued, but the standard signals we discuss here are not. - Delivery: A pending signal is delivered to a process when the kernel alters the process’s execution to deal with the signal. This happens just before the process is scheduled to resume execution in user mode. Before returning control to the process’s main instruction stream, the kernel checks for any pending signals. If one exists, the kernel arranges for the process to handle it. The process is temporarily “hijacked” to deal with the signal.
- Disposition: The disposition of a signal refers to the action the process takes upon its delivery. For every signal, a process has three choices:
- Default Action: Every signal has a default action defined by the system. For many signals, like
SIGSEGV
orSIGILL
(Illegal Instruction), the default action is to terminate the process, often creating a core dump file for debugging. For other signals, likeSIGCHLD
, the default action is to be ignored. ForSIGINT
, the default is to terminate the process. - Catch the Signal: A process can establish a custom function, a signal handler, to be executed when the signal is delivered. This allows the application to define its own behavior, such as performing cleanup operations before shutting down. This is the most powerful and common way to use signals in application development.
- Ignore the Signal: A process can explicitly choose to ignore a signal. When an ignored signal is delivered, the system simply discards it, and the process’s execution is unaffected. However, two signals,
SIGKILL
andSIGSTOP
, are special; they cannot be caught or ignored. They provide a guaranteed way for the system administrator (and the kernel) to terminate or stop a process, respectively. This is a crucial failsafe mechanism.
- Default Action: Every signal has a default action defined by the system. For many signals, like
A process can control a signal’s disposition using the signal()
or the more modern and recommended sigaction()
system call. These calls allow the process to specify a handler function or to set the disposition to ignore or revert to the default action.
Common Signals and Their Default Actions
While the Linux kernel defines over thirty different signals, a handful are particularly relevant in the context of embedded systems programming. Understanding their purpose and default behavior is fundamental.
SIGINT
(Signal Interrupt): This signal is sent to a process by its controlling terminal when a user presses the interrupt key, typicallyCtrl+C
. Its default action is to terminate the process. In embedded development, this is commonly caught to allow for a graceful shutdown of an application being tested from a command line. For example, a data logging application might catchSIGINT
to ensure all buffered data is written to the disk and files are closed properly before exiting.SIGTERM
(Signal Terminate): This is the standard, generic signal used to request the termination of a process. UnlikeSIGKILL
,SIGTERM
can be caught and handled, giving the application a chance to perform cleanup. This is the signal sent by thekill
command by default. In a system managed by an init system likesystemd
,SIGTERM
is sent to a service to ask it to stop. A well-behaved daemon should always handleSIGTERM
to shut down gracefully.SIGKILL
(Signal Kill): This signal causes the immediate termination of a process. It cannot be caught or ignored. It is the “last resort” for terminating a misbehaving or unresponsive process. While effective, it should be used with caution in embedded systems because it gives the application no opportunity to save its state, release resources (like hardware locks), or put hardware into a safe state. Relying onSIGKILL
is often a sign of a design flaw.SIGHUP
(Signal Hang Up): This signal’s name is a relic of the days of serial modems. It was originally sent to a process when its controlling terminal was disconnected (the line was “hung up”). Today, it is commonly used as a mechanism to signal a daemon process to reload its configuration file without shutting down. An embedded web server, for instance, might reload its settings upon receivingSIGHUP
. The default action is to terminate the process.SIGCHLD
(Signal Child): This signal is sent to a parent process whenever one of its child processes terminates, stops, or resumes after being stopped. The default action is to ignore it. However, this signal is critically important for preventing zombie processes. A zombie is a process that has completed execution but still has an entry in the process table. This entry is needed so the parent process can read its child’s exit status. If the parent never reads this status (by callingwait()
or a related system call), the zombie will persist, consuming a process ID and a small amount of kernel memory. While a few zombies are harmless, a large number can exhaust the system’s PIDs, preventing new processes from being created. A robust parent process must therefore catchSIGCHLD
and use its handler to callwait()
, thereby “reaping” the child and allowing the kernel to remove it from the process table.
sequenceDiagram actor Parent participant Child participant Kernel Parent->>Kernel: fork() activate Kernel Kernel-->>Parent: returns child_pid deactivate Kernel Kernel-->>Child: returns 0 activate Child Parent->>Parent: Continues other work... Child->>Child: Executes its task... Child->>Kernel: exit() activate Kernel Note right of Child: Child is now terminated<br>but is a Zombie Process. Kernel->>Parent: Sends SIGCHLD signal deactivate Child alt SIGCHLD is Handled (Correct Way) Parent->>Parent: SIGCHLD handler is invoked activate Parent Parent->>Kernel: waitpid(child_pid, ...) Kernel-->>Parent: Returns child's exit status deactivate Kernel Note right of Kernel: Kernel cleans up zombie<br>process from process table. deactivate Parent else SIGCHLD is Ignored (Wrong Way) Parent->>Parent: Ignores SIGCHLD Note over Parent,Kernel: Child remains a Zombie process,<br>consuming a PID until Parent exits. end
SIGUSR1
andSIGUSR2
(User-defined Signals): These signals are left without a predefined meaning, allowing developers to use them for custom purposes within an application or between cooperating processes. For example, one could useSIGUSR1
to toggle a debugging mode on or off in a running application, orSIGUSR2
to trigger an immediate data dump to a log file. Their default action is to terminate the process, so they must be caught to be useful.
The Challenge of Signal Handlers: Reentrancy
When a signal handler is invoked, the main program’s execution is paused at an arbitrary point. The handler code begins to execute, using the same process memory and resources. This creates a dangerous possibility: what if the signal arrived just after the main program had acquired a lock or was in the middle of updating a complex data structure? If the signal handler then tries to acquire the same lock or access the same partially-updated structure, the program could deadlock or suffer from data corruption.
To prevent this, functions called from within a signal handler must be async-signal-safe. This means the function is guaranteed to execute correctly even when called from a signal handler that interrupted another operation. A very limited set of standard library functions are guaranteed to be async-signal-safe. Functions like printf()
, malloc()
, free()
, and most other standard I/O functions are not safe. They often rely on global data structures or locks that could be in an inconsistent state when the signal is delivered.
Tip: The general best practice for signal handlers is to keep them as short and simple as possible. A common and safe pattern is to have the handler set a global
volatile sig_atomic_t
flag and then return. The main program loop can then periodically check this flag and perform the necessary actions in a safe context, outside of the handler.
This constraint is fundamental to writing reliable signal-handling code. Ignoring it is a frequent source of subtle, hard-to-diagnose bugs in multithreaded and signal-driven applications.
Practical Examples
Now, let’s translate this theory into practice on the Raspberry Pi 5. These examples will demonstrate how to capture signals and use them to control application behavior. You will need a Raspberry Pi 5 running a standard Raspberry Pi OS (or another Linux distribution) and access to its command line.
Example 1: Graceful Shutdown with SIGINT
This first example shows how to write a simple C program that catches the SIGINT
signal (Ctrl+C
) and performs a clean shutdown instead of terminating abruptly.
Code Snippet
Create a file named sigint_handler.c
and enter the following code. This program simulates a main loop that is doing work, and when interrupted, it prints a message and exits cleanly.
// sigint_handler.c
// Demonstrates a simple signal handler for SIGINT on a Raspberry Pi 5.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
// A global flag to indicate if a signal has been received.
// 'volatile' tells the compiler that this variable can be changed by external means
// (i.e., the signal handler) and prevents certain optimizations.
// 'sig_atomic_t' is an integer type that is guaranteed to be read/written atomically,
// preventing partial updates in the presence of a signal.
volatile sig_atomic_t g_signal_received = 0;
/**
* @brief Signal handler for SIGINT.
*
* This function is called when the process receives a SIGINT signal (e.g., from Ctrl+C).
* It sets a global flag and prints a message. Note the use of printf() here is
* technically not async-signal-safe, but is used for simple demonstration.
* In a real-world application, you would avoid complex I/O in a handler.
*
* @param signum The signal number that was caught.
*/
void sigint_handler(int signum) {
// Set the flag that the main loop will check.
g_signal_received = 1;
}
int main() {
printf("Process started with PID: %d\n", getpid());
printf("Press Ctrl+C to trigger the signal handler and exit gracefully.\n");
// Set up the signal handler using sigaction for robustness.
// sigaction is preferred over the older signal() call.
struct sigaction sa;
sa.sa_handler = sigint_handler; // Set our custom handler function
sigemptyset(&sa.sa_mask); // Do not block any other signals during execution of this handler
sa.sa_flags = 0; // No special flags
// Register the handler for SIGINT
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("Error: cannot handle SIGINT");
exit(EXIT_FAILURE);
}
// Main application loop.
// It will continue until the signal handler sets the flag.
while (!g_signal_received) {
printf("Doing work...\n");
sleep(1); // Simulate work being done
}
// This part of the code is executed after the loop breaks.
printf("\nSIGINT received. Shutting down gracefully.\n");
// Here you would perform cleanup tasks, e.g., closing files, releasing resources.
printf("Cleanup complete. Exiting.\n");
return EXIT_SUCCESS;
}
Build and Execution Steps
1. Compile the Code: Open a terminal on your Raspberry Pi 5 and compile the program using GCC.
gcc -o sigint_handler sigint_handler.c -Wall
The -o sigint_handler
flag specifies the output executable name, and -Wall
enables all compiler warnings, which is good practice.
2. Run the Program: Execute the compiled program.
./sigint_handler
3. Expected Output and Interaction: The program will start and print its Process ID (PID). It will then print “Doing work…” every second.
Process started with PID: 12345
Press Ctrl+C to trigger the signal handler and exit gracefully.
Doing work...
Doing work...
Doing work...
4. Trigger the Signal: While the program is running, press Ctrl+C
. Instead of immediately terminating, you will see the following output as the signal handler is invoked and the main loop exits:
^C
SIGINT received. Shutting down gracefully.
Cleanup complete. Exiting.
Notice that the printf
from within the handler itself might not appear, as the main loop detects the flag change and prints the shutdown messages. The ^C
is printed by the terminal to indicate the keypress. The program exits cleanly on its own terms.
Example 2: Preventing Zombie Processes with SIGCHLD
This example demonstrates a more advanced and critical use case in system programming: managing child processes. The parent process will fork a child, and then use a SIGCHLD
handler to wait for the child to terminate, preventing it from becoming a zombie.
Code Snippet
Create a file named sigchld_handler.c
.
// sigchld_handler.c
// Demonstrates handling SIGCHLD to prevent zombie processes.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
/**
* @brief Signal handler for SIGCHLD.
*
* This handler is designed to reap all terminated child processes.
* It uses a loop with waitpid() and the WNOHANG option to handle multiple
* children terminating in quick succession, ensuring no zombies are left.
*
* @param signum The signal number.
*/
void sigchld_handler(int signum) {
int saved_errno = errno; // waitpid() might change errno
// Reap all terminated children.
// WNOHANG ensures that waitpid returns immediately if no child has exited.
while (waitpid(-1, NULL, WNOHANG) > 0) {
// This loop will continue as long as there are terminated children to reap.
}
errno = saved_errno;
}
int main() {
printf("Parent process started with PID: %d\n", getpid());
// Register the handler for SIGCHLD.
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
// SA_RESTART flag causes certain system calls (like read) to be automatically
// restarted if they are interrupted by this signal handler.
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("Error: cannot handle SIGCHLD");
exit(EXIT_FAILURE);
}
// Fork a child process.
pid_t pid = fork();
if (pid < 0) {
// Fork failed
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// --- This is the child process ---
printf("Child process (PID: %d) is running.\n", getpid());
// Simulate doing some work
sleep(2);
printf("Child process is exiting.\n");
exit(EXIT_SUCCESS);
} else {
// --- This is the parent process ---
printf("Parent process created child with PID: %d\n", pid);
printf("Parent is now doing other work. The SIGCHLD handler will reap the child.\n");
// The parent can now do other things.
// We'll just sleep and let the signal handler do its job.
sleep(5);
printf("Parent process has finished its work and is now exiting.\n");
}
return EXIT_SUCCESS;
}
Build, Flash, and Boot Procedures
1. Compile the Code:
gcc -o sigchld_handler sigchld_handler.c -Wall
2. Run and Monitor: Execute the program. While it is running, open a second terminal to monitor the process states.
In Terminal 1:
./sigchld_handler
In Terminal 2, quickly run this command repeatedly while the program is active. Replace sigchld_handler
with the PID of the child process if you can catch it fast enough. The grep
command will show the child process and its state.
ps -ef | grep sigchld_handler | grep -v grep
Expected Output:
In Terminal 1, you will see:
Parent process started with PID: 23456
Parent process created child with PID: 23457
Parent is now doing other work. The SIGCHLD handler will reap the child.
Child process (PID: 23457) is running.
Child process is exiting.
Parent process has finished its work and is now exiting.
In Terminal 2, if you are fast, you might briefly see the child process listed. After it exits, you might see it for a split second in the Z
(zombie) state, indicated by <defunct>
in some ps
outputs. However, because our SIGCHLD
handler is in place, it will be cleaned up almost instantly. If you were to comment out the sigaction
call and recompile, the child process would remain as a zombie for the entire 5-second duration of the parent’s sleep. Our handler prevents this.
This example correctly demonstrates the asynchronous nature of process management and the essential role of SIGCHLD
in maintaining a clean system state.
Common Mistakes & Troubleshooting
Signal handling is powerful, but it’s also easy to make mistakes that lead to subtle bugs. Here are some common pitfalls and how to avoid them.
Exercises
These exercises will help you solidify your understanding of signal handling on your Raspberry Pi 5.
- Handle
SIGHUP
for Configuration Reload:- Objective: Modify the
sigint_handler.c
program to catchSIGHUP
in addition toSIGINT
. - Guidance:
- Create a new signal handler function,
sighup_handler
. Inside this handler, simply print a message like “SIGHUP received. Reloading configuration…”. - In
main()
, register this new handler for theSIGHUP
signal using anothersigaction
call. - Run the program in one terminal. From a second terminal, find its PID using
pgrep sigint_handler
. - Send the
SIGHUP
signal using thekill
command:kill -HUP <PID>
.
- Create a new signal handler function,
- Verification: The program should print the “Reloading configuration” message and continue running. Pressing
Ctrl+C
should still shut it down gracefully.
- Objective: Modify the
- Basic Inter-Process Communication with
SIGUSR1
:- Objective: Write a program where a parent process sends a
SIGUSR1
signal to its child to make it perform an action. - Guidance:
- The program should
fork()
. - The child process should install a signal handler for
SIGUSR1
. The handler should print a message like “Child received SIGUSR1!”. After setting up the handler, the child should enter an infinite loop (while(1) sleep(1);
). - The parent process should sleep for 2 seconds (to give the child time to set up its handler) and then send
SIGUSR1
to the child usingkill(child_pid, SIGUSR1)
. - The parent should then wait for a few more seconds before sending
SIGKILL
to the child to terminate it, and then exit.
- The program should
- Verification: You should see the “Child received SIGUSR1!” message printed to the console.
- Objective: Write a program where a parent process sends a
- Creating a Self-Terminating Process with
SIGALRM
:- Objective: Write a program that terminates itself after a set amount of time using an alarm.
- Guidance:
- Write a signal handler for
SIGALRM
that prints “Time’s up! Exiting.” and then callsexit(0)
. - Register this handler.
- In
main()
, callalarm(5)
. This system call instructs the kernel to send aSIGALRM
signal to the process after 5 seconds. - After the
alarm()
call, enter an infinite loop that prints “Working…” every second.
- Write a signal handler for
- Verification: The program should print “Working…” five times and then print the “Time’s up!” message before exiting automatically.
- Investigating
SIGCHLD
and Zombie Processes:- Objective: Prove that failing to handle
SIGCHLD
creates a zombie process. - Guidance:
- Take the
sigchld_handler.c
code from the example. - Comment out the line
if (sigaction(SIGCHLD, &sa, NULL) == -1) { ... }
. This disables the signal handler. - Increase the parent’s
sleep(5)
tosleep(30)
to give you more time to observe. - Compile and run the modified program.
- While it’s running, use
ps aux | grep <program_name>
in another terminal.
- Take the
- Verification: After the child exits (around the 2-second mark), you should see its entry in the
ps
output marked with aZ
or<defunct>
status. This zombie process will persist until the parent process exits after 30 seconds. This demonstrates visually what theSIGCHLD
handler prevents.
- Objective: Prove that failing to handle
Summary
- Signals are Asynchronous Notifications: They are a fundamental Linux mechanism for notifying a process that an event has occurred, interrupting its normal flow of execution.
- Signal Disposition: A process can handle a signal by performing the default action, ignoring the signal, or catching it with a custom signal handler.
sigaction
is the Preferred Tool: Thesigaction()
system call provides a robust and reliable way to set a signal’s disposition, avoiding the race conditions associated with the oldersignal()
call.- Key Signals:
SIGINT
is for user interruption (Ctrl+C
),SIGTERM
is for graceful termination requests,SIGKILL
is for forced termination, andSIGHUP
is often used for configuration reloads. SIGCHLD
is Critical for Process Management: HandlingSIGCHLD
and reaping terminated child processes withwaitpid()
is essential to prevent the accumulation of zombie processes.- Signal Handlers Must Be Simple: To avoid race conditions and data corruption, handlers must be written carefully using only async-signal-safe functions. The best practice is to set a flag in the handler and act on it in the main program loop.
Further Reading
- The Linux Programming Interface by Michael Kerrisk – Chapters 20-22 provide an exhaustive and authoritative reference on signals.
- Advanced Programming in the UNIX Environment by W. Richard Stevens and Stephen A. Rago – Chapter 10 is a classic and highly respected text on signal handling.
signal(7)
Linux Manual Page – Provides a comprehensive overview of all standard signals. Access viaman 7 signal
.sigaction(2)
Linux Manual Page – The definitive documentation for thesigaction
system call. Access viaman 2 sigaction
.- POSIX.1-2017 Standard, Section 3.3, Signals – The official standard defining how signals should behave on compliant systems. Available from the IEEE/The Open Group. https://www.open-std.org/jtc1/sc22/open/n4217.pdf
- “Signals” – Beej’s Guide to Unix Interprocess Communication – A very accessible, practical tutorial on signal concepts.