A periodic timerfd can expire several times before user space reads it. The next successful read() does not report only the most recent tick: it returns an unsigned 64-bit count of expirations accumulated since the previous successful read, or since the timer was configured if no read has completed yet.

That behavior makes a timer an event-loop object without converting each expiration into a signal. The descriptor becomes readable when at least one expiration is pending, and the same descriptor can participate in poll(), select(), or epoll() beside sockets, pipes, and other descriptor-backed event sources.

Readiness represents pending expirations

timerfd_create() creates a timer object and returns a file descriptor referring to it. timerfd_settime() then arms or disarms that timer and can configure either a one-shot expiration or a repeating interval.

Once one or more expirations occur, the descriptor is readable. A successful read() consumes the pending expiration count and copies a host-byte-order uint64_t value to user space. The buffer must be at least eight bytes.

For a nonblocking descriptor, a read with no pending expiration fails with EAGAIN. A blocking read waits for an expiration. This gives readiness polling and direct reads the same pending-event boundary.

uint64_t expirations;

ssize_t n = read(timer_fd, &expirations, sizeof(expirations));
if (n == sizeof(expirations)) {
    handle_expirations(expirations);
}

The count matters when scheduling latency or other work prevents the process from servicing the descriptor at every interval. A value greater than one records that multiple timer periods elapsed before consumption.

The selected clock defines timer progression

The clockid passed to timerfd_create() determines the time base. CLOCK_MONOTONIC advances monotonically but excludes time spent while the system is suspended. CLOCK_BOOTTIME is also monotonic and includes suspended time.

CLOCK_REALTIME follows the settable wall clock. It therefore has different semantics from monotonic clocks when system time changes discontinuously. Alarm variants can wake a suspended system, subject to the required CAP_WAKE_ALARM capability.

These clock properties belong to the selected Linux clock, not to descriptor readiness itself. timerfd exposes expiration state through a descriptor while retaining the semantics of its configured clock.

Relative and absolute deadlines use the same descriptor

By default, timerfd_settime() interprets it_value relative to the current value of the timer’s clock. With TFD_TIMER_ABSTIME, it_value instead names an absolute point on that clock.

The interval is independent of that choice. A nonzero it_interval produces repeated expirations after the initial deadline, while a zero interval makes the timer one-shot. Setting it_value to zero disarms the timer.

struct itimerspec spec = {
    .it_value = { .tv_sec = 2, .tv_nsec = 0 },
    .it_interval = { .tv_sec = 1, .tv_nsec = 0 },
};

timerfd_settime(timer_fd, 0, &spec, NULL);

This configuration first expires after two seconds and then once per second according to the timer’s clock. If three periodic expirations become pending before the next successful read, that read can return 3 rather than requiring three separate readiness events.

Wall-clock cancellation is explicit

An absolute timer based on CLOCK_REALTIME or CLOCK_REALTIME_ALARM can request TFD_TIMER_CANCEL_ON_SET together with TFD_TIMER_ABSTIME. A discontinuous change to the real-time clock then marks the timer as canceled, and a current or future read() reports ECANCELED.

That mechanism separates a clock discontinuity from an ordinary expiration. Software using a wall-clock deadline can treat clock replacement as a state transition instead of silently accepting a deadline computed against an earlier wall-clock mapping.

The cancellation flag does not apply generally to every timerfd clock. Its documented use is tied to absolute timers on the real-time clock variants.

Descriptor lifetime and process boundaries follow file semantics

After fork(), the inherited descriptor refers to the same underlying timer object as the parent’s corresponding descriptor. Reads from either process observe expirations of that shared timer object.

Across execve(), a timer descriptor remains open unless close-on-exec is set. TFD_CLOEXEC can be supplied at creation so the descriptor is closed atomically during a successful exec transition.

When all descriptors referring to the timer object are closed, the kernel disarms the timer and releases its resources. This lifetime model lets timer ownership follow descriptor ownership rather than a separate signal registration.

Expiration counts preserve elapsed timer events, not execution slots

A periodic timer does not reserve one callback execution for every interval. It records expirations, exposes readiness, and lets a read retrieve the accumulated count. Event-loop code can then decide whether a count of five means five units of work, one coalesced update, or a missed-deadline condition.

That distinction is central to timerfd: kernel timer progression and user-space service cadence remain separate. Descriptor readiness signals pending state, while the 64-bit read value preserves the number of expirations represented by that state.