Linux System Programming · intermediate · ~20 min

eventfd, timerfd, signalfd — synthetic fds

Feed signals, timers, and inter-thread wakeups into a single epoll loop.

Overview

eventfd is a per-process counter with a fd interface. timerfd is a periodic or one-shot timer that delivers via a fd. signalfd lets signals arrive on a fd instead of via async-unsafe handlers. All three are epoll-friendly.

Why it matters

Modern Linux C servers feed everything into one epoll loop. eventfd is how worker threads wake the loop. timerfd handles timeouts. signalfd replaces the entire volatile sig_atomic_t flag = 1 dance.

Core concepts

eventfd: maintains a 64-bit counter. write(fd, &n, 8) adds n; read(fd, &out, 8) returns the counter and zeroes it (or, in EFD_SEMAPHORE mode, decrements by 1).

timerfd_create + timerfd_settime: arm a one-shot or periodic timer. read blocks until it fires; returns the number of expirations.

signalfd: block the signals you care about with sigprocmask first, then signalfd makes them readable as struct signalfd_siginfo records. NO async-signal-safety constraints — you handle them in normal code.

Pentester mindset. A signalfd that doesn't first block the signals via sigprocmask will race — the default handler still runs. Audit checklist: block-then-signalfd is the pattern; anything else is a bug.

Defensive coding habit. Always create with _CLOEXEC flag so fds don't leak across exec. Always read the 8-byte payload — a short read errors out.

Syntax notes

#include <sys/eventfd.h>
int eventfd(unsigned init, int flags);
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
int timerfd_settime(int fd, int flags, const struct itimerspec *new, struct itimerspec *old);
#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t *mask, int flags);

Lesson

Three Linux-specific syscalls let you turn ordinarily-async things (a timer firing, a signal arriving, another thread saying 'hey wake up') into file-descriptor-readable events. Combined with epoll this gives you one unified event loop — no signal handlers, no pselect, no condition variables.

Code examples

int ev = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
/* In thread A: */ uint64_t n = 1; write(ev, &n, 8);
/* In epoll loop: */ uint64_t got; read(ev, &got, 8);  /* clears the counter */

Line by line

/* One-shot timer in 100 ms: */
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
struct itimerspec spec = {0};
spec.it_value.tv_sec  = 0;
spec.it_value.tv_nsec = 100 * 1000 * 1000;
timerfd_settime(tfd, 0, &spec, NULL);
/* In epoll loop, when tfd is readable: */
uint64_t exp; read(tfd, &exp, 8);   /* exp = number of times the timer fired */

Common mistakes

  • Calling read() on an eventfd with a buffer smaller than 8 bytes (errors out).

Debugging tips

ls -l /proc/PID/fd/* shows each fd's target. eventfd/timerfd appear as anon_inode:[eventfd] etc.

Memory safety

Each of these is a real fd; close it. Always read exactly 8 bytes — short reads are EINVAL.

Real-world uses

systemd, kubelet, every modern Linux daemon written in C/C++/Go that integrates timers and signals into an epoll loop.

Practice tasks

  1. eventfd as a thread-wakeup primitive. 2. timerfd one-shot drain. 3. signalfd for SIGINT inside the loop.

Summary

Turn timers, signals, and wakeups into fds. One epoll loop owns them all.

Practice with these exercises