A periodic timerfd does not require one userspace wakeup for every timer expiration. If several expirations occur before the descriptor is read, Linux accumulates them and returns the count in one 8-byte integer.
That behavior makes timer state fit the same readiness model used for sockets, pipes, and other descriptors. It also gives delayed event loops explicit information about missed periods rather than collapsing several expirations into one notification.
Expiration state becomes readable descriptor data
timerfd_create() creates a timer object and returns a file descriptor referring to it. The selected clock defines the timer’s time base. Common choices include CLOCK_MONOTONIC, CLOCK_REALTIME, and CLOCK_BOOTTIME.
timerfd_settime() arms or disarms the timer. A nonzero it_value sets the first expiration; a nonzero it_interval makes subsequent expirations periodic. Without TFD_TIMER_ABSTIME, it_value is relative to the clock value at the call. With that flag, it is an absolute clock value.
Once one or more expirations are pending, the descriptor is readable through poll(), select(), or epoll. A successful read() supplies a host-endian uint64_t containing the number of expirations since the previous successful read or the most recent timer setting change.
uint64_t expirations;
ssize_t n = read(timer_fd, &expirations, sizeof(expirations));
if (n == sizeof(expirations)) {
process_ticks(expirations);
}The buffer must be at least 8 bytes. On a nonblocking descriptor, a read with no pending expiration fails with EAGAIN.
Periodic timers preserve overrun information
Consider a timer with a 100 ms interval. If the event loop is occupied for 470 ms after a successful read, several periods can pass before it services the descriptor again. The next read can return a value greater than one.
That count is operational state, not merely a readiness signal. Code can use it to advance a logical tick counter, account for skipped sampling periods, or detect that processing latency exceeded the timer cadence.
A loop that ignores the returned value and assumes one tick per read discards this information:
uint64_t expirations;
if (read(timer_fd, &expirations, sizeof(expirations)) == sizeof(expirations)) {
logical_tick += expirations;
}The kernel does not promise that userspace runs exactly at each requested deadline. Scheduling latency and system load can delay observation. The expiration counter records how many timer events elapsed before userspace consumed the state.
Clock choice changes suspend and wall-clock semantics
CLOCK_MONOTONIC advances monotonically but does not include time spent while the system is suspended. CLOCK_BOOTTIME is also monotonic and includes suspended time. A timer tied to CLOCK_BOOTTIME can therefore account for elapsed suspend intervals where a CLOCK_MONOTONIC timer does not.
CLOCK_REALTIME represents settable wall-clock time. Discontinuous changes to that clock can move an absolute deadline relative to the new wall-clock value. That property is appropriate for some calendar-style deadlines but differs from duration measurement.
The clock selection is therefore part of the timer’s semantics. Descriptor readiness does not erase the distinction between elapsed-time clocks and a settable civil-time clock.
Absolute real-time timers can report clock discontinuities
For an absolute timer based on CLOCK_REALTIME or CLOCK_REALTIME_ALARM, TFD_TIMER_CANCEL_ON_SET marks the timer as cancelable when the real-time clock changes discontinuously.
struct itimerspec spec = {
.it_value = deadline,
.it_interval = {0, 0},
};
int rc = timerfd_settime(
timer_fd,
TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET,
&spec,
NULL
);After a qualifying clock change, a current or later read() fails with ECANCELED. This separates a clock discontinuity from an ordinary expiration. Software that maps a wall-clock deadline to an external event can then recompute state instead of treating the old deadline as normally reached.
This cancellation behavior is specific to the eligible real-time clocks and the two flags used together. It is not a generic property of every timerfd.
Readiness and timer accounting stay in one event loop
A timerfd can be registered beside sockets and pipes in an epoll set. The event loop receives descriptor readiness, then consumes the expiration counter with read(). No signal handler is required for that timer path.
This does not make timer delivery identical to byte-stream I/O. The descriptor represents a kernel timer object, and its reads have timer-specific semantics: an 8-byte expiration count, blocking or EAGAIN when no expiration is pending, and optional ECANCELED for selected wall-clock discontinuities.
The useful boundary is precise: timerfd converts timer expiration state into descriptor readiness and a counted read result. Event multiplexing handles notification, while the returned counter preserves the number of elapsed expirations.