Timer periodik dapat mencapai expiration beberapa kali sebelum event loop yang sibuk kembali mendapat waktu CPU. Linux timerfd tidak mereduksi keterlambatan itu menjadi notifikasi “timer aktif” semata. read() yang berhasil mengembalikan counter unsigned 64-bit berisi jumlah expiration yang terakumulasi sejak timer diaktifkan atau sejak read sebelumnya yang berhasil.

Counter tersebut mengubah semantik penanganan timer yang terlambat. Readiness menyatakan setidaknya satu expiration sedang tertunda; nilai yang dibaca dari descriptor menyatakan berapa banyak periode yang telah berlalu.

Timer juga berupa file descriptor

timerfd_create() membuat objek timer yang direpresentasikan sebagai file descriptor. Descriptor ini dapat masuk ke poll(), select(), dan epoll() bersama socket, pipe, serta objek pollable lain.

int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
if (fd == -1) {
    perror("timerfd_create");
}

CLOCK_MONOTONIC sesuai untuk penjadwalan berbasis waktu berlalu karena perubahan wall clock secara diskontinu tidak mengubah clock tersebut. Pilihan clock lain yang didukung memiliki semantik berbeda, termasuk clock yang terkait real time atau perilaku suspend.

Descriptor menjadi readable setelah satu atau lebih expiration. Dengan TFD_NONBLOCK, read() saat tidak ada expiration tertunda gagal dengan EAGAIN, bukan menunggu.

read mengembalikan jumlah expiration

Payload read memiliki bentuk semantik tetap: integer unsigned 8-byte dalam host byte order.

uint64_t expirations;
ssize_t n = read(fd, &expirations, sizeof(expirations));

if (n == sizeof(expirations)) {
    printf("expired %llu times\n",
           (unsigned long long) expirations);
}

Jika tiga periode timer berlalu sebelum program membaca descriptor, nilai yang dikembalikan dapat berupa 3. Aplikasi dapat membedakan satu wakeup yang teramati dari jumlah interval timer yang direpresentasikan wakeup tersebut.

Buffer yang lebih kecil dari delapan byte tidak valid untuk operasi ini dan menyebabkan read() gagal dengan EINVAL. Setelah read berhasil, jumlah terakumulasi yang dilaporkan telah dikonsumsi; read berikutnya menghitung expiration setelahnya.

Ini bukan queue yang berisi satu record untuk setiap expiration. Kernel mengekspos sebuah counter. Perbedaan tersebut membuat event loop tidak perlu menguras rangkaian record timer identik hanya untuk menentukan jumlah periode yang telah berlalu.

Arming periodik memisahkan deadline pertama dari interval

timerfd_settime() menggunakan struct itimerspec. it_value menetapkan expiration awal, sedangkan it_interval menetapkan periode berulang setelah expiration pertama.

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

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

Dengan flags bernilai nol, it_value bersifat relatif terhadap nilai terkini dari clock yang dipilih. Jika kedua field it_interval bernilai nol, timer menjadi one-shot, bukan periodik. Menetapkan kedua field it_value ke nol akan menonaktifkan timer.

TFD_TIMER_ABSTIME mengubah expiration awal dari durasi relatif menjadi nilai absolut pada clock yang dipilih. Interval berulang tetap menyatakan periode setelah expiration awal tersebut.

Readiness dan kebijakan scheduling adalah hal terpisah

Anggap timer memiliki interval 100 ms dan event loop sibuk selama 450 ms. Saat loop kembali ke epoll_wait(), descriptor timer dapat sudah readable dengan beberapa expiration terakumulasi.

periode timer:   |---|---|---|---|---|
loop sibuk:      [-----------------]
descriptor:       counter expiration bertambah
loop kembali:                         read count

Kernel melaporkan expiration yang telah berlalu, tetapi tidak menentukan kebijakan aplikasi terhadapnya. Program dapat menjalankan satu unit kerja per expiration, memajukan logical clock berdasarkan count, menggabungkan pekerjaan yang terlambat menjadi satu aksi, atau membatasi catch-up agar latensi pekerjaan lain tetap terkendali.

Kebijakan tersebut memiliki konsekuensi berbeda. Menganggap setiap event readable tepat sebagai satu periode akan membuang informasi periode yang telah berlalu. Sebaliknya, menganggap count sebagai jumlah wajib pekerjaan mahal dapat menciptakan lonjakan catch-up. timerfd menyediakan count; event loop menentukan arti count bagi workload-nya.

Readiness epoll tidak menggantikan read

Notifikasi epoll menyatakan bahwa descriptor timer bersifat readable. Notifikasi itu tidak membawa expiration count.

struct epoll_event ev = {
    .events = EPOLLIN,
    .data.fd = fd,
};

epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

Setelah readiness dilaporkan, aplikasi membaca descriptor timer untuk mengonsumsi count yang tertunda. Pola ini mengikuti model event berbasis file descriptor secara umum: multiplexer menunjukkan objek yang memiliki state tersedia, sedangkan operasi milik objek tersebut mengambil state itu.

Pada event loop edge-triggered, I/O nonblocking dan konsumsi state secara lengkap tetap menjadi bagian penting dari desain. Fakta spesifik timer lebih sederhana: expiration tertunda membuat descriptor readable, lalu read() mengambil jumlah yang terakumulasi.

Timer real-time absolut dapat melaporkan diskontinuitas clock

Scheduling berbasis wall clock memiliki batas yang tidak terdapat pada scheduling berbasis elapsed time. Timer absolut berbasis CLOCK_REALTIME atau CLOCK_REALTIME_ALARM dapat diaktifkan dengan TFD_TIMER_CANCEL_ON_SET.

Saat flag tersebut digabungkan dengan TFD_TIMER_ABSTIME, perubahan diskontinu pada real-time clock menandai timer sebagai canceled. read() yang sedang berjalan atau dilakukan kemudian akan gagal dengan ECANCELED. Dengan demikian aplikasi menerima sinyal eksplisit bahwa deadline wall clock absolutnya tidak lagi valid akibat clock step.

Perilaku tersebut khusus untuk konfigurasi real-time clock yang didokumentasikan. Ini bukan mekanisme cancellation umum untuk setiap clock timerfd.

State timer bertahan melewati keterlambatan event loop

Batas penting pada timerfd terletak antara notifikasi dan accounting. Polling API mengekspos readiness sebagai kondisi boolean, tetapi descriptor timer mempertahankan catatan numerik expiration yang terakumulasi sampai read yang berhasil mengonsumsinya.

Akibatnya, keterlambatan scheduler tidak otomatis menghilangkan informasi periode timer. Aplikasi tetap harus menentukan respons yang sesuai terhadap periode yang terlambat, tetapi state yang tersedia cukup untuk mengambil keputusan tersebut secara eksplisit, bukan menebak interval yang berlalu dari satu event readiness.