Skip to content

sigaction

The POSIX interface for examining or replacing one signal's process-wide disposition together with its handler-time mask and behavioral flags, while optionally returning the prior action as a restorable state.

Version
v2 · 2026-09-06 · History
Domain-specific #
2778
Origin domain
POSIX systems programming
Subdomain
asynchronous signal handling
Aliases
Sigaction(), POSIX sigaction

Core Idea

sigaction is the POSIX interface for examining and changing the action associated with one signal. The application selects a signal number and supplies a struct sigaction that couples three decisions: the disposition to take when the signal is delivered, the additional signals to block while a catching function runs, and flags that modify delivery or aftermath. The call may also return the previous action, allowing state to be inspected, replaced, and later restored.[1]

Its basic signature is:

#include <signal.h>

int sigaction(int sig,
              const struct sigaction *restrict act,
              struct sigaction *restrict oact);

If act is non-null, it specifies the new action. If oact is non-null, it receives the prior action. If act is null, the call queries without changing the action. Successful replacement returns zero; failure returns -1, sets errno, and installs no new catching function. The standard structure contains at least a handler choice, sa_mask, sa_flags, and the alternate three-argument sa_sigaction callback selected by SA_SIGINFO.[1]

The important abstraction is not “register a callback.” A POSIX signal is an asynchronous operating-system event with a process-wide disposition, per-thread delivery masks, special uncatchable signals, constrained handler execution, inherited/reset state across fork() and exec, and effects on interrupted functions. sigaction packages the portion of that contract attached to one signal. Its state persists until explicitly changed, reset through SA_RESETHAND, or affected by an exec operation under POSIX rules.[2][3]

The locked identity is:

one POSIX signal number + current process-wide action + replacement or query operation + default/ignore/catch disposition + handler-time supplemental mask + semantic flags + optional prior-action return + persistence and failure guarantees + async-signal-safe handler boundary -> a sigaction configuration

This contract recurs in portable Unix-like systems programming for termination requests, timers, child-state notification, fault reporting, and user-defined signals. It survives as a domain-specific abstraction rather than a mere function name because the same roles support diagnostics, safe examples, portability reasoning, and failure analysis across applications and implementations. Generic Interface or callback registration does not entail its signal semantics.

Structural Signature

The recurring workflow is:

choose the signal → decide default, ignore, simple catch, or information-rich catch → initialize the handler-time mask → choose only justified flags → install while optionally capturing the old action → keep the handler within the async-signal-safe envelope → coordinate with normal-flow code → restore or replace the action when ownership ends

The mandatory roles are:

  • Signal selector. sig identifies exactly one POSIX signal. SIGKILL and SIGSTOP cannot be caught or ignored, and attempts to install such actions fail or are constrained as the standard specifies.
  • Current process disposition. The action is a process attribute shared across threads, not a private callback belonging to the thread that called sigaction.[3]
  • Disposition choice. With SA_SIGINFO clear, sa_handler is SIG_DFL, SIG_IGN, or a one-argument catching function. With SA_SIGINFO set, sa_sigaction designates a three-argument catcher receiving siginfo_t and context. Storage for the two handler members may overlap, so an application must not use both simultaneously.[1]
  • Handler-time mask. sa_mask adds signals to the executing thread's mask while the catcher runs. The delivered signal is also blocked by default, unless SA_NODEFER changes that behavior. This temporary mask is distinct from the ordinary per-thread mask managed by pthread_sigmask() or sigprocmask().
  • Behavioral flags. Flags select semantics such as information-rich delivery (SA_SIGINFO), restart behavior for some interrupted interfaces (SA_RESTART), alternate-stack execution (SA_ONSTACK), one-shot reset (SA_RESETHAND), same-signal reentrancy (SA_NODEFER), and SIGCHLD treatment (SA_NOCLDSTOP, SA_NOCLDWAIT). Availability and exact effects must be checked against POSIX and implementation documentation.
  • Prior-state channel. oact lets a caller retrieve the previous complete action. That enables query, scoped replacement, diagnostics, and restoration, but does not by itself solve concurrent ownership races among libraries or threads.
  • Installation result. The call's success/failure result determines whether the new contract became active. Code must not proceed as though installation succeeded after -1.
  • Delivery semantics. When an unblocked matching signal is delivered, the system applies the installed disposition. For a catcher it adjusts the mask, constructs handler context, invokes the function, and ordinarily restores context when the handler returns.[3]
  • Handler safety envelope. Asynchronous delivery can interrupt code while library invariants or locks are in transient states. The catcher therefore calls only async-signal-safe operations or uses narrowly justified language/implementation guarantees; ordinary I/O, allocation, locking, and most application logic remain outside.[1][4]
  • Lifecycle rules. fork() copies dispositions. An exec operation resets caught dispositions to defaults while ignored dispositions remain ignored under the defined rules. Libraries must account for process-wide ownership rather than treating handlers as local resources.

