On this page
epoll and Event-Driven Programming
Coverage: epoll_create1/epoll_ctl/epoll_wait → LT vs ET → timerfd/signalfd/eventfd → Comparison with select/poll → Key points of epoll internal implementation Applicable to: Linux 2.6+
Overview
epoll is the standard interface for high-performance event-driven programming on Linux. nginx, HAProxy, and Redis are all built on epoll. The fundamental difference from select/poll is that epoll provides O(1) waiting (returning only active file descriptors), whereas select/poll must scan the entire file descriptor set with O(n) complexity each time.
epoll API
int epfd = ; // Create an epoll instance
struct epoll_event ev = ;
; // Register the file descriptor
struct epoll_event events;
int nfds = ;
for
epoll_event.events
EPOLLIN: Readable (data arrived or connection established)
EPOLLOUT: Writable (send buffer has space)
EPOLLERR: Error (automatic, no need to set)
EPOLLHUP: Hangup (peer closed)
EPOLLRDHUP: Peer closed the write half of the connection (4.17+)
EPOLLET: Edge-Triggered (see below)
EPOLLONESHOT: One-shot (automatically removed after triggering, until re-added via EPOLL_CTL_MOD)
EPOLLEXCLUSIVE: Exclusive wake-up (reduces thundering herd, 4.5+)
LT vs ET: Key Differences
LT (Level-Triggered, default):
As long as the fd remains in a readable/writable state → epoll_wait returns every time
→ Simple: You can read only part of the data and read the rest next time
→ Risk: If not fully read, epoll_wait will return again next time → loop
ET (Edge-Triggered):
Notifies only on state transitions (unreadable→readable, unwritable→writable)
→ Efficient: No repeated notifications
→ Requirement: Must read/write until EAGAIN (process everything)
→ Must use O_NONBLOCK (otherwise read may block)
Correct Usage of ET Mode
// In ET mode, you must loop to read:
while
timerfd / signalfd / eventfd: epoll Partners
// Unified event loop: Convert all asynchronous events into file descriptors → managed uniformly by epoll
int epfd = ;
// 1. Network socket
;
// 2. timerfd: Timer
int tfd = ;
;
// 3. signalfd: Signals
sigset_t mask; ; ;
;
int sfd = ;
;
// 4. eventfd: Inter-thread notification
int efd = ;
;
// Single thread: epoll_wait → Unified handling of all events
while
Comparison with select/poll
| select | poll | epoll | |
|---|---|---|---|
| FD Limit | FD_SETSIZE (1024) | Unlimited | Unlimited |
| Waiting Time | O(n) scan | O(n) scan | O(1) based on active count |
| State Storage | Passed every time (stack) | Passed every time (stack) | Maintained by kernel (RB-tree) |
| Edge Triggering | No | No | Yes (ET) |
| 10k idle connections | ~100μs | ~100μs | ~1μs |
epoll Internal Implementation
// fs/eventpoll.c
// An epoll instance contains:
// 1. Red-black tree (rbr): List of registered fds (epoll_ctl ADD → insert)
// 2. Ready list (rdllist): Active fds (returned by epoll_wait)
//
// When an event arrives → driver/protocol stack calls ep_poll_callback:
// → Check if event matches? → Add to rdllist
// → If there are waiters (epoll_wait) → Wake up
References
- man: epoll(7), epoll_create1(2), epoll_ctl(2), epoll_wait(2)
- Source code:
fs/eventpoll.c - LWN: "The epoll API", "Edge triggered epoll"
Keywords: epoll, EPOLLET, EPOLLONESHOT, edge-triggered, timerfd, signalfd, eventfd, event loop