A Linux eventfd can receive several writes before any consumer runs, yet the descriptor does not retain those writes as separate messages. Each accepted write adds its unsigned 64-bit value to a kernel-maintained counter. Without EFD_SEMAPHORE, one successful read returns the current counter and resets it to zero.
That behavior makes eventfd a counter-backed notification primitive rather than a message queue. Readiness indicates that the counter is nonzero; it does not preserve the number, ordering, or boundaries of individual write operations.
Writes accumulate numeric value
eventfd() creates an eventfd object with an initial counter value supplied as an unsigned integer. A write must provide exactly the eight-byte integer representation expected by the interface. The written value is added to the counter rather than appended as a record.
int fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
uint64_t a = 2;
uint64_t b = 5;
write(fd, &a, sizeof(a));
write(fd, &b, sizeof(b));If no read occurs between those writes, the counter becomes 7. A later read in the default mode can return 7 in one eight-byte value and reset the counter to zero.
The arithmetic boundary is deliberate. The largest ordinary counter value is UINT64_MAX - 1. A user-space write of UINT64_MAX is rejected with EINVAL. If an addition would exceed the permitted maximum, a blocking descriptor waits until a read creates capacity; a nonblocking descriptor returns EAGAIN.
This means writability is also state-dependent. poll() and equivalent interfaces can report the descriptor writable when at least the value 1 can be added without blocking.
Readability represents counter state
An eventfd is readable while its counter is greater than zero. poll(), select(), and epoll can therefore integrate the object into the same readiness loop as sockets, pipes, timerfds, and other descriptors.
The readiness event does not carry the counter value. The value is obtained by reading the descriptor.
uint64_t value;
ssize_t n = read(fd, &value, sizeof(value));
if (n == sizeof(value)) {
/* value is the accumulated counter in default mode */
}In default mode, the successful read consumes the complete current counter. If producers write again afterward, readability can become true again. Several producer actions that occur before the consumer reads can consequently produce one readiness interval and one aggregate read.
A buffer smaller than eight bytes causes EINVAL. If the counter is zero, a blocking read waits; with EFD_NONBLOCK, the same condition produces EAGAIN.
Coalescing removes write boundaries
Suppose three producers write 1, 1, and 1 before a consumer receives CPU time. The observable counter can be 3, but the eventfd itself does not retain metadata identifying three producers or three distinct calls.
The same counter value could result from one producer writing 3. From the eventual read alone, those histories are indistinguishable.
That loss of boundaries is useful when the signal means that work exists elsewhere. A producer can update a separate queue and then write to eventfd so an event loop becomes runnable. Multiple notifications may merge while the queue retains the actual work items.
The separation also creates a correctness boundary. If application semantics require per-message payloads, ordering between payloads, or independent delivery records, the eventfd counter cannot supply those properties. The payload must live in another data structure or a message-oriented IPC mechanism.
EFD_SEMAPHORE changes consumption, not storage
Creating the descriptor with EFD_SEMAPHORE changes successful read behavior. The object still maintains one counter, and writes still add values to it. A successful read returns 1 and decrements the counter by one instead of returning the complete value and resetting it to zero.
For a counter value of 7, seven successful semaphore-mode reads can each return 1, assuming no concurrent writes alter the state.
This mode changes how counter units are consumed. It does not restore original write boundaries. A single write of 7 and seven writes of 1 still create equivalent counter state before reads begin.
The distinction matters for designs that use eventfd as a wakeup token source. Semaphore mode can distribute available units across separate reads, but it remains a numeric synchronization object rather than a record transport.
Descriptor duplication shares the same object
A duplicated eventfd descriptor refers to the same underlying eventfd object. The same is true for the descriptor inherited across fork(). Reads and writes through those references operate on one shared counter.
As a result, a read through one duplicate consumes state visible through the others. Descriptor duplication does not clone the current counter into independent notification channels.
The object remains alive while at least one associated file descriptor remains open. EFD_CLOEXEC controls whether a newly created descriptor is automatically closed across execve(); it does not alter the counter semantics.
Counter overflow has a separate kernel-origin case
Ordinary user-space write() calls cannot drive the counter past its permitted maximum because writes block or fail before that happens. Linux documents a distinct overflow condition for the theoretical case in which kernel asynchronous I/O performs 2^64 eventfd signal posts without a read.
In that state, readiness APIs expose an error indication and a read returns UINT64_MAX. This overflow path is separate from normal user-space addition rules and should not be inferred from an EAGAIN caused by a nearly full counter.
The distinction keeps two boundaries clear: user-space writes are capacity-checked, while kernel-origin signal posting has a documented overflow representation.
Counter semantics shape event-loop contracts
An event loop that treats one readiness dispatch as one producer event imposes a property that eventfd does not provide. Scheduling delay, batching, and concurrent writers can all merge notification activity into the same nonzero counter state.
A robust contract instead assigns meaning to the counter itself or uses it only as a wakeup indication for state stored elsewhere. Default reads provide an aggregate numeric value; semaphore reads consume one unit at a time. Neither mode preserves a stream of write records.
That narrow contract is the source of eventfd’s utility. It exposes a small kernel counter through ordinary file-descriptor readiness, allowing notification state to compose with descriptor-based event loops while leaving richer payload and ordering semantics to the surrounding system.