Proses Linux dapat memberi sinyal bahwa ada pekerjaan melalui file descriptor tanpa memindahkan byte stream antara producer dan consumer. eventfd() membuat counter 64-bit yang dikelola kernel dan readiness-nya dapat diamati oleh poll(), select(), atau epoll. Operasi write menambah counter; operasi read mengonsumsi state yang telah terakumulasi sesuai mode descriptor.

Bentuk ini membedakan eventfd dari pipe. Pipe mempertahankan urutan byte. Eventfd mempertahankan state counter. Saat aplikasi memerlukan sinyal wakeup beserta sedikit state terakumulasi, perbedaan ini menghilangkan buffering dan framing yang diperlukan oleh byte stream.

Descriptor merepresentasikan satu unsigned counter

Antarmukanya kecil:

#include <sys/eventfd.h>

int eventfd(unsigned int initval, int flags);

Nilai awal mengisi counter. Kernel menyimpan counter sebagai unsigned integer 64-bit, sedangkan argumen awal hanya selebar 32 bit. Pemanggilan yang berhasil mengembalikan file descriptor yang merujuk ke objek eventfd.

Dua flag umum memengaruhi integrasi descriptor:

  • EFD_CLOEXEC menerapkan semantik close-on-exec tanpa operasi fcntl() terpisah.
  • EFD_NONBLOCK membuat operasi melaporkan EAGAIN, bukan menunggu, saat operasi tidak dapat langsung dilanjutkan.

EFD_SEMAPHORE mengubah semantik read dan merupakan properti objek eventfd, bukan opsi per operasi read.

Write mengakumulasi nilai, bukan mengantrekan record

write() yang berhasil memasok tepat delapan byte berisi nilai uint64_t. Kernel menambahkan nilai itu ke counter saat ini. Nilai UINT64_MAX tidak diterima sebagai nilai write untuk eventfd.

Nilai normal maksimum counter adalah UINT64_MAX - 1. Jika penambahan akan melewati batas tersebut, descriptor blocking menunggu sampai read menyediakan ruang; descriptor nonblocking mengembalikan EAGAIN.

Artinya, sepuluh write dengan nilai 1 tidak membuat sepuluh record yang dapat dibaca secara terpisah dalam mode default. Semuanya dapat melebur menjadi nilai counter 10 sebelum consumer berjalan. Aplikasi yang memerlukan satu payload untuk setiap event memerlukan transport lain atau struktur data terpisah.

uint64_t one = 1;

if (write(efd, &one, sizeof one) != sizeof one) {
    /* handle error */
}

Karena itu, eventfd cocok untuk penggabungan notifikasi. Producer dapat menaikkannya saat pekerjaan tersedia, sedangkan consumer memakai readiness sebagai sinyal untuk memeriksa work queue yang sebenarnya.

Read default menguras nilai yang terakumulasi

Tanpa EFD_SEMAPHORE, read() delapan byte yang berhasil mengembalikan counter saat ini lalu meresetnya menjadi nol.

uint64_t count;

if (read(efd, &count, sizeof count) == sizeof count) {
    /* count is the accumulated counter value */
}

Jika counter sudah nol, read blocking akan menunggu. Dengan EFD_NONBLOCK, state yang sama menghasilkan EAGAIN.

Operasi reset-ke-nol penting bagi event loop. Beberapa increment dari producer dapat menghasilkan satu episode readiness, dan satu read oleh consumer dapat menguras state notifikasi yang terakumulasi. Eventfd berhenti berstatus readable setelah counternya mencapai nol.

Mode semaphore mengonsumsi satu unit per read

Dengan EFD_SEMAPHORE, read yang berhasil mengembalikan 1 dan mengurangi counter sebesar 1. Jika counter berisi 5, diperlukan lima read yang berhasil untuk mengembalikannya ke nol.

int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE);

Mode ini mengubah granularitas konsumsi, tetapi tidak mengubah descriptor menjadi antrean identitas producer atau pesan. Setiap unit yang dikonsumsi memiliki nilai sama. Metadata tetap harus disimpan di tempat lain.

Readiness mengikuti state counter

Eventfd berstatus readable selama counternya lebih besar dari nol. Eventfd berstatus writable saat setidaknya nilai 1 dapat ditambahkan tanpa melewati batas normal counter. Properti tersebut memungkinkan eventfd berada dalam set multiplexing yang sama dengan socket, pipe, timer, dan descriptor lain yang dapat dipantau.

Integrasi epoll yang ringkas dapat mendaftarkan eventfd untuk EPOLLIN:

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

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

Producer tidak memerlukan akses ke instance epoll. Producer hanya memerlukan referensi ke descriptor eventfd, atau ke open file description yang sama setelah transfer atau pewarisan descriptor.

Untuk epoll edge-triggered, aplikasi tetap memerlukan logika drain yang disiplin. Eventfd nonblocking memungkinkan consumer melakukan read sampai EAGAIN, sesuai pola edge-triggered umum tanpa risiko read blocking setelah state yang tersedia telah dikonsumsi.

Ukuran operasi delapan byte merupakan bagian ABI

Operasi read() dan write() memakai nilai integer delapan byte. Buffer read yang lebih kecil dari delapan byte gagal dengan EINVAL; eventfd tidak menyediakan pembacaan counter parsial. Write juga beroperasi pada nilai integer delapan byte.

ABI dengan lebar tetap ini menjadi batas lain antara eventfd dan descriptor berorientasi stream. Tidak ada message header, delimiter, atau payload dengan ukuran variabel yang perlu diurai.

Helper dapat menjaga representasi tetap eksplisit:

static int signal_eventfd(int fd, uint64_t n)
{
    ssize_t rc = write(fd, &n, sizeof n);
    return rc == sizeof n ? 0 : -1;
}

Kode produksi tetap harus menangani interruption, backpressure nonblocking, lifetime descriptor, dan kebijakan error yang ditentukan aplikasi.

Fasilitas kernel dapat memakai eventfd sebagai endpoint notifikasi

Descriptor ini tidak terbatas pada signaling antarthread melalui write(). Antarmuka Linux dapat menerima eventfd dan memberi sinyal melalui aktivitas yang dikelola kernel. Dengan sifat tersebut, counter dapat menjadi adapter antara state completion suatu subsistem dan event loop berbasis file descriptor.

Semantik signaling yang tepat mengikuti subsistem yang menerima eventfd. Mendaftarkan eventfd ke API lain tidak berarti setiap event kernel dipetakan menjadi satu job tingkat aplikasi, dan tidak menambahkan semantik payload di luar counter. Consumer harus mengikuti kontrak kedua antarmuka.

State counter dan state pekerjaan sebaiknya tetap terpisah

Desain yang kuat memperlakukan eventfd sebagai mekanisme notifikasi, bukan penyimpanan otoritatif untuk pekerjaan kompleks. Producer dapat menaruh entri ke queue lalu menaikkan eventfd. Consumer terbangun, menguras eventfd, lalu memproses queue sesuai aturan sinkronisasi queue itu sendiri.

Pemisahan ini juga menangani penggabungan notifikasi secara alami. Counter dapat menyatakan bahwa aktivitas terjadi, sementara queue membawa ownership, ordering, cancellation, dan data payload.

eventfd tetap ringkas karena kontraknya sempit: satu descriptor yang dapat dipantau, satu counter 64-bit, operasi berukuran tetap, dan dua mode konsumsi read. Mekanisme ini tepat untuk event loop ketika state notifikasi dapat direpresentasikan oleh kontrak tersebut; data event yang lebih kaya ditempatkan pada kanal terpisah.