๐Ÿ“ timerfd API

๐Ÿ“ timerfd API
Chapter 23 | Part 9 of 9 โ€” Timers That Notify via File Descriptors
๐Ÿง Linux
Kernel โ‰ฅ 2.6.25
๐Ÿ“ FD-based
select/poll/epoll
๐Ÿ“– Read
uint64_t count

What Is the timerfd API?

The timerfd API (Linux 2.6.25+) creates timers whose expirations can be read from a file descriptor. This is a game-changer for event-driven programs โ€” you can include timers in select(), poll(), or epoll alongside sockets, pipes, and other fds.

No signal handling required. No race conditions. Works perfectly in multi-threaded programs.

โš–๏ธ timerfd vs POSIX Timer โ€” When to Use Which
Use timerfd when…

  • Building event loops (epoll-based servers)
  • Mixing timers with I/O in select()/poll()
  • Multi-threaded programs (no signal safety concerns)
  • Want clean, readable expiration count
Use POSIX timers when…

  • Portability across POSIX systems
  • Signal-based notification preferred
  • Thread start function on expiry (SIGEV_THREAD)
  • Profiling timers (CPUTIME clocks)

โš™๏ธ How timerfd Works
๐Ÿ“Œ

timerfd_create()
Returns fd

โ†’
โ–ถ๏ธ

timerfd_settime()
Arm timer

โ†’
โฐ

Timer fires
fd becomes
readable

โ†’
๐Ÿ“–

read(fd, &n, 8)
n = # expirations

โ†’
๐Ÿ”’

close(fd)
when done

๐Ÿ“‹ timerfd Function Signatures
#include <sys/timerfd.h>

/* Create a timer file descriptor */
int timerfd_create(int clockid,  /* CLOCK_REALTIME or CLOCK_MONOTONIC */
                   int flags);   /* 0, TFD_CLOEXEC, TFD_NONBLOCK */
/* Returns: fd on success, -1 on error */

/* Arm or disarm the timer */
int timerfd_settime(int fd,
                    int flags,   /* 0 or TFD_TIMER_ABSTIME */
                    const struct itimerspec *new_value,
                    struct itimerspec *old_value);  /* NULL if unused */
/* Returns: 0 on success, -1 on error */

/* Query current timer state */
int timerfd_gettime(int fd,
                    struct itimerspec *curr_value);
/* Returns: 0 on success, -1 on error */

/* Read expiration count (BLOCKING unless TFD_NONBLOCK) */
#include <stdint.h>
ssize_t read(int fd, uint64_t *buf, 8);
/* buf = number of expirations since last read / settime
   Blocks if no expirations yet.
   Returns 8 on success, -1 on error */

/* When done: */
close(fd);

/* Compile: no extra -l flags needed (built into kernel) */

๐Ÿšฉ timerfd Flags
Flag Used In Effect
TFD_CLOEXEC timerfd_create() Set close-on-exec flag on fd (like O_CLOEXEC)
TFD_NONBLOCK timerfd_create() Non-blocking reads: return EAGAIN if no expirations
TFD_TIMER_ABSTIME timerfd_settime() new_value is absolute time (like TIMER_ABSTIME)

๐Ÿ”€ fork() & exec() Behavior
fork()

Child inherits COPIES of timerfd file descriptors. Both parent and child can read timer expirations from the SAME timer object.

exec()

timerfd fds are preserved across exec() (unless TFD_CLOEXEC was set). Armed timers continue generating expirations after exec.

๐Ÿ’ป Example 1: Basic timerfd โ€” Read Expirations
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <time.h>

int main(void) {
    int fd;
    struct itimerspec ts;
    uint64_t expirations;
    ssize_t n;

    /* Create timer fd using monotonic clock */
    fd = timerfd_create(CLOCK_MONOTONIC, 0);
    if (fd == -1) { perror("timerfd_create"); return 1; }

    /* Arm: first expiry in 1s, repeat every 1s */
    ts.it_value.tv_sec    = 1;
    ts.it_value.tv_nsec   = 0;
    ts.it_interval.tv_sec  = 1;
    ts.it_interval.tv_nsec = 0;

    if (timerfd_settime(fd, 0, &ts, NULL) == -1) {
        perror("timerfd_settime"); return 1;
    }

    printf("Timer started. Reading 5 expirations...\n");

    for (int i = 0; i < 5; i++) {
        /* read() blocks until timer expires */
        n = read(fd, &expirations, sizeof(uint64_t));
        if (n != sizeof(uint64_t)) {
            perror("read"); return 1;
        }
        printf("Expiration #%d: count=%llu\n",
               i + 1, (unsigned long long)expirations);
    }

    close(fd);
    printf("Done.\n");
    return 0;
}

/* Compile: gcc -o tfd_basic tfd_basic.c
   Output:
   Timer started. Reading 5 expirations...
   Expiration #1: count=1
   Expiration #2: count=1
   Expiration #3: count=1
   Expiration #4: count=1
   Expiration #5: count=1
   Done.
*/

๐Ÿ’ป Example 2: timerfd + select() โ€” Mix Timer with I/O

The key feature: include timer fd in select() alongside stdin.

#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <sys/select.h>
#include <time.h>
#include <string.h>

