Linux eventfd exposes a kernel-maintained 64-bit counter through a file descriptor. Its compact interface hides an important semantic choice: a normal read drains the current counter value, while an EFD_SEMAPHORE read consumes exactly one unit. The write path still adds values to the same counter.

That distinction changes the object from an aggregate notification counter into a descriptor-backed source of individually consumable units. The readiness model remains compatible with poll, epoll, and related descriptor multiplexing, so the same object can connect producer accounting with an event loop without adding a separate pipe payload.

The counter is shared state behind a descriptor

An eventfd object contains an unsigned 64-bit counter maintained by the kernel. eventfd(initval, flags) creates the object and returns a descriptor referring to it. A successful write supplies an eight-byte unsigned integer and adds that value to the counter, subject to the interface’s counter limit.

Conceptually:

counter = 2

write(fd, 3)
counter = 5

The descriptor is not a byte stream. A successful read transfers one eight-byte integer, and buffers smaller than eight bytes are rejected. The integer uses host byte order.

This fixed-width protocol matters because readiness and accounting refer to the counter, not to queued write records. Two writes of 2 and 3 produce the same counter state as one write of 5 before a reader consumes the value.

Normal reads drain accumulated work

Without EFD_SEMAPHORE, a successful read returns the current nonzero counter and resets it to zero.

counter = 5
read -> 5
counter = 0

This mode fits notification coalescing. Several producers can increment the object, while a consumer wakes and obtains the accumulated amount in one operation. The returned value can represent a count of pending actions, completions, or other units defined by the application.

The kernel does not preserve producer boundaries in that value. If a consumer needs distinct messages, ordering metadata, or variable payloads, eventfd alone does not provide those properties.

EFD_SEMAPHORE changes only read consumption

Creating the object with EFD_SEMAPHORE changes the successful read result. When the counter is nonzero, a read returns 1 and decrements the counter by one.

counter = 5

read -> 1
counter = 4

read -> 1
counter = 3

Writes still add their supplied values. A producer can therefore publish multiple units with one write, while consumers claim those units one at a time.

The flag does not turn the object into a POSIX semaphore or a general mutex. It gives eventfd semaphore-like read semantics over its counter. There is no ownership concept attached to a unit, and the interface does not carry protected data with the decrement.

Zero determines blocking and readiness

A read can proceed when the counter is nonzero. If the counter is zero, a blocking descriptor waits for the counter to become nonzero. With EFD_NONBLOCK, the same condition causes read() to fail with EAGAIN instead.

This produces a direct readiness boundary:

counter == 0  -> no unit available
counter > 0   -> readable

In normal mode, one reader can drain the accumulated counter and make the object non-readable immediately. In semaphore mode, one successful read removes only one unit. If the counter remains above zero, additional units remain available.

That difference is relevant with multiple consumers. Readiness indicates that some consumption can succeed; it does not reserve a unit for a particular thread merely because that thread observed a readiness event. Consumers still need to handle races between readiness observation and the actual read, especially with nonblocking descriptors.

Write saturation creates backpressure at the counter boundary

The counter cannot accept the all-ones 64-bit value through the ordinary write interface. A write that would exceed the permitted maximum blocks until a read creates enough space, or fails with EAGAIN when the descriptor is nonblocking.

This gives eventfd a bounded accounting state even though it does not queue individual records. Large increments therefore need the same error handling discipline as other nonblocking descriptor operations.

A value of UINT64_MAX is not a valid ordinary write value. The exceptional all-ones read value documented for kernel-side overflow is associated with an overflow case caused by kernel signaling paths; normal userspace writes cannot create that state by simply adding beyond the maximum.

Descriptor semantics make the counter composable

Because eventfd is represented by a file descriptor, it can participate in descriptor-oriented infrastructure. An event loop can include an eventfd beside sockets, pipes, timer descriptors, or signal descriptors in an epoll set.

That property is often more significant than the counter arithmetic itself. A thread that needs to wake an event loop can write to an eventfd rather than requiring a separate condition-variable path outside the loop’s wait primitive.

EFD_CLOEXEC can establish close-on-exec at creation, avoiding a separate fcntl() transition. EFD_NONBLOCK similarly sets nonblocking status as part of creation. These flags concern descriptor lifecycle and I/O behavior; EFD_SEMAPHORE concerns counter consumption.

Aggregation and unit consumption encode different contracts

Normal and semaphore modes expose the same underlying counter but imply different consumer contracts.

Normal mode asks a consumer to take the accumulated value as one observation. It is suitable when coalescing is acceptable and the magnitude itself carries useful accounting information.

Semaphore mode asks each successful read to claim one unit. It is suitable when the counter represents a pool of interchangeable permits or work units and each consumer should remove only one per read.

Neither mode provides a payload queue. The application still needs separate storage when each unit has associated data. A common design is to keep data in a queue and use eventfd only as a readiness or accounting signal, with synchronization rules that keep queue state and counter updates consistent.

The key boundary is therefore not simply blocking versus nonblocking I/O. It is whether a read consumes the entire accumulated count or one unit from it. EFD_SEMAPHORE moves that policy into the kernel-visible descriptor operation while retaining eventfd’s compact counter and event-loop integration.