Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The rt_sig* Kernel ABI — rt_sigaction, rt_sigprocmask, and Friends (IF-014 – IF-016)

Almost nobody calls the rt_sig* system calls directly — and that is the point of this chapter. The glibc wrappers you use (sigaction(3), sigprocmask(3), sigsuspend(3), sigpending(3), sigtimedwait(3)) hide a per-architecture binary interface that differs in struct layout, flag-bit values, how-constant values, and the ownership of the signal trampoline. This matters when you read strace(1) output, write a libc or a runtime, implement seccomp filters, or port to a new architecture. (IF-016 is the cross-link inventory at the end: the existing per-syscall pages this chapter supplements.)

Why the rt_* syscalls exist

Per signal(7) and the VERSIONS sections of the man pages: Linux 2.0 and earlier tracked signal masks in a fixed 32-bit sigset_t. The real-time range (32–64, 128 on mips — see real-time signals) needed wider sets, so Linux 2.2 added a parallel family of syscalls taking a kernel_sigset_t of _NSIG bits plus an explicit size argument:

Old (Linux ≤ 2.0)New (Linux ≥ 2.2)
sigaction(2)rt_sigaction(2)
sigprocmask(2)rt_sigprocmask(2)
sigpending(2)rt_sigpending(2)
sigsuspend(2)rt_sigsuspend(2)
sigtimedwait(2)rt_sigtimedwait(2)
sigreturn(2)rt_sigreturn(2)

The glibc wrappers transparently call the rt_* forms where available (per sigaction(2) and sigprocmask(2) VERSIONS).

rt_sigaction(2) (IF-014)

/* no glibc wrapper — libc's sigaction(3) *is* the wrapper */
int syscall(SYS_rt_sigaction, int signum,
            const struct kernel_sigaction *act,
            struct kernel_sigaction *oldact,
            size_t sigsetsize);

The fourth argument, sigsetsize, specifies the size in bytes of the sa_mask sets in both structs; it is currently required to equal sizeof(sigset_t) (8 bytes on 64-bit, 8× the kernel long — i.e. _NSIG/_NSIG_BPW words) or the call fails EINVAL (per sigaction(2) VERSIONS). The size exists so the kernel can grow the ABI without breaking old binaries — newer kernels reject mismatched sizes instead of guessing.

The kernel struct differs per architecture

The libc struct sigaction is a POSIX-visible fiction; the kernel’s per-arch UAPI struct is what crosses the syscall boundary (all layouts below verified in the UAPI headers):

ArchitectureKernel struct sigaction field order (user-space view)Notes
asm-generic (arm64, riscv, loongarch inherit)handler; flags; restorer*; maskmask last “for extensibility”; restorer present only when the arch defines SA_RESTORER
x86-64handler; flags; restorer; maskSA_RESTORER = 0x04000000
x86 (i386)`handlersigaction(union);mask; flags; restorer`
arm`handlersigaction(union);mask; flags; restorer`
s390union; flags; restorer; maskheader comments document a historic 31-bit/64-bit split that made the uapi struct unusable — glibc has always used its own definitions
powerpchandler; flags; restorer; maskalso struct old_sigaction (32-bit mask) for the legacy syscall
m68kunion; mask; flags; restoreri386-style ordering; no SA_RESTORER bit defined
mipsflags (unsigned int!); handler; maskno restorer field at all — the SA_RESTORER functionality was removed for 2.5.48, only the bit definition remains as a placeholder; stack_t field order also differs (ss_sp, ss_size, ss_flags)
alphaunion; mask; int flagsno restorer; int flags
sparc__new_sigaction: handler; flags; restorer; mask (restorer “not used by Linux/SPARC yet”)separate __old_sigaction with 32-bit mask
shincludes asm-generic; adds SA_RESTORER 0x04000000 and struct old_sigaction

The SA_* flag values also differ per architecture — asm-generic uses SA_NOCLDSTOP 0x1, SA_NOCLDWAIT 0x2, SA_SIGINFO 0x4, … SA_ONSTACK 0x08000000, SA_RESTART 0x10000000, SA_NODEFER 0x40000000, SA_RESETHAND 0x80000000, while alpha packs ONSTACK/RESTART/NOCLDSTOP/NODEFER/RESETHAND/NOCLDWAIT/SIGINFO into bits 0–6, sparc uses 0x8/0x1/0x2/0x4/0x20/0x100/0x200, mips has SA_SIGINFO = 0x8 and SA_NOCLDWAIT = 0x10000, and parisc uses low bits 0x1–0x80 with no SA_RESTORER (all verified in the respective arch/*/include/uapi/asm/signal.h). Likewise the how constants for rt_sigprocmask are 0/1/2 (BLOCK/UNBLOCK/SETMASK) on asm-generic but 1/2/3 on alpha and mips and 1/2/4 on sparc. Conclusion for IF-014/IF-015: any code constructing these structs by hand must be written per architecture from the headers — this is exactly why libcs exist.

sa_restorer: why glibc inserts its own trampoline

Per sigaction(2) VERSIONS and sigreturn(2) NOTES: when a handler returns, control passes to a piece of user-space code called the signal trampoline, which invokes sigreturn(2)/rt_sigreturn(2) to restore the interrupted context (mask, alternate stack, registers) from the frame the kernel built on the user stack. The kernel does not provide this code itself; it is supplied per architecture:

  • Historically it lived on the user stack; contemporary systems mark the stack non-executable, so the trampoline now lives in the vDSO (e.g. x86-64) or in the C library (per sigreturn(2) NOTES).
  • Where the trampoline is in the C library, the glibc sigaction wrapper writes the trampoline’s address into act.sa_restorer and sets the SA_RESTORER flag before invoking rt_sigaction(2) (per sigaction(2) VERSIONS). Applications must never set these — the libc owns the code that matches its own unwinding and stack layout.
  • On architectures without SA_RESTORER (mips, alpha, parisc), the kernel arranges the return path itself (fixed entry points / vDSO) — another reason the struct layouts differ.

glibc also filters its reserved signals here: the sigaction wrapper refuses (EINVAL) attempts to install handlers for the two NPTL-internal real-time signals (per sigaction(2) VERSIONS; see real-time signals).

rt_sigprocmask(2) (IF-015)

int syscall(SYS_rt_sigprocmask, int how,
            const kernel_sigset_t *set, kernel_sigset_t *oldset,
            size_t sigsetsize);

Same shape as the libc call plus the trailing sigsetsize (fixed arch-specific value, per sigprocmask(2) VERSIONS). The how constants are the per-arch kernel values discussed above; glibc translates POSIX SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK. The legacy sigprocmask syscall (32-bit old_kernel_sigset_t) still exists but is deprecated. The glibc wrapper additionally silently ignores attempts to block the two NPTL-internal RT signals (per sigprocmask(2) VERSIONS) — behavior applications relying on “block everything” (sigfillset) get for free.

The book already documents the kernel side of the family; this section is the entry point to those pages (semantics at the syscall level, syscall numbers, kernel versions):

References