2026-08-29
You've seen eventfd and signalfd turn cross-thread signaling and async signals into readable file descriptors. timerfd does the same for kernel timers: instead of SIGALRM, setitimer(), or a background sleeping thread, you get an fd that becomes readable when the timer expires — pluggable straight into epoll_wait.
The API is three syscalls:
timerfd_create(clockid, flags) — returns an fd backed by a kernel hrtimer. clockid is usually CLOCK_MONOTONIC (immune to wall-clock jumps) or CLOCK_REALTIME (fires on absolute wall time; combine with TFD_TIMER_CANCEL_ON_SET to detect NTP/user setting the clock).timerfd_settime(fd, flags, new, old) — arms or disarms. it_value is the first expiration; if it_interval is nonzero, it re-arms periodically. TFD_TIMER_ABSTIME treats it_value as an absolute deadline instead of a relative delay.read(fd, &buf, 8) — returns a uint64_t: the number of expirations that occurred since the last read. This is the trick — if your event loop stalled for 100ms with a 10ms interval timer, one read returns 10, not ten separate wakeups.Real-world example: a game server or trading engine wants a 1ms tick without blocking its main epoll_wait loop. Traditional options are awful: SIGALRM interrupts arbitrary code and forces async-signal-safe handlers, while a sleeping thread costs a wakeup + IPC to notify the main loop. With timerfd:
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
struct itimerspec spec = {
.it_value = { .tv_sec = 0, .tv_nsec = 1000000 }, // first fire: 1ms
.it_interval = { .tv_sec = 0, .tv_nsec = 1000000 }, // then every 1ms
};
timerfd_settime(tfd, 0, &spec, NULL);
epoll_ctl(epfd, EPOLL_CTL_ADD, tfd, &ev);
Now the tick is just another epoll event, handled inline with your network I/O — no signal races, no extra thread.
The overrun counter matters: if you sleep past N intervals, the next read tells you exactly how many ticks you missed. For a fixed-timestep simulation, you use that number to run the physics step N times to catch up. Ignore it and you'll silently drift.
Rule of thumb: timerfd resolution follows hrtimers — sub-microsecond on modern kernels — but actual delivery latency is bounded by scheduler wake latency (typically 10–50 µs) and, on tickless kernels, by whether the target CPU is in a deep C-state (add ~50 µs for C6 exit). Don't expect a 1µs timerfd to fire in 1µs; expect it in ~20µs on average, ~100µs tail.
