Signal / Event / IPC · Linux userspace / kernel ABI

signal mask, pending, delivery

Connects signal generation, pending queues, thread selection, masks, handler frames, and sigreturn in one flow, and summarizes handler-safety rules.

Series
22 / 38
Build
cc -std=c17 -Wall -Wextra -O2 signal_wait.c -o signal_wait
Run
./signal_wait
Kernel
Linux 6.18.37 LTS

Why do signal generation and handler execution happen at different times?

Once generated, a signal enters process-directed or thread-directed pending state. A handler or default action is applied only when a target thread does not block the signal and reaches a point where it returns from the kernel to userspace.

A handler runs by creating a sigframe on the original userspace stack and changing the instruction pointer to the handler. Returning is not completed by an ordinary function return alone; an rt_sigreturn trampoline restores registers and the mask.

Structure diagram

Figure 1. Relationships among the process pending queue, thread masks, and signal frames

Process signal state

  • shared disposition
  • process pending
  • SIGUSR1 siginfo

Thread A

  • mask: SIGUSR1 blocked
  • thread pending
  • not deliverable

Thread B

  • mask: unblocked
  • selected by get_signal
  • use user stack

rt_sigframe

  • saved registers
  • old mask
  • ucontext · return trampoline

After entering the pending queue, a process-directed signal can be delivered to any one thread that does not block it.

Call path

Figure 2. From userspace code to observable results
signal generate kill/fault/timer
pending queue process or thread
mask check select deliverable signal
sigframe save context on stack
handler/sigreturn restore after execution

Separate generation, pending, and delivery. A mask delays delivery rather than deleting a signal, and repeated standard signals may coalesce into one.

Figure 3. Major points along the kernel-internal path
send_signal sigpending enqueue
get_signal select action
arch setup_frame copy ucontext
handler EL0 restricted work
rt_sigreturn restore registers

This diagram is not for memorizing function names. Follow which return value, file descriptor, memory mapping, or wait queue is passed to the next stage.

Linux 6.18.37 LTS source locations

Go beyond the glibc function to the files where the syscall implementation meets kernel objects. Each link points to the original file at the same tag.

FileFunction / structureWhat to inspect
kernel/signal.c __send_signal_locked(), get_signal() Select from the pending queue and disposition
arch/x86/kernel/signal.c arch_do_signal_or_restart(), setup_rt_frame() Construct a userspace signal frame
arch/x86/entry/entry_64.S syscall/interrupt return Connect signal processing before return to userspace

Complete runnable example

The code below is not pseudocode with explanatory lines omitted. It is a minimal example that can be built and run as a file.

Buildcc -std=c17 -Wall -Wextra -O2 signal_wait.c -o signal_wait
01#define _POSIX_C_SOURCE 200809L
02#include <signal.h>
03#include <stdio.h>
04#include <unistd.h>
05
06int main(void)
07{
08    sigset_t set;
09    sigemptyset(&set);
10    sigaddset(&set, SIGUSR1);
11    if (sigprocmask(SIG_BLOCK, &set, NULL) < 0)
12        return 1;
13
14    printf("pid=%ld; waiting for SIGUSR1\n", (long)getpid());
15    fflush(stdout);
16
17    siginfo_t info;
18    int signo = sigwaitinfo(&set, &info);
19    if (signo < 0)
20        return 1;
21    printf("received signal=%d sender=%ld value=%d\n", signo,
22           (long)info.si_pid, info.si_value.sival_int);
23    return 0;
24}

Code notes

Source line 9sigemptyset(&set)

Creates an empty set through the API rather than assuming an internal all-0 representation for sigset_t.

Source line 11SIG_BLOCK

Blocks the target signal first so it stays in the pending queue instead of taking the default action or entering a handler.

Source line 15fflush(stdout)

Makes the PID immediately visible to the signal sender even when stdout is redirected to a pipe and is not line-buffered.

Source line 18sigwaitinfo(&set

Consumes the signal and siginfo through a synchronous function return rather than an asynchronous handler. The waited-for signal must be blocked in the calling thread.

Source line 22info.si_value.sival_int

Can read a realtime/queued value sent by sigqueue. Do not expect a meaningful payload for a standard signal sent with kill.

Detailed behavior

01

Thread selection for a process-directed signal

A signal sent to a process with kill can be delivered to any thread that does not block it. Use the pthread_kill/tgkill family to target a particular thread.

A multithreaded program can reduce handler races by blocking signals in the initial thread and consuming them in a dedicated sigwait/signalfd thread.

02

Only a limited set of functions may be called from a handler

If a handler interrupts malloc, printf, or pthread_mutex_lock while it is updating internal state, reentry can cause deadlock or corruption. Call only functions on the async-signal-safe list.

A handler usually does no more than set a sig_atomic_t flag or write to a self-pipe, deferring the real cleanup to the main loop.

03

Queueing rules for standard and realtime signals

Repeated instances of the same standard signal can coalesce into one pending bit while blocked. Realtime signals form an ordered queue with payloads, but are subject to resource limits.

Do not represent a workload that must not lose event counts as a count of standard signals.

Objects and lifetimes

ObjectCreation and releaseValues to inspect
sighand_structA thread group shares signal dispositions; caught actions are reset by exechandler, flags, mask
sigpendingExists on the process or task from signal generation through delivery/consumptionsignal bitmap, queued siginfo
rt_sigframeCreated on the user stack during delivery and consumed by sigreturnucontext, old mask, registers

Failure conditions and common misconceptions

Observed symptomLikely causesHow to verify
Signal occurrences are loststandard signal coalescingConsider a realtime signal or eventfd
handler deadlockAn async-signal-unsafe function is reenteredInspect the handler call graph and core stack
The desired thread does not receive the signalProcess-directed delivery and mask configuration/proc/PID/task/*/status SigBlk

Verify it yourself

  1. Send kill -USR1 to the running PID and verify that sigwaitinfo returns the sender PID.
  2. Send SIGUSR1 several times while it is blocked and observe SigPnd in /proc/PID/status plus one consumption.
  3. Write a sender that transmits integer payloads with sigqueue and verify the order of several values on a realtime signal.
Run./signal_wait
Tracestrace -e trace=rt_sigprocmask,rt_sigtimedwait,kill ./signal_wait

Primary sources