The recognition test asks whether all three action dimensions—disposition, handler-time mask, and flags—are installed or queried for one signal under POSIX lifecycle and safety rules. If a mechanism only sends a signal, changes a thread mask, waits synchronously, or registers an ordinary event-loop callback, it is outside the node.

What It Is Not

sigaction is not a signal and does not generate one. kill(), raise(), pthread_kill(), sigqueue(), timers, terminals, and the kernel generate signals through separate mechanisms.

It is not the handler body. The interface installs a function pointer and semantics; the application implements the catcher and bears responsibility for async-signal-safety, state coordination, and termination behavior.

It is not a thread's signal mask. Dispositions are process-wide, while each thread has an independent mask. pthread_sigmask() and related operations determine which threads can receive which signals.[3]

It is not synchronous signal acceptance. sigwait(), sigwaitinfo(), and sigtimedwait() accept blocked signals in ordinary control flow, while Linux signalfd() exposes them through a file descriptor. POSIX warns that using sigaction and a sigwait function concurrently on the same signal has unspecified results.[1]

It is not an arbitrary callback or closure. A C signal-catching function does not capture a lexical environment, can arrive between ordinary instructions, and is constrained by a kernel-defined calling and safety contract. The live Closure (programming) node is therefore not coverage.

It is not the ISO C signal() interface. POSIX specifies signal() but recommends sigaction() as the more comprehensive and reliable mechanism. Historical claims that signal() always resets before every handler call are not portable universal truths; its semantics depend on the governing language and platform contract.[5]

Scope of Application

The abstraction applies to POSIX-conforming C interfaces and closely compatible Unix-like systems. It supports program responses to interactive termination, hangup, child-state changes, timers, asynchronous I/O notifications, user signals, and selected synchronous faults. The portable core is the POSIX contract; Linux's libc wrapper and underlying rt_sigaction system call, implementation-specific flags, and architecture-specific restorer details are variants rather than the definition.[6]

Typical uses include establishing a minimal SIGTERM/SIGINT catcher that requests orderly shutdown, receiving siginfo_t metadata with SA_SIGINFO, controlling whether selected operations restart after a catcher through SA_RESTART, suppressing unwanted SIGCHLD stop notifications, and arranging an alternate stack for a handler with sigaltstack() plus SA_ONSTACK.

It is often the wrong high-level coordination primitive in a multithreaded application. A common design blocks selected asynchronous signals in all worker threads and dedicates one thread to sigwait(), letting ordinary synchronized code process them. That alternative does not diminish sigaction's identity; it marks the boundary between asynchronous catcher installation and synchronous acceptance.

Clarity

Four objects must remain separate:

  1. Disposition: default, ignore, or catch; one per signal per process.
  2. Ordinary signal mask: per-thread state controlling whether delivery is currently blocked.
  3. sa_mask: additional temporary blocking while this particular catcher executes.
  4. Pending set: signals generated but not yet delivered because they are blocked or awaiting selection.

Many programming errors come from collapsing them. Installing a catcher does not unblock the signal. Blocking a signal does not change its disposition. Adding a signal to sa_mask does not permanently block it after normal catcher return. A pending standard signal is generally a recorded occurrence rather than an unbounded event queue, while POSIX real-time signal rules differ.[2][3]

