A Linux signalfd becomes readable when a signal selected by its mask is pending for the reading context. A successful read(2) consumes pending signal state and returns one or more fixed-size signalfd_siginfo records. Signal handling can therefore enter a descriptor-driven event loop without turning asynchronous handlers into the primary dispatch mechanism.

The descriptor mask does not block signals

The mask passed to signalfd(2) selects signals that the descriptor can accept. It does not modify the calling thread’s signal mask. Normal use separately blocks those signals with sigprocmask(2) or pthread_sigmask(3) so their ordinary dispositions do not run before descriptor consumption.

This separation creates two related states: the thread mask controls whether delivery is blocked, while the signalfd mask controls which pending signals are eligible for descriptor reads. Reconfiguring an existing signalfd replaces its descriptor mask, but it does not alter thread masks.

SIGKILL and SIGSTOP cannot be received through signalfd. Including either in the descriptor mask has no effect, matching their special status in the signal model.

Reads consume pending signal state

When at least one selected signal is pending, read(2) returns as many complete struct signalfd_siginfo records as fit in the supplied buffer. The buffer must hold at least one record. Signals represented by a successful read are consumed and are no longer pending for later acceptance by another signalfd, sigwaitinfo(2), or a signal handler.

That consumption property matters when several signalfd objects have overlapping masks. A pending occurrence that matches more than one descriptor can be consumed through any one of them, but the same occurrence is not duplicated into every matching descriptor.

Standard and real-time signals retain their existing queuing semantics. signalfd changes the acceptance interface; it does not replace the kernel’s signal-generation, pending, coalescing, or real-time queuing rules.

Readiness joins the ordinary I/O set

select(2), poll(2), and epoll(7) can report a signalfd as readable when selected signal state is available. SFD_NONBLOCK makes an empty read fail with EAGAIN rather than wait. SFD_CLOEXEC sets close-on-exec when the descriptor is created.

This lets a process place signals beside sockets, pipes, timerfds, and other pollable sources. The shared readiness interface does not create a total ordering among those independent event sources. An event loop still defines its own dispatch and fairness policy when several descriptors are ready together.

A read may return multiple records, so one readiness notification is not equivalent to one signal record. Draining behavior depends on blocking mode, buffer capacity, and the pending set at each read.

Thread targeting remains part of signal semantics

Signal routing does not become descriptor-local. A thread reading a signalfd can accept signals directed to itself and process-directed signals available to the thread group, but not signals directed specifically to another thread.

For multithreaded programs, blocking strategy is therefore part of the design boundary. Creating one signalfd in one thread does not automatically make matching signals unavailable to handlers in threads where those signals remain unblocked.

After fork(2), a child inherits the descriptor and can read signals queued to the child. There is a narrower epoll(7) boundary: an inherited signalfd that was added to an epoll instance before the fork does not cause the child’s epoll_wait(2) to report child-directed signal readiness. Recreating and registering a signalfd after the fork avoids relying on that inherited registration behavior.

Synchronous fault signals stay outside this mechanism

signalfd is suited to asynchronously generated signals that can become pending while blocked. It cannot replace a handler for synchronous fault signals generated by the executing instruction, such as an invalid-memory SIGSEGV or arithmetic SIGFPE.

The boundary is consequently precise. signalfd converts eligible pending signal acceptance into structured descriptor I/O, while signal masks, target selection, queueing rules, synchronous faults, and process lifecycle semantics remain part of the Linux signal subsystem.