Linux - Signals
Linux signals are an asynchronous inter-process communication (IPC) mechanism used by the kernel to notify a process that a specific event has occurred (such as a hardware exception, a terminal interrupt, or a custom event).
Core Architecture & Concepts
When a signal is sent to a process, it transitions through three primary stages:
+-----------------+ +-----------------+ +-------------------+
| Signal Sent | ---> | Signal Pending | ---> | Signal Delivered |
| (kill / kernel) | | (Pending Bitset)| | (Handler/Action) |
+-----------------+ +-----------------+ +-------------------+
- Generation: A signal is generated by the kernel (e.g., page fault for
SIGSEGV) or sent by another process via systems calls likekill(2)ortgkill(2). - Pending: The kernel updates the task's pending signal set (
sigpending). If the signal is blocked by the thread's signal mask (sigprocmask), it remains pending until unblocked. - Delivery: When the thread is about to return from kernel mode back to user mode (e.g., after a system call or timer interrupt), the kernel checks for unblocked pending signals and executes the configured handler.
Signal Configuration: sigaction
The standard POSIX interface for signal handling is sigaction(2), which replaces the legacy signal() function.
#include <signal.h>
struct sigaction sa;
sa.sa_sigaction = my_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigaction(SIGUSR1, &sa, NULL);
sa_handlervssa_sigaction:sa_handler: Traditional handler function pointer taking a single argument:void (*sa_handler)(int).sa_sigaction: Advanced handler pointer used whenSA_SIGINFOflag is set. It takes three parameters:void (*sa_sigaction)(int sig, siginfo_t *info, void *ucontext);
sa_mask: A set of signals that are automatically blocked during the execution of the signal handler to prevent recursive re-entrancy issues.sa_flags: Flags modifying signal behavior:SA_SIGINFO: Enables extra payload details insiginfo_tanducontext_t.SA_NODEFER: Prevents automatically blocking the signal currently being handled.SA_RESTART: Automatically restarts certain interrupted system calls (e.g.,read,write).SA_ONSTACK: Tells the kernel to execute the handler on an alternate signal stack configured viasigaltstack.
Alternate Signal Stacks: sigaltstack
When a thread triggers a SIGSEGV due to stack overflow, the execution stack pointer (RSP/ESP) has hit its limit or invalid memory. If the kernel attempts to push a signal frame onto the current user stack, it triggers another memory fault, causing a kernel crash or immediate process crash before any handler can run.
sigaltstack(2) allows a process to designate a separate region of memory (usually allocated via malloc or mmap) dedicated strictly to signal handlers.
stack_t ss;
ss.ss_sp = malloc(SIGSTKSZ); // Alternate stack memory space
ss.ss_size = SIGSTKSZ;
ss.ss_flags = 0;
if (sigaltstack(&ss, NULL) == 0) {
// Registered stack; handlers registered with SA_ONSTACK will now use this area
}
When a signal arrives for a handler registered with SA_ONSTACK, the kernel switches the thread's stack pointer to ss.ss_sp + ss.ss_size before jumping into user space.
Signal Trampoline (sigtramp) & Delivery Lifecycle
Executing a user-space C function from the kernel requires safely jumping into user space and then returning control to the kernel once the function finishes.
Signal Control Flow
[ Kernel Space ]
|
1. Detect Pending Unblocked Signal
2. Setup User Frame (Save context + inject trampoline return address)
|
v
[ User Space ]
3. Execute Signal Handler (`sighandler`)
|
4. Return to Trampoline Code (`sigtramp`)
5. Call `rt_sigreturn()` system call
|
v
[ Kernel Space ]
6. Restore original user context (Registers, PC, Stack)
7. Resume normal process execution
Dynamic Breakdown of the Lifecycle
- Kernel Trap / Transition: A process is running in kernel space (returning from a syscall or interrupt).
- Signal Detection: Before returning to user space, the kernel checks the task struct for pending, unblocked signals.
- Stack Frame Injection:
- The kernel constructs a
ucontext_tstructure containing the saved CPU registers, register states, and signal mask on the user stack (or altstack). - It sets the return address on the user stack (or in a register like
RA/LR) to point to a trampoline instruction sequence (sigtramp).
- Jump to Handler: The kernel updates the program counter (
RIP/EIP) to point to the address ofsighandlerand returns to user mode. - Handler Execution: The signal handler (
sighandler) executes normally in user space. - Trampoline Jump (
sigtramp): Whensighandlerreturns, execution pops off the return address, hitting thesigtrampcode (often provided byglibcor the Linux vDSO). - System Call
rt_sigreturn: Thesigtrampexecutes a specific system call (e.g.,mov $15, %rax; syscallon x86-64 forsys_rt_sigreturn). - Context Restoration: The kernel receives the
rt_sigreturnsyscall, inspects the saveducontext_tframe on the user stack, restores the CPU registers to their pre-signal state, and resumes normal program execution.
Signal Safety Considerations
Because signal handlers interrupt execution at arbitrary instruction boundaries, they can cause deadlocks or state corruption if they call non-reentrant functions.
- Async-Signal-Safe Functions: Only call functions guaranteed to be async-signal-safe by POSIX (e.g.,
write,_exit,read,signal,sigaction). - Non-Safe Operations: Do not call dynamic memory allocation (
malloc,free), standard I/O functions (printf), or lock-acquiring operations inside a signal handler. - Volatile Flags: If setting a flag inside a handler to be read by the main thread, use volatile atomic types:
volatile sig_atomic_t flag = 0;