The timerfd_create() Syscall: Turning Kernel Timers Into a Pollable File Descriptor

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:

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.

Key Takeaway: timerfd unifies timers with the fd-based event loop, and its overrun counter turns missed ticks from silent bugs into a number you can act on.

All newsletters