The two function-pointer members also require a strict diagnostic. If SA_SIGINFO is clear, initialize and use sa_handler. If it is set, initialize and use sa_sigaction. Do not assign both merely because both names appear in a struct rendering; their storage may overlap.

Manages Complexity

Signals combine asynchronous timing with inherited process state and interrupted control flow. sigaction reduces that problem to an inspectable record. Reviewers can ask: which signal, which disposition, which temporary exclusions, which semantic flags, what previous owner, and what safe handoff to normal code? Without the record, behavior is scattered among global state, platform defaults, and implicit timing assumptions.

The optional old-action output supports reversible configuration. A library or scoped subsystem can capture the prior action, install its own, and restore the exact prior record rather than guessing a default. Yet the abstraction also makes the limitation visible: because the state is process-wide, two independent owners can interleave capture/install/restore and overwrite each other. Safe ownership needs a program-level policy or serialization beyond the system interface.

The handler mask turns reentrancy from an accidental race into an explicit policy. A catcher can block related signals during its critical minimal section, while SA_NODEFER deliberately permits same-signal recursion. The flags expose other hidden policy choices—restart versus EINTR, normal versus alternate stack, persistent versus one-shot action—so portability review can focus on actual commitments.

Abstract Reasoning

The action can be modeled as a state tuple

\[ A_s=(D_s,M_s,F_s), \]

where \(D_s\) is the disposition for signal \(s\), \(M_s\) is the supplemental catcher mask, and \(F_s\) is the flag set. A successful installation changes \(A_s\) to the supplied tuple and can return the old tuple. An unsuccessful call leaves the requested replacement uninstalled. Query is the special case act == NULL; replacement without observation uses oact == NULL.

On delivery to a catcher, the executing thread's effective blocked set becomes approximately

