Linux epoll API for Scalable IO Event Notification
The epoll (event poll) API is a scalable I/O event
notification facility in the Linux kernel designed to monitor multiple
file descriptors to see if I/O is possible on any of them. This article
explains the primary function of epoll, how it overcomes
the architectural limitations of older system calls like
select and poll, its core operational
mechanisms, and why it serves as the foundation for high-concurrency
network servers in modern Linux environments.
The Problem with Legacy I/O Multiplexing
Before epoll, Linux systems relied on
select() and poll() to handle multiplexed I/O.
Both mechanisms suffer from severe performance degradation as the number
of monitored connections grows:
- Linear Complexity (\(O(N)\)): Both
select()andpoll()require passing the entire set of file descriptors from user space to kernel space on every invocation. The kernel must iterate through every descriptor to check its status. - Scan Overhead in User Space: When the call returns, user space must loop through the entire list to identify which descriptors are ready for I/O.
- Memory Copying: Constantly copying large descriptor sets between user space and kernel space creates significant CPU and memory bandwidth overhead when handling tens of thousands of idle connections.
Core Function and Mechanism of epoll
The primary function of epoll is to decouple the
registration of monitored file descriptors from the actual event-waiting
process, providing an \(O(1)\) lookup
time relative to the number of monitored descriptors. Instead of passing
an entire array of descriptors on every call, epoll
maintains state within the kernel.
The epoll interface operates through three primary
system calls:
epoll_create1(int flags): Allocates anepollinstance in the kernel and returns a file descriptor referencing it.epoll_ctl(int epfd, int op, int fd, struct epoll_event *event): Manages the "interest list." It allows applications to add (EPOLL_CTL_ADD), modify (EPOLL_CTL_MOD), or remove (EPOLL_CTL_DEL) specific file descriptors to monitor for events such as readability (EPOLLIN) or writability (EPOLLOUT).epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout): Suspends the calling thread until one or more monitored file descriptors are ready for I/O. It returns only the descriptors that actually have pending events, populating a user-provided array.
Internal Architecture
To achieve high efficiency, the Linux kernel structures the
epoll subsystem using two primary data structures:
- Red-Black Tree: Stores the interest list. Adding,
modifying, or removing a file descriptor via
epoll_ctl()takes \(O(\log N)\) time, ensuring fast lookups and state management. - Ready List (Doubly Linked List): When an I/O event
occurs on a monitored socket, a kernel callback adds that descriptor to
the ready list. When
epoll_wait()is called, the kernel simply checks if the ready list is non-empty and transfers those active events to user space, taking \(O(K)\) time, where \(K\) is the number of ready events, rather than the total number of monitored connections (\(N\)).
Level-Triggered vs. Edge-Triggered Modes
The epoll API supports two distinct event notification
modes:
- Level-Triggered (LT): The default behavior.
epoll_wait()notifies the application as long as the underlying buffer has data available to read or space available to write. If the application reads only part of the incoming data, subsequent calls toepoll_wait()will continue to flag the descriptor. - Edge-Triggered (ET): Enabled via the
EPOLLETflag.epoll_wait()delivers a notification only when a state change occurs (e.g., new data arrives). The application must consume all available data using non-blocking I/O until the read operation returnsEAGAINorEWOULDBLOCK. Edge-triggered mode reduces redundant kernel wakeups and is common in ultra-high-throughput architectures.
Summary
The epoll API provides scalable, event-driven I/O
notifications by maintaining kernel-side state and returning only ready
descriptors. By eliminating the \(O(N)\) scanning bottlenecks inherent to
select() and poll(), epoll
enables software like Nginx, Node.js, Redis, and modern asynchronous
runtimes to scale efficiently to hundreds of thousands of concurrent
connections.