QUESTION
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
Structure diagram
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
Call path
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.
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.
SOURCE COORDINATES
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.
| File | Function / structure | What 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 PROGRAM
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.
cc -std=c17 -Wall -Wextra -O2 fd_counters.c -o fd_counters01#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
Code notes
CLOCK_MONOTONICCreates an elapsed-time timer unaffected by wall-clock changes. For a calendar deadline, examine CLOCK_REALTIME and cancel-on-set policy.
.it_intervalBecause this is not 0, the timer is periodic and rearms at the same interval after its first expiration.
nanosleep(&delayDoes not read events for 550ms so several 100ms ticks accumulate.
poll(&pfd, 1, 0)Checks current readability with timeout 0. POLLIN is set when the counter exceeds 0.
read(fd, &expirationsReads exactly sizeof(uint64_t) and obtains the accumulated expiration count. The read consumes the counter through the current point.
DETAILS
Detailed behavior
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.
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.
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
Objects and lifetimes
| Object | Creation and release | Values to inspect |
|---|---|---|
timerfd_ctx | Created by timerfd_create and released when its fd closes | clockid, ticks, interval |
hrtimer/alarm | Armed by settime and changed on expiration/cancellation | expires, interval, callback |
eventfd counter | Retained from eventfd creation to close and incremented/decremented by write/read | count, semaphore flag, wait queue |
FAILURE PATH
Failure conditions and common misconceptions
| Observed symptom | Likely causes | How to verify |
|---|---|---|
| Tick counts are lost | 1 readiness notification was interpreted as 1 expiration | Log the uint64 return value |
| timer drift | The timer is rearmed relatively after the handler completes | Use a periodic timer or absolute deadline |
| eventfd write EAGAIN | The counter is near its maximum | Inspect consumer progress and counter protocol |
LAB
Verify it yourself
- Change the delay to 1.05 seconds and verify that the expiration count accumulates to about 10.
- Register timerfd together with eventfd in epoll and handle worker wakeups and periodic ticks in one loop.
- Use a monotonic absolute deadline with TFD_TIMER_ABSTIME and compare drift against relative rearming.
./fd_countersstrace -e trace=timerfd_create,timerfd_settime,eventfd2,poll,read,write ./fd_countersPRIMARY REFERENCES