A Linux process can signal work through a file descriptor without moving a byte stream between producer and consumer. eventfd() creates a kernel-maintained 64-bit counter whose readiness can be observed by poll(), select(), or epoll. A write adds to the counter; a read consumes its accumulated state according to the descriptor mode.
That shape makes eventfd different from a pipe. A pipe preserves a sequence of bytes. An eventfd preserves counter state. When the application needs a wakeup edge plus a compact amount of accumulated state, that distinction removes buffering and framing that a byte stream would otherwise require.
The descriptor represents one unsigned counter
The interface is small:
#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);The initial value seeds the counter. The kernel stores the counter as an unsigned 64-bit integer, while the initial argument is only 32 bits wide. Successful calls return a file descriptor referring to the eventfd object.
Two flags commonly affect descriptor integration:
EFD_CLOEXECapplies close-on-exec semantics without a separatefcntl()operation.EFD_NONBLOCKmakes operations reportEAGAINinstead of sleeping when they cannot proceed immediately.
EFD_SEMAPHORE changes read semantics and is a property of the eventfd object rather than a per-read option.
Writes accumulate rather than enqueue records
A successful write() supplies exactly eight bytes containing a uint64_t value. The kernel adds that value to the current counter. The value UINT64_MAX is not accepted as an eventfd write value.
The counter’s maximum normal value is UINT64_MAX - 1. If an addition would exceed that limit, a blocking descriptor waits until a read creates room; a nonblocking descriptor returns EAGAIN.
This means ten writes of value 1 do not create ten independently readable records in the default mode. They can collapse into counter value 10 before the consumer runs. Applications that require one payload per event need another transport or a separate data structure.
uint64_t one = 1;
if (write(efd, &one, sizeof one) != sizeof one) {
/* handle error */
}The eventfd is therefore suitable for notification coalescing. Producers can increment it as work becomes available, while the consumer uses readiness as a signal to inspect the actual work queue.
Default reads drain the accumulated value
Without EFD_SEMAPHORE, a successful eight-byte read() returns the current counter and resets it to zero.
uint64_t count;
if (read(efd, &count, sizeof count) == sizeof count) {
/* count is the accumulated counter value */
}If the counter is already zero, a blocking read sleeps. With EFD_NONBLOCK, the same state produces EAGAIN.
The reset-to-zero operation is significant for event loops. Multiple producer increments can produce one readiness episode, and one consumer read can drain the accumulated notification state. The eventfd stops being readable once its counter reaches zero.
Semaphore mode consumes one unit per read
With EFD_SEMAPHORE, a successful read returns 1 and subtracts 1 from the counter. If the counter held 5, five successful reads are required to return it to zero.
int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE);This mode changes consumption granularity, but it does not turn the descriptor into a queue of producer identities or messages. Every consumed unit has the same value. Metadata still belongs elsewhere.
Readiness follows counter state
An eventfd is readable while its counter is greater than zero. It is writable when at least the value 1 can be added without exceeding the normal counter limit. Those properties let an eventfd participate in the same multiplexing set as sockets, pipes, timers, and other pollable descriptors.
A compact epoll integration can register the eventfd for EPOLLIN:
struct epoll_event ev = {
.events = EPOLLIN,
.data.fd = efd,
};
epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &ev);A producer does not need access to the epoll instance. It only needs a reference to the eventfd descriptor, or to the same underlying open file description after descriptor transfer or inheritance.
For edge-triggered epoll, the application still needs disciplined draining logic. A nonblocking eventfd allows the consumer to read until EAGAIN, matching the usual edge-triggered pattern without risking a blocking read after the available state has been consumed.
The eight-byte operation size is part of the ABI
read() and write() operations use eight-byte integer values. A read buffer smaller than eight bytes fails with EINVAL; eventfd does not provide partial counter reads. Writes likewise operate on an eight-byte integer value.
That fixed-width ABI is another boundary between eventfd and stream-oriented descriptors. There is no message header, delimiter, or variable payload to parse.
A helper can keep the representation explicit:
static int signal_eventfd(int fd, uint64_t n)
{
ssize_t rc = write(fd, &n, sizeof n);
return rc == sizeof n ? 0 : -1;
}Production code must still handle interruption, nonblocking backpressure, descriptor lifetime, and application-specific error policy.
Kernel facilities can use eventfd as a notification endpoint
The descriptor is not limited to thread-to-thread signaling through write(). Linux interfaces can accept an eventfd and signal it from kernel-managed activity. This makes the counter useful as an adapter between subsystem completion state and a file-descriptor event loop.
The exact signaling semantics belong to the subsystem that accepts the eventfd. Registering an eventfd with another API does not imply that each kernel event maps to one application-level job, nor does it add payload semantics beyond the counter. The consumer must follow the contract of both interfaces.
Counter state and work state should remain separate
A robust design treats eventfd as a notification mechanism, not as the authoritative storage for complex work. A producer can place entries into a queue and then increment the eventfd. The consumer wakes, drains the eventfd, and processes the queue according to the queue’s own synchronization rules.
That separation also handles coalescing naturally. The counter can say that activity occurred while the queue carries ownership, ordering, cancellation, and payload data.
eventfd is compact because its contract stays narrow: one pollable descriptor, one 64-bit counter, fixed-size operations, and two read-consumption modes. It fits event loops precisely when notification state can be represented by that contract; richer event data belongs in a separate channel.