An eventfd descriptor becomes readable when its kernel-maintained counter is greater than zero. A write does not enqueue a variable-length message. It adds an unsigned 64-bit value to that counter, turning accumulated notification state into ordinary file-descriptor readiness.
This boundary is useful in systems where a thread or kernel facility must wake an event loop without introducing a byte-stream protocol. The state carried by the descriptor is deliberately narrow: a counter, a readiness condition, and two possible consumption semantics.
Writes accumulate counter state
eventfd() creates an eventfd object with an initial counter value and returns a file descriptor referring to it. The counter is maintained by the kernel as an unsigned 64-bit integer.
A successful write() transfers exactly eight bytes interpreted in host byte order and adds that value to the counter. The value UINT64_MAX is rejected. The largest normal counter value is UINT64_MAX - 1.
uint64_t increment = 3;
ssize_t n = write(event_fd, &increment, sizeof(increment));If adding the supplied value would exceed the permitted maximum, a blocking write waits until a read creates enough counter capacity. With EFD_NONBLOCK, the same condition produces EAGAIN instead.
This differs from a pipe used only as a wakeup channel. Three writes of value 1 can collapse into counter value 3; there are no three independent payload records to preserve.
Default reads drain the accumulated value
Without EFD_SEMAPHORE, a successful read() returns the current nonzero counter as one eight-byte integer and resets the counter to zero.
uint64_t pending;
ssize_t n = read(event_fd, &pending, sizeof(pending));
if (n == sizeof(pending)) {
process_notifications(pending);
}Suppose producers write 2, 4, and 1 before the consumer reads. In the absence of intervening reads, the counter reaches 7. The consumer receives 7 and the counter returns to zero.
The result represents accumulated quantity, not producer identity or ordering. Applications that need per-event metadata require another data structure or transport. An eventfd can signal that such data is available, but its counter does not contain that data.
EFD_SEMAPHORE changes consumption granularity
Creating the descriptor with EFD_SEMAPHORE leaves write behavior additive but changes successful reads. Each read returns the value 1 and decrements the counter by one.
For a counter value of 7, seven successful reads can consume the state one unit at a time. This gives the descriptor semaphore-like consumption semantics while retaining descriptor readiness and the same counter storage boundary.
int event_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE);The flag does not create a POSIX semaphore and does not attach ownership or fairness guarantees to waiters. It changes the eventfd read operation from drain-all to decrement-one behavior.
Readiness follows counter capacity
poll(), select(), and epoll() can monitor an eventfd alongside sockets and other descriptors. Readability means the counter is greater than zero. Writability means at least value 1 can be added without blocking.
That mapping gives an event loop a compact state transition:
counter = 0 -> not readable
counter > 0 -> readable
successful drain -> counter = 0With EFD_SEMAPHORE, a successful read may leave the counter above zero, so the descriptor remains readable until enough reads consume the pending units.
Readiness is therefore level-sensitive state at the eventfd object, independent of whether an event loop uses level-triggered or edge-triggered epoll. Edge-triggered consumers still need to apply the normal rule of consuming available state until a nonblocking operation reports no more progress.
Descriptor copies refer to the same object
A descriptor inherited across fork() refers to the same eventfd object. Descriptor duplication has the same consequence: operations through those descriptors act on one shared counter rather than private copies.
The object remains alive while references to it remain open. Closing one duplicate does not discard counter state if another descriptor still refers to the object.
This reference model makes eventfd suitable for narrowly scoped coordination across threads or related processes, but descriptor transfer and inheritance are part of the synchronization design. EFD_CLOEXEC prevents an unintended reference from surviving a successful execve().
Counter semantics define the boundary
An eventfd is not a general message queue. It preserves a numeric aggregate and exposes whether that aggregate can be consumed or extended without blocking. Default reads exchange batching for compactness by draining the aggregate in one operation; EFD_SEMAPHORE trades that drain behavior for unit-at-a-time consumption.
The useful property is the conversion of counter state into file-descriptor readiness. Producers can post numeric state, consumers can observe it through the same multiplexing interfaces used for I/O, and the kernel keeps the transition between zero and nonzero state attached to one descriptor-backed object.