A Linux eventfd can collapse many notifications into one kernel-maintained counter while still participating in poll(), select(), and epoll. Writers add unsigned 64-bit values to the counter; readiness reports whether that state can be consumed. The interface carries arithmetic state rather than a byte stream or a queue of individual messages.

That distinction matters at process and thread boundaries. A wakeup says that the counter is nonzero. It does not preserve the number of write operations, writer identities, or ordering among independent notification sources.

Writes add values rather than records

eventfd() creates an eventfd object containing an unsigned 64-bit counter. The initval argument supplies its initial value. EFD_NONBLOCK applies nonblocking I/O semantics, while EFD_CLOEXEC sets close-on-exec on the returned descriptor.

A successful write() supplies exactly one eight-byte integer in host byte order. The value is added to the current counter. Writing UINT64_MAX is invalid, and ordinary user-space writes cannot raise the stored counter above UINT64_MAX - 1.

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

uint64_t n = 3;
ssize_t written = write(efd, &n, sizeof(n));

The write above contributes three units of counter state. It does not append an eight-byte record that a later read can retrieve unchanged. If another writer contributes five before a read, the default read semantics can expose the aggregate value eight.

This aggregation makes eventfd compact for notification counts, but it also removes message boundaries. A component that requires per-event payloads, source identity, or strict record ordering needs another transport or separate state outside the eventfd counter.

Default reads exchange accumulated state for zero

Without EFD_SEMAPHORE, a successful read() returns the current nonzero counter as one eight-byte host-order integer and resets the counter to zero.

uint64_t value;
ssize_t nread = read(efd, &value, sizeof(value));

If the counter contains eight, the read returns eight and consumes that accumulated state in one operation. A later read sees no available counter value until another contribution arrives.

When the counter is zero, a blocking read waits for it to become nonzero. With EFD_NONBLOCK, the same condition produces EAGAIN. A buffer smaller than eight bytes produces EINVAL.

The semantic unit is therefore the current aggregate, not one notification. Applications that treat each readiness event as exactly one producer action can silently discard information even when the kernel has preserved the total count.

Semaphore mode changes consumption, not production

EFD_SEMAPHORE changes successful read behavior while retaining additive writes. When the counter is nonzero, each read returns the value one and decrements the counter by one instead of returning the full aggregate and resetting it.

If writers contribute three and five, the counter can reach eight in either mode. Default mode permits one read to consume eight. Semaphore mode permits eight successful reads that each consume one, assuming no concurrent writes alter the state.

This is a consumption contract, not a record-recovery mechanism. Semaphore mode still does not reveal whether eight arose from eight writes of one, one write of eight, or another combination. It preserves units in the counter while discarding producer-operation boundaries.

Multiple readers also compete for the same state. A successful read by one thread or process changes the counter observed by every other reference to that eventfd object.

Readiness follows counter arithmetic

An eventfd is readable when its counter is greater than zero. It is writable when at least the value one can be added without blocking. These conditions make counter state visible to file-descriptor multiplexing without converting it into stream data.

The writable condition has a less common but precise boundary. If an addition would exceed UINT64_MAX - 1, a blocking write waits until a read reduces the counter. With nonblocking I/O, the write fails with EAGAIN.

Readiness is level-sensitive state at the eventfd interface: a nonzero counter remains readable until reads consume it. Edge-triggered epoll adds its own notification rules, but it does not change eventfd counter arithmetic. Event-loop code still has to reason about the state left after each read.

A readiness notification can also be stale by the time a consumer acts. Another reader may consume the counter after the event loop observes readiness. Nonblocking mode lets such races resolve as EAGAIN instead of turning them into an unexpected wait.

Descriptor duplication shares one counter

A descriptor inherited across fork() refers to the same eventfd object. Descriptors produced by duplication also refer to that shared object. Writes through any reference add to the same counter, and reads through any reference consume from it.

This makes eventfd useful as a narrow coordination primitive across threads or related processes, but descriptor count does not create subscriber count. Two readers do not each receive a copy of the accumulated value.

The lifetime follows the shared kernel object. Once every descriptor referring to the eventfd object is closed, the kernel can release its resources. EFD_CLOEXEC is significant when the notification channel must not cross a successful execve() boundary.

Counter state and application state remain separate

eventfd can signal that work exists without storing the work itself. A queue can hold work items while eventfd supplies a pollable indication that producers have changed queue state. Correctness then depends on the queue’s synchronization rules as well as the eventfd contract.

A counter contribution does not by itself publish arbitrary user-space memory under the C or C++ memory models. Thread synchronization still requires operations whose language-level ordering rules cover the shared data. Across processes, the shared-memory mechanism and its synchronization protocol carry their own constraints. eventfd provides kernel-mediated notification and counter operations; it is not a replacement for the memory-ordering contract around separate data.

The same boundary applies to counting. If each producer writes one only after successfully enqueueing one item, the eventfd value can be used as a count associated with that protocol. If producers batch values, retry writes, drop queue entries, or update the queue and counter under different failure rules, equality between counter units and queue entries is an application invariant rather than an eventfd guarantee.

eventfd is strongest when that narrow contract is explicit: writes accumulate numeric state, reads consume it according to one of two modes, readiness reflects whether the state is currently consumable, and every descriptor referring to the object participates in the same counter.