An eventfd normally turns its entire nonzero counter into one read result and resets the counter to zero. Creating it with EFD_SEMAPHORE changes only the read side: each successful read returns the 64-bit value 1 and subtracts one from the kernel-maintained counter.
That difference lets several units accumulated by writers remain separately consumable. The object is still an eventfd, with the same counter, write rules, descriptor lifetime, and readiness integration.
The counter remains a 64-bit kernel object
eventfd() creates an object containing an unsigned 64-bit counter. The initval argument supplies its initial value, while the returned file descriptor names the object.
int efd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK | EFD_CLOEXEC);A write supplies exactly eight bytes containing a uint64_t. The value is added to the counter. EFD_SEMAPHORE does not change that operation.
uint64_t n = 3;
write(efd, &n, sizeof(n));After this write, a counter that started at zero contains three. The difference appears when readers consume that state.
Semaphore mode consumes one unit per read
Without EFD_SEMAPHORE, a successful read of a nonzero counter returns its current value and resets it to zero.
With semaphore mode, a successful read instead returns 1 and decrements the counter by one.
counter before read returned value counter after read
3 1 2
2 1 1
1 1 0The read buffer must still be large enough for an eight-byte integer. A smaller buffer causes EINVAL; semaphore mode does not turn the descriptor into a byte-oriented stream.
The returned integer is in host byte order, matching ordinary eventfd reads.
A zero counter controls blocking and readiness
When the counter is zero, a blocking read waits until the counter becomes nonzero. If the descriptor uses EFD_NONBLOCK, the same read fails with EAGAIN.
This gives the counter a direct readiness boundary. The descriptor is readable when the counter is greater than zero. In semaphore mode, one successful read may leave it readable because the counter can remain above zero.
counter = 4
|
+-- read -> 1, counter = 3 [still readable]
+-- read -> 1, counter = 2 [still readable]
+-- read -> 1, counter = 1 [still readable]
`-- read -> 1, counter = 0 [not readable]That behavior matters in epoll, poll, or select loops. One readiness notification does not imply that one read drains all accumulated units.
Writes add units in batches
Semaphore-like reads do not require writers to post only the value one. A writer can add a larger value in a single write, subject to the normal eventfd counter limits.
A write of five followed by five successful semaphore-mode reads consumes the same accumulated count one unit at a time. The batching boundary on the producer side is therefore independent of the consumption boundary on the reader side.
This is useful when the counter represents available work tokens or another count where individual consumers should claim one unit. It is not a message queue: five units carry no five distinct payloads, ordering metadata, or producer identity.
The maximum value keeps a reserved boundary
Normal writes may raise the counter only as far as one less than the maximum unsigned 64-bit value. Writing UINT64_MAX itself is invalid and fails with EINVAL.
If adding a write would exceed the permitted counter range, a blocking write waits until a read creates enough room. With EFD_NONBLOCK, it fails with EAGAIN instead.
Semaphore mode does not alter these producer-side limits. It changes the rate at which reads reduce the counter, which can affect how quickly space becomes available to blocked writers.
Multiple readers share one counter
Duplicated descriptors that refer to the same eventfd object share its counter. The same is true after fork() when the inherited descriptor still refers to that object.
With EFD_SEMAPHORE, concurrent successful readers each consume one unit from the shared counter. The kernel performs the counter operation as part of the eventfd read; applications do not need a separate userspace load-and-decrement sequence for that counter.
This property should not be expanded into a broader scheduling guarantee. The interface does not promise fair distribution among waiting threads or processes. It defines counter consumption, not a fairness policy.
eventfd readiness fits descriptor-driven loops
An eventfd can be monitored with select, poll, and epoll. That makes the counter usable as a synchronization signal inside an event loop alongside sockets, timers, and other descriptor-backed sources.
Semaphore mode preserves this integration while changing what one read consumes. An event loop that wants to process all currently available units can continue reading until nonblocking read() returns EAGAIN. A loop that intentionally claims one unit can stop after one successful read.
Those are application policies layered over the same readiness rule. The kernel exposes whether the counter is nonzero; it does not prescribe how many units a worker should consume before returning to other events.
The mode changes consumption granularity, not event identity
EFD_SEMAPHORE is narrow by design. It converts a draining counter read into a one-unit counter read. It does not attach payloads to units, identify writers, preserve individual write boundaries, or provide a fairness contract among readers.
That boundary separates eventfd from message-oriented IPC. The stored state is a count. Semaphore mode makes that count divisible across reads, allowing each successful read to claim one unit while leaving any remainder available through the same descriptor.