An eventfd can absorb several notification writes before userspace services the descriptor. The kernel stores those writes in a 64-bit counter, so readiness represents pending counter state rather than a queue containing one record per notification.

That distinction matters in event loops. A producer can add values while a consumer is occupied, and the next read can collapse accumulated state into one result. With EFD_SEMAPHORE, the same object exposes a different consumption rule without changing its readiness model.

The object is a counter behind a file descriptor

eventfd(initval, flags) creates an eventfd object and returns a file descriptor referring to it. The kernel initializes the counter from initval. The descriptor can be monitored by select(), poll(), and epoll alongside sockets and other readiness sources.

A write supplies exactly one 8-byte integer value. The kernel adds that value to the counter. The value UINT64_MAX is rejected, and ordinary userspace writes cannot raise the counter above UINT64_MAX - 1.

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

if (n != sizeof(increment)) {
    /* handle failure */
}

If an addition would exceed the permitted maximum, a blocking write waits until a read creates capacity. With EFD_NONBLOCK, the same condition fails with EAGAIN.

The counter therefore carries quantity as well as notification state. Writing 3 is not equivalent to writing three bytes to a pipe; it atomically contributes the numeric value three to the eventfd counter.

Default reads drain the accumulated value

Without EFD_SEMAPHORE, a successful read returns the current nonzero counter as one 8-byte host-endian integer and resets the counter to zero.

uint64_t pending;
ssize_t n = read(efd, &pending, sizeof(pending));

if (n == sizeof(pending)) {
    process_notifications(pending);
}

Suppose producers write 2, 4, and 1 before the consumer runs. If no read occurs between those writes, the counter reaches 7. The next default-mode read returns 7, then leaves the counter at zero.

This is aggregation, not preservation of producer boundaries. The consumer can recover the accumulated numeric total, but it cannot infer whether that total came from one write of 7, seven writes of 1, or another combination. Protocols requiring per-event payloads or ordering need a separate data channel.

When the counter is zero, a blocking read waits for it to become nonzero. A nonblocking read fails with EAGAIN instead.

EFD_SEMAPHORE changes consumption granularity

Creating the object with EFD_SEMAPHORE changes successful reads. Each read returns the value 1 and decrements the counter by one rather than returning the full counter and resetting it.

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

If the counter is 7, seven successful semaphore-mode reads are needed to reduce it to zero. Readiness remains tied to whether the counter is nonzero; the flag changes how much state one read consumes.

This mode can represent units of available work or permits when a unit count is sufficient. It still does not identify individual producers or attach metadata to each unit. The counter records quantity, not provenance.

Readiness follows counter capacity

An eventfd is readable when its counter is greater than zero. It is writable when a value of at least 1 can be added without blocking. These conditions let an event loop react to both pending notification state and counter saturation.

The usual userspace write() path cannot overflow the counter because additions that would exceed UINT64_MAX - 1 block or return EAGAIN. Linux documents a separate overflow condition for kernel eventfd signal posts; poll() reports POLLERR for that exceptional state, and a read returns UINT64_MAX.

That kernel-side overflow case is distinct from an application repeatedly calling write(). Treating the two paths as identical would assign behavior to userspace writes that the API explicitly prevents.

Descriptor duplication preserves the shared object

After fork(), an inherited copy of an eventfd descriptor refers to the same eventfd object. Descriptor duplication has the same consequence: the references share one counter rather than creating independent counters.

This makes eventfd suitable for synchronization paths spanning threads or related processes, but ownership must remain explicit. A read through any reference consumes counter state visible through all other references to that object.

EFD_CLOEXEC sets FD_CLOEXEC at creation and avoids a separate race-prone fcntl() step when the descriptor should not survive execve(). EFD_NONBLOCK similarly sets nonblocking status at creation.

Notification count and work state are separate concepts

An eventfd counter is often used as a wakeup bridge for another data structure. In that design, the counter signals that work may be available, while a queue or ring contains the actual records. The numeric eventfd value does not automatically equal the number of records currently consumable.

This boundary is especially important when another subsystem posts eventfd notifications with batching semantics. For example, an interface may use eventfd only as a hint to inspect its own completion state. Correctness then comes from draining the authoritative queue or ring, not from assuming a one-to-one relation between eventfd increments and application objects.

eventfd provides a compact kernel-maintained counter that participates in descriptor readiness. Default reads exchange aggregation for a single drain operation; EFD_SEMAPHORE preserves unit-by-unit consumption. Both modes expose the same core boundary: the object records numeric pending state, not an ordered event log.