\[ B' = B \cup M_s \cup \{s\}, \]

with \(\{s\}\) omitted when SA_NODEFER applies and unmaskable signals excluded by the system. This equation predicts nested-delivery hazards: removing the self-block makes the handler recursively reentrant, while an overbroad sa_mask delays unrelated handling.

SA_RESTART does not mean “all system calls can never return EINTR.” POSIX and operating systems classify interfaces differently. Correct code still documents which calls restart and handles interruption where required.[3]

Because the disposition is process-wide and the mask is thread-local, changing the action in one thread changes what every eligible thread would execute, while changing one thread's mask changes delivery eligibility only for that thread. This cross-product is the central multithreaded diagnostic.

Knowledge Transfer

The exact contract transfers across POSIX applications and conforming implementations. A maintainer can review any sigaction site with the same checklist: initialize every relevant field, choose the correct handler member, initialize sa_mask, justify each flag, check the return, preserve prior state when ownership is temporary, and audit everything reachable from the catcher for async-signal-safety.

The design pattern also transfers among signals. SIGTERM may request shutdown, SIGCHLD may notify child-state change, and a timer signal may set a flag or write to a pipe. The application-specific response changes; the action tuple and lifecycle stay fixed.

Outside POSIX systems programming, “register a handler with options” is only analogy. Event listeners, GUI callbacks, interrupt vector tables, exception handlers, and message consumers have different delivery, state, concurrency, and safety contracts. Their portable residue belongs to Interface, Callback, State and State Transition, and generic exclusion/masking structures rather than to sigaction.

Examples

Minimal termination handoff. A program installs a SIGTERM catcher whose only work is setting a volatile sig_atomic_t flag, or writing one byte to a pre-created pipe using an async-signal-safe write(). sa_mask is initialized explicitly, no unneeded flags are set, and the return value is checked. The normal event loop observes the flag or pipe and performs logging, allocation, cleanup, and orderly shutdown outside the catcher. The roles are signal selector, persistent caught disposition, minimal handler, mask, installation result, and safe handoff.

Information-rich child notification. A supervisor installs sa_sigaction for SIGCHLD with SA_SIGINFO, perhaps adding SA_NOCLDSTOP when stopped children are irrelevant. The catcher records or forwards only safe minimal information, while normal control flow calls waitpid() in a loop to reap all available children. The loop matters because ordinary signals may coalesce; one delivery is not proof of one child transition. The example differs from the frozen page's unsafe printf()-inside-handler pattern.

Interrupted input policy. A command-line program catches a signal and chooses SA_RESTART so selected blocking interfaces resume after the handler returns. Another program omits the flag because it wants an interruptible wait and handles EINTR explicitly. Both instantiate sigaction; the flag makes the interruption policy visible. Neither may assume that the flag controls every interface identically.[3]

Alternate-stack fault reporting. A diagnostic subsystem allocates an alternate signal stack with sigaltstack() and installs a catcher using SA_ONSTACK. This can preserve some ability to report a stack-exhaustion fault, but sigaction alone does not create the alternate stack and a catcher cannot safely perform arbitrary symbolization or I/O. The example marks the boundary between an action flag and the separate resource it presupposes.

Scoped replacement with conflict risk. A library queries and saves oact, installs a temporary action, and later restores the saved tuple. This is reversible in a single-owner context. If another component changes the same process-wide action in between, blind restoration overwrites the newer owner. The interface supplies prior state; it does not supply ownership arbitration.

Structural Tensions

Immediate asynchronous response versus safe minimalism. Catchers run at the moment of delivery, which is valuable for urgent notification, but the interrupted code may hold internal locks or partially update shared state. The safest catcher does almost nothing and hands work to normal flow.

Process-wide disposition versus modular ownership. One action per signal gives the kernel an unambiguous rule. Libraries, runtimes, profilers, crash reporters, and applications may all want the same signal, creating last-writer and restoration conflicts. A program-wide signal policy is often necessary.

Blocking for invariants versus delivery latency. sa_mask can prevent related catchers from observing inconsistent state, but an overbroad mask delays important signals. SA_NODEFER lowers latency at the cost of recursive reentrancy.

Restart convenience versus explicit cancellation. SA_RESTART reduces incidental EINTR handling, while omitted restart semantics make a signal an intentional escape from a blocking call. Portability requires auditing each affected interface rather than treating either choice as universal.

Rich context versus portability and safety. SA_SIGINFO exposes origin and context fields useful for diagnostics. Field availability depends on signal cause and platform, and the extra information does not expand the set of safe handler operations.

Reversible replacement versus concurrent races. oact makes restoration possible, yet capture and later restore are not a transaction over other components' changes. Exact state recovery and cooperative ownership pull in opposite directions.

Structural–Framed Character

sigaction is highly structured and strongly domain-framed. Its tuple of disposition, mask, flags, prior state, and lifecycle is stable across conforming systems and supports reasoning independently of one application's code. It is more than a token in a header file.

The frame is nevertheless constitutive: POSIX signal numbers, process-wide dispositions, per-thread masks, asynchronous delivery, fork/exec inheritance, C function pointers, and async-signal-safety define the identity. Removing them leaves the broader notion of a configurable interface or handler registry. The node is therefore domain-specific, not prime.

Structural Core vs. Domain Accent

The structural core is named event class → query/replace action tuple → temporary exclusion policy → semantic modifiers → persistent state → optional prior-state restoration → constrained asynchronous execution. This explains why installation, delivery, and handler safety must be reviewed together.

The domain accent is constitutive: the event is a POSIX signal; the action is a process disposition; the exclusions are signal sets; delivery modifies a thread mask; flags have standardized names; and lifecycle crosses fork() and exec. An ordinary callback registry does not inherit those commitments.

Interface captures the boundary contract between application and operating system. Callback captures the advance handoff of a catcher for later foreign invocation. State and State Transition captures the replacement of one action tuple with another. Generic masking captures deferred delivery, and interrupt handling captures execution outside ordinary control flow. sigaction retains their POSIX-specific composition.

Interface is the minimal live parent. sigaction is a rule-governed boundary through which an application declares to the operating system how one signal is to be handled and receives a success result plus optional prior state. The proposed relation is composition / part_of / strict: the POSIX operation is an interface instance, while Interface does not entail signals, handler masks, flags, or lifecycle semantics.

Callback describes advance registration of a catcher for later invocation, and State and State Transition describes replacement of \(A_s\). Generic masking describes deferred delivery and handler-time exclusions, while interrupt handling describes asynchronous diversion and return. They remain prose relations because one minimal parent is sufficient. Closure (programming) is a contrast, not a parent: a C signal catcher carries no captured lexical environment.

Relationships to Other Abstractions

Local relationship map for sigactionParents appear above the current abstraction, mutual partners to the right, and children below. Node labels state whether each abstraction is prime or domain-specific; colors identify relation types.sigactionDOMAINPrime abstraction: Interface — is part ofInterfacePRIME

Current abstraction sigaction Domain-specific

Parents (1) — more general patterns this builds on

  • sigaction is part of Interface Prime

    Interface is the minimal live parent.

Hierarchy path (1) — routes to 1 parentless root

Neighborhood in Abstraction Space

sigaction sits in a sparse region of the domain-specific corpus (90th percentile for distinctiveness): few abstractions share its structure, so a faithful description tends to retrieve it precisely.

Family — Software Dependency & Coordination Failures (5 abstractions)

Nearest neighbors

Computed from structural-signature embeddings · 2026-09-08

Not to Be Confused With

  • Signal disposition: the process state that sigaction examines or changes; the interface is not the state itself.
  • signal(): the simpler ISO C/POSIX interface. POSIX recommends sigaction() for comprehensive and reliable control.[5]
  • sigprocmask() / pthread_sigmask(): interfaces for ordinary thread signal masks, not process-wide actions.
  • sigwait() / sigwaitinfo() / sigtimedwait(): synchronous acceptance of blocked signals in normal control flow.
  • signalfd(): a Linux-specific file-descriptor delivery interface, outside portable POSIX sigaction identity.
  • sigaltstack(): allocation/registration of an alternate signal stack; SA_ONSTACK only requests its use.
  • kill() / raise() / sigqueue(): signal-generation interfaces.
  • Handler implementation: application code invoked by a caught signal, subject to async-signal-safety constraints.
  • Hardware interrupt registration: a kernel or embedded-system mechanism with different privilege, context, and delivery rules.
  • Exception handling: synchronous language or hardware control transfer; POSIX signals and C++ exceptions are not generally interchangeable.
  • Closure (programming): code plus a captured lexical environment. A signal handler is a constrained function pointer, not a closure.
  • Progressive-Disclosure Failure: the frozen semantic leader is a lexical/embedding false neighbor involving hidden complexity in presentation; it shares no POSIX signal-action semantics.

References

[1] The Open Group and IEEE (2024). sigaction — examine and change a signal action.” POSIX.1-2024, System Interfaces. Normative interface, structure members, persistence, query/replacement behavior, errors, flags, and application usage. registry ↩a ↩b ↩c ↩d ↩e

[2] The Open Group and IEEE (2024). “Signal Concepts.” POSIX.1-2024, General Information. Normative generation, delivery, action, masking, pending-state, and async-signal-safety rules. registry ↩a ↩b

[3] Linux man-pages project (2026). signal(7) — overview of signals.” Linux man-pages 6.18. Dispositions, per-thread masks, delivery, handler-frame construction, inheritance, real-time behavior, and interrupted calls. registry ↩a ↩b ↩c ↩d ↩e ↩f ↩g

[4] Linux man-pages project (2026). signal-safety(7) — async-signal-safe functions.” Linux man-pages 6.18. Authoritative implementation-oriented list and explanation of safe operations and libc deviations. registry

[5] The Open Group and IEEE (2024). signal — signal management.” POSIX.1-2024, System Interfaces. Authoritative comparison stating that sigaction() is the more comprehensive and reliable preferred interface. registry ↩a ↩b

[6] Linux man-pages project (2026). sigaction(2) — examine and change a signal action.” Linux man-pages 6.18. Linux realization, flags, wrapper/kernel boundary, errors, and implementation notes. registry

[7] Stevens, W. R., and Rago, S. A. (2013). Advanced Programming in the UNIX Environment, 3rd ed. Addison-Wesley. Authoritative systems-programming treatment of reliable signals, sigaction, masks, interrupted calls, and handler design. registry