Signal / Event / IPC · Linux userspace / kernel ABI

timerfd and eventfd

Represents timer expiration and cross-thread wakeups as 8-byte counter fds integrated into an epoll loop, then examines counter accumulation and overflow rules.

Series
24 / 38
Build
cc -std=c17 -Wall -Wextra -O2 fd_counters.c -o fd_counters
Run
./fd_counters
Kernel
Linux 6.18.37 LTS

When several timer events are delayed, how many expirations does one read represent?

A timerfd read returns in a uint64_t not one event, but the number of expirations accumulated since the last read. Even if the event loop wakes late, it can determine how many periods elapsed. eventfd also uses a uint64 counter to turn thread/process wakeup credits into fd readiness.

Both use an exact 8-byte read/write rule. Treating them like streams with arbitrary buffer lengths, or treating one readiness notification as exactly one occurrence, loses accumulated information.

Structure diagram

Figure 1. Accumulated periodic-timer expirations and an 8-byte read
0 ms timer arm interval=100 ms
100 tick counter=1
200 tick counter=2
300 tick counter=3
400 tick counter=4
500 tick counter=5
550 read uint64 returns 5 · counter=0

If the event loop does not read for 550 ms, expirations from the 100 ms timer accumulate in the counter. One read consumes the accumulated value.

Call path

Figure 2. From userspace code to observable results
timer/event write increment counter
fd readable counter > 0
epoll_wait join other I/O
read uint64 consume accumulated value
policy catch up or skip

Separate readiness count from actual event count. After reading the accumulated counter, the application decides whether to catch up every tick or compute only the latest state.

Figure 3. Major points along the kernel-internal path
hrtimer/eventfd change counter/state
wake_up poll wait queue
ep_poll_callback link as ready
read 8-byte copy
counter reset/sub apply read mode

This diagram is not for memorizing function names. Follow which return value, file descriptor, memory mapping, or wait queue is passed to the next stage.

Linux 6.18.37 LTS source locations

Go beyond the glibc function to the files where the syscall implementation meets kernel objects. Each link points to the original file at the same tag.

FileFunction / structureWhat to inspect
fs/timerfd.c timerfd_settime(), timerfd_read() hrtimer expiration count and 8-byte return
fs/eventfd.c eventfd_write(), eventfd_read() Counter increment/consumption and semaphore mode
kernel/time/hrtimer.c hrtimer_start_range_ns() monotonic timer scheduling

Complete runnable example

The code below is not pseudocode with explanatory lines omitted. It is a minimal example that can be built and run as a file.

Buildcc -std=c17 -Wall -Wextra -O2 fd_counters.c -o fd_counters
01#define _GNU_SOURCE
02#include <poll.h>
03#include <stdint.h>
04#include <stdio.h>
05#include <sys/timerfd.h>
06#include <time.h>
07#include <unistd.h>
08
09int main(void)
10{
11    int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
12    if (fd < 0)
13        return 1;
14    struct itimerspec spec = {
15        .it_value = { .tv_sec = 0, .tv_nsec = 100000000 },
16        .it_interval = { .tv_sec = 0, .tv_nsec = 100000000 }
17    };
18    if (timerfd_settime(fd, 0, &spec, NULL) < 0)
19        return 1;
20
21    struct timespec delay = { .tv_sec = 0, .tv_nsec = 550000000 };
22    nanosleep(&delay, NULL);
23    struct pollfd pfd = { .fd = fd, .events = POLLIN };
24    if (poll(&pfd, 1, 0) != 1)
25        return 1;
26
27    uint64_t expirations;
28    if (read(fd, &expirations, sizeof(expirations)) != sizeof(expirations))
29        return 1;
30    printf("expirations=%llu\n", (unsigned long long)expirations);
31    close(fd);
32    return 0;
33}

Code notes

Source line 11CLOCK_MONOTONIC

Creates an elapsed-time timer unaffected by wall-clock changes. For a calendar deadline, examine CLOCK_REALTIME and cancel-on-set policy.

Source line 16.it_interval

Because this is not 0, the timer is periodic and rearms at the same interval after its first expiration.

Source line 22nanosleep(&delay

Does not read events for 550ms so several 100ms ticks accumulate.

Source line 24poll(&pfd, 1, 0)

Checks current readability with timeout 0. POLLIN is set when the counter exceeds 0.

Source line 28read(fd, &expirations

Reads exactly sizeof(uint64_t) and obtains the accumulated expiration count. The read consumes the counter through the current point.

Detailed behavior

01

Processing every periodic tick is application policy

If expirations is 100, the handler can run 100 times to catch up, or it can recompute state once from the current monotonic time and skip stale ticks. Control loops and UI refresh commonly make different choices.

Measure the assumption that callback execution remains shorter than the interval.

02

eventfd separates wakeup from value transport

An eventfd write adds its value to the counter, and a normal read returns the full accumulated value and resets the counter to 0. In EFD_SEMAPHORE mode, each read returns 1 and decrements the counter by 1.

Place complex payloads in a separate queue and use eventfd as a wakeup credit indicating that the queue is nonempty.

03

Fd-based timing avoids signal handlers

Using timerfd instead of a POSIX timer signal lets the epoll thread control ordering and ownership. It reduces signal-mask and async-signal-safe handler burden.

It still cannot guarantee an exact execution time because kernel timer precision, slack, and scheduler latency apply.

Objects and lifetimes

ObjectCreation and releaseValues to inspect
timerfd_ctxCreated by timerfd_create and released when its fd closesclockid, ticks, interval
hrtimer/alarmArmed by settime and changed on expiration/cancellationexpires, interval, callback
eventfd counterRetained from eventfd creation to close and incremented/decremented by write/readcount, semaphore flag, wait queue

Failure conditions and common misconceptions

Observed symptomLikely causesHow to verify
Tick counts are lost1 readiness notification was interpreted as 1 expirationLog the uint64 return value
timer driftThe timer is rearmed relatively after the handler completesUse a periodic timer or absolute deadline
eventfd write EAGAINThe counter is near its maximumInspect consumer progress and counter protocol

Verify it yourself

  1. Change the delay to 1.05 seconds and verify that the expiration count accumulates to about 10.
  2. Register timerfd together with eventfd in epoll and handle worker wakeups and periodic ticks in one loop.
  3. Use a monotonic absolute deadline with TFD_TIMER_ABSTIME and compare drift against relative rearming.
Run./fd_counters
Tracestrace -e trace=timerfd_create,timerfd_settime,eventfd2,poll,read,write ./fd_counters

Primary sources