int main(void) {
    int tfd;
    struct itimerspec ts;
    uint64_t expirations;
    char buf[128];
    fd_set read_fds;
    int nfds;

    /* Create 2-second periodic timer */
    tfd = timerfd_create(CLOCK_MONOTONIC, 0);
    ts.it_value.tv_sec    = 2;
    ts.it_value.tv_nsec   = 0;
    ts.it_interval.tv_sec  = 2;
    ts.it_interval.tv_nsec = 0;
    timerfd_settime(tfd, 0, &ts, NULL);

    nfds = (tfd > STDIN_FILENO ? tfd : STDIN_FILENO) + 1;

    printf("Watching stdin AND timer (every 2s). Type or wait...\n");
    printf("Press Ctrl-C to exit.\n");

    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(STDIN_FILENO, &read_fds); /* Watch keyboard */
        FD_SET(tfd, &read_fds);          /* Watch timer   */

        if (select(nfds, &read_fds, NULL, NULL, NULL) == -1) {
            perror("select"); break;
        }

        if (FD_ISSET(tfd, &read_fds)) {
            read(tfd, &expirations, sizeof(uint64_t));
            printf("[TIMER] %llu expiration(s) occurred\n",
                   (unsigned long long)expirations);
        }

        if (FD_ISSET(STDIN_FILENO, &read_fds)) {
            ssize_t n = read(STDIN_FILENO, buf, sizeof(buf) - 1);
            if (n > 0) {
                buf[n] = '\0';
                printf("[INPUT] You typed: %s", buf);
            }
        }
    }

    close(tfd);
    return 0;
}

/* This program handles both user input and timer ticks
   using a single select() โ€” no signals needed at all! */

๐Ÿ’ป Example 3: timerfd + epoll() โ€” Event Loop Pattern

Production-grade: integrate timer into an epoll event loop.

#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <time.h>

#define MAX_EVENTS 4
#define TICK_MS    500   /* 500ms tick */

int main(void) {
    int epfd, tfd;
    struct itimerspec ts;
    struct epoll_event ev, events[MAX_EVENTS];
    uint64_t expirations;
    int n, ticks = 0;

    /* Create epoll instance */
    epfd = epoll_create1(0);

    /* Create timerfd */
    tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);

    /* Arm timer: 500ms periodic */
    ts.it_value.tv_sec    = 0;
    ts.it_value.tv_nsec   = TICK_MS * 1000000L;
    ts.it_interval.tv_sec  = 0;
    ts.it_interval.tv_nsec = TICK_MS * 1000000L;
    timerfd_settime(tfd, 0, &ts, NULL);

    /* Register timerfd with epoll */
    ev.events   = EPOLLIN;
    ev.data.fd  = tfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, tfd, &ev);

    printf("Event loop running (500ms ticks)...\n");

    while (ticks < 6) {
        n = epoll_wait(epfd, events, MAX_EVENTS, -1);
        for (int i = 0; i < n; i++) {
            if (events[i].data.fd == tfd) {
                read(tfd, &expirations, sizeof(uint64_t));
                ticks += (int)expirations;
                printf("Tick! expirations=%llu, total_ticks=%d\n",
                       (unsigned long long)expirations, ticks);
                /* In a real server: ticks could trigger
                   timeouts, keepalives, housekeeping... */
            }
            /* Add more fds (sockets, pipes) to this loop */
        }
    }

    close(tfd);
    close(epfd);
    printf("Event loop done.\n");
    return 0;
}

/* This is the pattern used by:
   - libuv (Node.js event loop)
   - libev, libevent
   - many high-performance servers
*/

๐ŸŽ“ Interview Questions
Q1. What is the main advantage of timerfd over POSIX timers?

Timer expirations can be read via a file descriptor, so they integrate naturally with select(), poll(), and epoll event loops. No signal handlers needed, no async-signal restrictions.

Q2. What does read() return from a timerfd?

An 8-byte uint64_t containing the number of timer expirations since the last read() or settime(). If multiple expirations happened (overruns), you get them all in one read.

Q3. What clock IDs can timerfd_create() use?

CLOCK_REALTIME or CLOCK_MONOTONIC. Unlike POSIX timers, CPU-time clocks (CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID) are not supported.

Q4. What happens to timerfd fds after fork()?

Both parent and child inherit copies of the fd referring to the SAME timer object. Either can read expirations โ€” but each expiration is consumed by only one read() call.

Q5. What is TFD_NONBLOCK and when is it useful?

Makes read() non-blocking โ€” returns EAGAIN instead of blocking if no expirations. Useful in event loops where you want to poll the timer fd without blocking the event thread.

๐Ÿ“š Chapter 23 โ€” Full Summary
API When to Use Precision Header
alarm() Simple one-shot real timer Seconds <unistd.h>
setitimer() Classic interval timers (3 types) Microseconds <sys/time.h>
sleep() Simple process suspend Seconds <unistd.h>
nanosleep() High-precision sleep Nanoseconds <time.h>
clock_gettime() Read various POSIX clocks Nanoseconds <time.h> -lrt
clock_nanosleep() Drift-free precise sleeping Nanoseconds <time.h> -lrt
timer_create() Multiple POSIX timers, signal/thread notify Nanoseconds <time.h> -lrt
timerfd_create() Event-loop timers, epoll/select integration Nanoseconds <sys/timerfd.h>

๐ŸŽ‰ Chapter 23 Complete!

You have covered all 9 parts of Linux Timers & Sleeping.

โ† Back to Index Part 1: Interval Timers

Leave a Reply

Your email address will not be published. Required fields are marked *