An eventfd object stores an unsigned 64-bit counter in the kernel and exposes that state through a file descriptor. Writes add to the counter under defined bounds; reads consume counter state; readiness interfaces expose whether an operation can proceed without blocking. The result is a compact synchronization boundary that fits descriptor-oriented event loops without turning the counter into a byte stream.

The interface is Linux-specific. Its guarantees come from the eventfd system-call contract and kernel descriptor semantics, not from the C language or POSIX.

Counter state is the shared object

A call to eventfd() creates a descriptor referring to an eventfd object initialized with the supplied unsigned value:

int fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);

The stored counter has a maximum usable value of UINT64_MAX - 1. A userspace write() supplies exactly eight bytes interpreted as a uint64_t. Writing UINT64_MAX is invalid and fails with EINVAL.

For ordinary eventfd mode, a successful write adds the supplied value to the current counter. If that addition would exceed the maximum usable value, a blocking descriptor waits until a write can proceed; a descriptor created with EFD_NONBLOCK fails with EAGAIN instead.

This is an arithmetic interface rather than message storage. Two writes of values 3 and 5 can produce counter state 8; the object does not retain two independently addressable records.

Ordinary reads exchange accumulated state for zero

Without EFD_SEMAPHORE, a successful read() transfers the current counter value as one eight-byte uint64_t and resets the counter to zero. If the counter is already zero, a blocking read waits. With nonblocking mode, it fails with EAGAIN.

That reset operation gives the reader an accumulated notification count at the instant the kernel performs the read. Concurrent writers can add new state after the read, so application logic must not treat a returned value as a permanent statement that no additional events exist.

The transfer size is part of the API contract. Reads smaller than eight bytes fail with EINVAL; successful reads return eight bytes. The descriptor therefore does not have the partial-record behavior associated with a generic stream.

Semaphore mode changes read consumption

EFD_SEMAPHORE changes only the read-side consumption rule. When the counter is nonzero, each successful read returns the value 1 and decrements the counter by 1 instead of returning the full accumulated value and resetting it to zero.

A counter holding 4 therefore permits four successful semaphore-mode reads before reaching zero. Writers still add unsigned values subject to the same counter bound.

This mode can represent units of availability, but it does not establish ownership of any external resource. If a program associates one counter unit with a queue element or permit, consistency between those objects remains an application-level invariant.

Readiness follows permitted counter operations

An eventfd descriptor is readable when its counter is greater than zero. It is writable when at least the value 1 can be added without blocking. poll(), epoll, and related Linux descriptor multiplexing interfaces can observe these states.

Readiness is therefore derived from counter state rather than from queued byte count. A readable notification says that a read can consume counter state; it does not identify the producer, preserve write boundaries, or describe an external work item.

The writable condition also has a specific bound. Because the counter cannot normally exceed UINT64_MAX - 1, a sufficiently full counter can apply backpressure to writers. Most coordination designs keep values far from that limit, but the bound remains part of observable API behavior.

Descriptor duplication shares the same counter

Descriptors produced by dup(), inherited across fork(), or otherwise referring to the same open file description access the same eventfd object and counter. A read through one such descriptor changes the state observed through the others.

This matters when several consumers wait on duplicated descriptors. In ordinary mode, one reader can consume the accumulated value and reset the shared counter. In semaphore mode, successful readers consume units one at a time. Scheduling among competing threads is separate from the arithmetic guarantee; the API does not turn duplicated descriptors into independent counters.

EFD_CLOEXEC requests close-on-exec atomically at creation. It controls descriptor inheritance across execve() and does not alter the counter semantics while the descriptor remains open.

Counter overflow has a kernel-originated exceptional case

Userspace writes cannot directly overflow the counter because additions that would exceed its normal maximum block or fail. Linux also documents an exceptional overflow case associated with extremely many eventfd signal posts from kernel AIO. In that state, poll() reports both readable and error conditions, and a read returns UINT64_MAX.

That exceptional path is distinct from ordinary userspace write() behavior. Treating UINT64_MAX as a normal counter value would erase a documented boundary between regular arithmetic and kernel-reported overflow state.

A notification count is not a work queue

eventfd is effective when the shared fact to communicate is a bounded count or wakeup condition. It does not carry payloads, producer identity, ordering metadata, or per-write boundaries. Those properties must live in another shared structure or protocol when an application requires them.

A common design therefore pairs an eventfd with separately owned state: producers mutate a queue or state machine, then increment the eventfd counter to make work visible to a descriptor-driven consumer. Correctness still depends on the synchronization contract protecting that external state. The eventfd counter supplies notification and arithmetic state; it does not replace memory-ordering rules, queue ownership, or application invariants.