signalfd(2)
๐ signalfd(2) โ the signal-accepting file descriptor, bestie ๐
okay so quick vibe check: this is the System Calls Manual, section 2, and we're talking about signalfd. no cap, this one is a banger. ๐ฆ
โจ NAME โจ
signalfd โ create a file descriptor for accepting signals
๐ LIBRARY ๐
Standard C library (libc, -lc)
๐ SYNOPSIS ๐
#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t *mask, int flags);
๐ธ DESCRIPTION ๐ธ
so signalfd() makes you a file descriptor that you can use to accept signals that are targeted at you, the caller. it's the glow-up alternative to using a signal handler or sigwaitinfo(2), and the absolutely iconic advantage is that the file descriptor can be monitored by select(2), poll(2), and epoll(7). main character energy fr. โจ
the mask argument says which set of signals you wanna accept through the file descriptor. it's a signal set, and you init its contents with the macros from sigsetops(3). normally, bestie, you should block the set of signals you're gonna receive via the file descriptor using sigprocmask(2) โ that's so they don't get handled according to their default dispositions first. and listen: you literally cannot receive SIGKILL or SIGSTOP through a signalfd file descriptor. if you put them in mask, they're silently ignored. the audacity, but okay. ๐
if the fd argument is -1, then the call creates a brand-new file descriptor and associates the signal set from mask with it. if fd is not -1, then it has to be a valid existing signalfd file descriptor, and mask replaces the signal set that was associated with that file descriptor. simple as. ๐
starting with Linux 2.6.27, you can bitwise OR these values into flags to change how signalfd() behaves:
SFD_NONBLOCKโ sets theO_NONBLOCKfile status flag on the open file description (checkopen(2)) that the new file descriptor refers to. using this flag saves you extra calls tofcntl(2)just to get the same result. efficiency queen. ๐SFD_CLOEXECโ sets the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. peep the description of theO_CLOEXECflag inopen(2)for why this is lowkey useful.
up to Linux 2.6.26, the flags argument is unused and must be specified as zero. don't get cute.
signalfd() returns a file descriptor that supports these operations:
read(2)โ if one or more of the signals inmaskis pending for the process, the buffer you handed toread(2)gets used to return one or moresignalfd_siginfostructures (scroll down for the tea โ) that describe the signals. theread(2)returns info for as many signals as are pending and will fit in the buffer you supplied. the buffer must be at leastsizeof(struct signalfd_siginfo)bytes. the return value of theread(2)is the total number of bytes read.
and here's the plot twist: as a consequence of the read(2), the signals get consumed. they're no longer pending for the process โ meaning they won't be caught by signal handlers, and they can't be accepted using sigwaitinfo(2). gone, ate, no crumbs. ๐ญ
if none of the signals in mask is pending for the process, then the read(2) either blocks until one of the signals in mask is generated for the process, or fails with the error EAGAIN if the file descriptor has been made nonblocking.
poll(2),select(2)(and similar) โ the file descriptor is readable (that's theselect(2)readfdsargument; thepoll(2)POLLINflag) if one or more of the signals inmaskis pending for the process.
the signalfd file descriptor also supports the other file-descriptor multiplexing APIs: pselect(2), ppoll(2), and epoll(7). it's giving versatile. ๐
close(2)โ when you don't need the file descriptor anymore, close it. when all file descriptors associated with the same signalfd object have been closed, the kernel frees the resources for that object. clean exit, we love to see it. ๐ซถ
๐ง The signalfd_siginfo structure ๐ง
the format of the signalfd_siginfo structure(s) that read(2) hands back from a signalfd file descriptor goes like this:
struct signalfd_siginfo {
uint32_t ssi_signo; /* Signal number */
int32_t ssi_errno; /* Error number (unused) */
int32_t ssi_code; /* Signal code */
uint32_t ssi_pid; /* PID of sender */
uint32_t ssi_uid; /* Real UID of sender */
int32_t ssi_fd; /* File descriptor (SIGIO) */
uint32_t ssi_tid; /* Kernel timer ID (POSIX timers)
uint32_t ssi_band; /* Band event (SIGIO) */
uint32_t ssi_overrun; /* POSIX timer overrun count */
uint32_t ssi_trapno; /* Trap number that caused signal */
int32_t ssi_status; /* Exit status or signal (SIGCHLD) */
int32_t ssi_int; /* Integer sent by sigqueue(3) */
uint64_t ssi_ptr; /* Pointer sent by sigqueue(3) */
uint64_t ssi_utime; /* User CPU time consumed (SIGCHLD) */
uint64_t ssi_stime; /* System CPU time consumed
(SIGCHLD) */
uint64_t ssi_addr; /* Address that generated signal
(for hardware-generated signals) */
uint16_t ssi_addr_lsb; /* Least significant bit of address
(SIGBUS; since Linux 2.6.37) */
uint8_t pad[X]; /* Pad size to 128 bytes (allow for
additional fields in the future) */
};
every field in this struct is analogous to the similarly named field in the siginfo_t structure. siginfo_t is described over in sigaction(2). and bestie, heads up: not all fields in the returned signalfd_siginfo structure will be valid for a specific signal. you figure out which set of fields is valid from the value returned in the ssi_code field. that field is the analog of the siginfo_t si_code field โ sigaction(2) has the details. iykyk. ๐
๐ fork(2) semantics ๐
after a fork(2), the child inherits a copy of the signalfd file descriptor. a read(2) from the file descriptor in the child returns info about signals queued to the child. passing the aux, but make it signals. ๐
๐ Semantics of file descriptor passing ๐
just like with other file descriptors, signalfd file descriptors can be passed to another process via a UNIX domain socket (check unix(7)). in the receiving process, a read(2) from the received file descriptor returns information about signals queued to that process. the vibes transfer. ๐ฆ
โญ execve(2) semantics โญ
exactly like any other file descriptor, a signalfd file descriptor stays open across an execve(2) โ unless it's been marked for close-on-exec (peep fcntl(2)). any signals that were available for reading before the execve(2) remain available to the newly loaded program. (this is analogous to traditional signal semantics, where a blocked signal that's pending stays pending across an execve(2).) periodt. ๐
๐ Thread semantics ๐
the semantics of signalfd file descriptors in a multithreaded program mirror the standard semantics for signals. in other words: when a thread reads from a signalfd file descriptor, it's gonna read the signals directed at the thread itself plus the signals directed at the process (i.e., the entire thread group). (a thread will not be able to read signals that are directed to other threads in the process. sorry bestie, no snooping.) ๐
๐ฅ epoll(7) semantics ๐ฅ
if a process adds (via epoll_ctl(2)) a signalfd file descriptor to an epoll(7) instance, then epoll_wait(2) returns events only for signals sent to that process. in particular, if the process then uses fork(2) to create a child process, the child will be able to read(2) signals that are sent to it using the signalfd file descriptor โ but epoll_wait(2) will not indicate that the signalfd file descriptor is ready. awkward. ๐ญ
in this scenario, a possible workaround is: after the fork(2), the child process can close the signalfd file descriptor it inherited from the parent, then create another signalfd file descriptor and add that to the epoll instance. alternatively, the parent and the child could delay creating their (separate) signalfd file descriptors and adding them to the epoll instance until after the call to fork(2). choose your fighter. ๐ซถ
๐ RETURN VALUE ๐
on success, signalfd() returns a signalfd file descriptor โ that's either a new file descriptor (if fd was -1), or fd if fd was a valid signalfd file descriptor. on error, -1 is returned and errno is set to indicate the error. slay when it works, sigh when it doesn't.
๐ ERRORS ๐
EBADFโ thefdfile descriptor is not a valid file descriptor.EINVALโfdis not a valid signalfd file descriptor.EINVALโflagsis invalid; or, in Linux 2.6.26 or earlier,flagsis nonzero.EMFILEโ the per-process limit on the number of open file descriptors has been reached.ENFILEโ the system-wide limit on the total number of open files has been reached.ENODEVโ could not mount (internal) anonymous inode device.ENOMEMโ there was insufficient memory to create a new signalfd file descriptor.
โจ VERSIONS โจ
๐งต C library/kernel differences ๐งต
the underlying Linux system call requires an additional argument, size_t sizemask, which specifies the size of the mask argument. the glibc signalfd() wrapper function does not include this argument, since it provides the required value for the underlying system call itself. glibc said "i got it, don't worry." ๐
there are two underlying Linux system calls: signalfd() and the more recent signalfd4(). the former system call does not implement a flags argument. the latter system call implements the flags values described above. starting with glibc 2.9, the signalfd() wrapper function will use signalfd4() where it's available.
๐ STANDARDS ๐
Linux.
๐ HISTORY ๐
signalfd()โ Linux 2.6.22, glibc 2.8.signalfd4()โ Linux 2.6.27.
โฆand bestie, we are not done, because there is literally a whole part 2 coming. don't go anywhere. ๐ธ
๐ธ NOTES ๐ธ
Okay bestie so here's the tea: a process can totally create multiple signalfd file descriptors. That means you can accept different signals on different file descriptors. It's giving organized. ๐
- This is highkey useful if you're monitoring the file descriptors with
select(2),poll(2), orepoll(7), because the arrival of different signals will make different file descriptors ready. โจ - And if a signal shows up in the mask of more than one of the file descriptors? Then occurrences of that signal can be read (once) from any one of the file descriptors. Not duplicated, just... whoever gets there first, iykyk. ๐
Attempts to sneak SIGKILL and SIGSTOP into mask are silently ignored. Like, the kernel literally does not hear you. ๐
The signal mask that a signalfd file descriptor is using can be viewed via the entry for the corresponding file descriptor in the process's /proc/pid/fdinfo directory. Peep proc(5) for further details. ๐
Limitations
Real talk though โ the signalfd mechanism can't be used to receive signals that are synchronously generated, like the SIGSEGV signal that results from accessing an invalid memory address, or the SIGFPE signal that results from an arithmetic error. Those babies can be caught only via signal handler. No signalfd shortcut for them, sorry. ๐ญ
And like, as described above, in normal usage you block the signals that will be accepted via signalfd(). But here's the plot twist: if you're spawning a child process to execute a helper program (one that does not need the signalfd file descriptor), then, after the call to fork(2), you will normally want to unblock those signals before calling execve(2), so that the helper program can see any signals that it expects to see. ๐ซถ
Be aware, however, that this won't be possible in the case of a helper program spawned behind the scenes by any library function that the program may call. In such cases, one must fall back to using a traditional signal handler that writes to a file descriptor monitored byselect(2),poll(2), orepoll(7). ๐
๐ BUGS ๐
Before Linux 2.6.25, the ssi_ptr and ssi_int fields are not filled in with the data accompanying a signal sent by sigqueue(3). So if you're on something ancient... the data just ain't there bestie. Old kernel behavior, fr fr.
โจ EXAMPLES โจ
The program below accepts the signals SIGINT and SIGQUIT via a signalfd file descriptor. The program terminates after accepting a SIGQUIT signal. The following shell session demonstrates the use of the program:
$ ./signalfd_demo
^C # Control-C generates SIGINT
Got SIGINT
^C
Got SIGINT
^\ # Control-\ generates SIGQUIT
Got SIGQUIT
$
Program source
#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/signalfd.h>
#include <sys/types.h>
#include <unistd.h>
int
main(void)
{
int sfd;
ssize_t s;
sigset_t mask;
struct signalfd_siginfo fdsi;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
/* Block signals so that they aren't handled
according to their default dispositions. */
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
err(EXIT_FAILURE, "sigprocmask");
sfd = signalfd(-1, &mask, 0);
if (sfd == -1)
err(EXIT_FAILURE, "signalfd");
for (;;) {
s = read(sfd, &fdsi, sizeof(fdsi));
if (s != sizeof(fdsi))
err(EXIT_FAILURE, "read");
if (fdsi.ssi_signo == SIGINT) {
printf("Got SIGINT\n");
} else if (fdsi.ssi_signo == SIGQUIT) {
printf("Got SIGQUIT\n");
exit(EXIT_SUCCESS);
} else {
printf("Read unexpected signal\n");
}
}
}
๐ SEE ALSO ๐
eventfd(2)poll(2)read(2)select(2)sigaction(2)sigprocmask(2)sigwaitinfo(2)timerfd_create(2)sigsetops(3)sigwait(3)epoll(7)signal(7)
And that's a wrap on signalfd(2) bestie!! ๐ This iconic lil page came from Linux man-pages 6.20, dated 2026-02-08. Go block some signals, reap some struct signalfd_siginfo, and stay slay. Periodt. ๐โจ๐ฆ
๐ฆ where this page comes from
- arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
- nixpkgs unstable: man-pages 6.20
- ๐ the page's license: GPL-2.0-or-later
- Copyright 2008, Michael Kerrisk <mtk.manpages@gmail.com>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms