epoll(7)
๐ epoll โ I/O event notification facility (yes that's the NAME, bestie)
okay so this page is epoll(7) from the Miscellaneous Information Manual, and it's literally the group chat where your file descriptors come to say "hey i'm ready". let's get into it ๐
โจ SYNOPSIS โจ
#include <sys/epoll.h>
๐ธ DESCRIPTION ๐ธ
so bestie, the epoll API does basically the same job as poll(2): it watches a whole bunch of file descriptors at once to see if I/O is possible on any of them. And here's the iconic part โ epoll can run either as an edge-triggered or a level-triggered interface, and it scales beautifully to large numbers of watched file descriptors. she's popular for a reason. ๐
The main character of the whole epoll API is the epoll instance โ an in-kernel data structure that, from user space's point of view, is basically a container holding two lists:
- The interest list (sometimes called the epoll set): the set of file descriptors the process has registered an interest in monitoring. ๐ซถ
- The ready list: the set of file descriptors that are "ready" for I/O. The ready list is a subset of (or, to be more precise, a set of references to) the file descriptors in the interest list. The kernel fills the ready list in dynamically as a result of I/O activity on those file descriptors. โจ
here are the system calls that create and manage an epoll instance:
epoll_create(2)creates a new epoll instance and hands you back a file descriptor referring to that instance. (the newerepoll_create1(2)is justepoll_create(2)with extra features, she extends the functionality.) ๐- then you register interest in particular file descriptors with
epoll_ctl(2), which adds items to the interest list of the epoll instance. - and
epoll_wait(2)waits for I/O events, blocking the calling thread if no events are currently available. (think of this system call as fetching items off the ready list of the epoll instance.) ๐ฆ
๐ซ Level-triggered and edge-triggered ๐ซ
The epoll event distribution interface can act both as edge-triggered (ET) and as level-triggered (LT). The difference between the two mechanisms goes like this. imagine this scenario plays out:
- The file descriptor that represents the read side of a pipe (
rfd) is registered on the epoll instance. - A pipe writer writes 2 kB of data on the write side of the pipe.
- A call to
epoll_wait(2)is done, and it will returnrfdas a ready file descriptor. - The pipe reader reads 1 kB of data from
rfd. - A call to
epoll_wait(2)is done.
So if the rfd file descriptor was added to the epoll interface using the EPOLLET (edge-triggered) flag, the epoll_wait(2) call in step 5 will probably hang ๐ญ โ even though there's still data sitting right there in the file input buffer, and meanwhile the remote peer is probably sitting there expecting a response based on the data it already sent. why? because edge-triggered mode only delivers events when changes occur on the monitored file descriptor โ meaning an event gets generated each time a chunk of data arrives. so in step 5 the caller might end up waiting for data that's already inside the input buffer. in the example above, an event on rfd gets generated because of the write done in step 2, and that event is consumed in step 3. since the read operation in step 4 doesn't consume the whole buffer's data, the epoll_wait(2) call in step 5 might block indefinitely. ๐
an application that uses the EPOLLET flag should use nonblocking file descriptors, so that a blocking read or write doesn't starve a task that's handling multiple file descriptors.
The suggested way to use epoll as an edge-triggered (EPOLLET) interface is:
- with nonblocking file descriptors; and
- by waiting for an event only after
read(2)orwrite(2)returnEAGAIN.
meanwhile, when you use it as a level-triggered interface (that's the default, i.e. when EPOLLET is not specified), epoll is just a faster poll(2) โ and you can use it anywhere the latter is used, since it shares the exact same semantics. slay. ๐
Also, since even with edge-triggered epoll multiple events can be generated when multiple chunks of data arrive, you have the option to set the EPOLLONESHOT flag, which tells epoll to disable the associated file descriptor after it receives one event with epoll_wait(2). when EPOLLONESHOT is specified, it's your job (the caller's responsibility) to rearm the file descriptor using epoll_ctl(2) with EPOLL_CTL_MOD. don't forget, bestie. ๐ซ
Now โ if multiple threads (or processes, if child processes inherited the epoll file descriptor across fork(2)) are blocked in epoll_wait(2) waiting on the same epoll file descriptor, and a file descriptor in the interest list that's marked for edge-triggered (EPOLLET) notification becomes ready, then just one of those threads (or processes) gets woken from epoll_wait(2). that's a genuinely useful optimization for dodging "thundering herd" wake-ups in some scenarios. ๐๐ซ
๐ด Interaction with autosleep ๐ด
If the system is in autosleep mode via /sys/power/autosleep, and an event happens that wakes the device from sleep, the device driver will keep the device awake only until that event is queued. To keep the device awake until that event has been processed, you need the epoll_ctl(2) EPOLLWAKEUP flag.
When the EPOLLWAKEUP flag is set in the events field for a struct epoll_event, the system is kept awake from the moment the event is queued, through the epoll_wait(2) call that returns the event, until the subsequent epoll_wait(2) call. If the event needs to keep the system awake beyond that time, then you should take a separate wake_lock before the second epoll_wait(2) call. ๐
๐๏ธ /proc interfaces ๐๏ธ
these interfaces let you limit how much kernel memory epoll is allowed to eat:
/proc/sys/fs/epoll/max_user_watches(since Linux 2.6.28) โ this specifies a limit on the total number of file descriptors that a user can register across all epoll instances on the system. The limit is per real user ID. Each registered file descriptor costs roughly 90 bytes on a 32-bit kernel, and roughly 160 bytes on a 64-bit kernel. Currently, the default value formax_user_watchesis 1/25 (4%) of the available low memory, divided by the registration cost in bytes. ๐
๐งช Example for suggested usage ๐งช
okay so while using epoll as a level-triggered interface does have the same semantics as poll(2), the edge-triggered usage needs a lil more clarification so your application event loop doesn't stall. In this example, listener is a nonblocking socket on which listen(2) has been called. The function do_use_fd() uses the new ready file descriptor until EAGAIN is returned by either read(2) or write(2). An event-driven state machine application should, after receiving EAGAIN, record its current state so that on the next call to do_use_fd() it continues to read(2) or write(2) from where it stopped before. ๐
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Code to set up listening socket, 'listen_sock',
(socket(), bind(), listen()) omitted. */
epollfd = epoll_create1(0);
if (epollfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &addr, &addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(events[n].data.fd);
}
}
}
and when you're using it as an edge-triggered interface, for performance reasons you can add the file descriptor inside the epoll interface (EPOLL_CTL_ADD) once by specifying (EPOLLIN|EPOLLOUT). that lets you avoid continuously switching back and forth between EPOLLIN and EPOLLOUT and calling epoll_ctl(2) with EPOLL_CTL_MOD every five seconds. efficient queen. โจ
โ Questions and answers โ
- What is the key used to distinguish the file descriptors registered in an interest list?
The key is the combination of the file descriptor number and the open file description (also known as an "open file handle", i.e. the kernel's internal representation of an open file). ๐
- What happens if you register the same file descriptor on an epoll instance twice?
You will probably get EEXIST. However โ plot twist โ it is possible to add a duplicate (dup(2), dup2(2), fcntl(2) F_DUPFD) file descriptor to the same epoll instance. This can be a genuinely useful technique for filtering events, if the duplicate file descriptors are registered with different events masks. ๐ญ
- Can two epoll instances wait for the same file descriptor? If so, are events reported to both epoll file descriptors?
Yes, and events would be reported to both. However, careful programming may be needed to do this correctly. โ ๏ธ
- Is the epoll file descriptor itself poll/epoll/selectable?
Yes. If an epoll file descriptor has events waiting, then it will indicate as being readable. ๐
- What happens if one attempts to put an epoll file descriptor into its own file descriptor set?
The epoll_ctl(2) call fails (EINVAL). However, you can add an epoll file descriptor inside another epoll file descriptor set. ๐คน
- Can I send an epoll file descriptor over a UNIX domain socket to another process?
Yes, but it doesn't make sense to do this, since the receiving process would not have copies of the file descriptors in the interest list. ๐ญ
- Will closing a file descriptor cause it to be removed from all epoll interest lists?
Okay so plot twist: a file descriptor is literally just a reference to an open file description (go peep open(2) for the lore ๐ซถ). Every time an fd gets duplicated โ whether it's via dup(2), dup2(2), fcntl(2) F_DUPFD, or fork(2) โ you get a brand-new file descriptor that points at the same open file description. And an open file description doesn't ghost until every last fd referring to it has been closed. periodt.
And this is the part that catches people: an fd only gets removed from an interest list after all the file descriptors referring to that underlying open file description have been closed. Which means even after you've closed an fd that was chillin' in an interest list, events can still get reported for it if other fds pointing at the same underlying open file description are still open. ๐ญ To stop that from happening, the fd has to be explicitly removed from the interest list (via epoll_ctl(2) EPOLL_CTL_DEL) before it gets duplicated. Or, alternate route: the application makes sure every single fd gets closed โ which is lowkey hard if some library function went behind your back and duplicated them with dup(2) or fork(2) ๐.
- If more than one event happens between
epoll_wait(2)calls, do they get combined or reported separately?
They will be combined. no extra drama. โจ
- Does an operation on a file descriptor mess with events that were already collected but not yet reported?
There are two operations you can do on an existing file descriptor. A Remove would be meaningless for this case. A Modify will reread available I/O.
- Do I have to keep reading/writing a file descriptor until
EAGAINwhen I'm using theEPOLLETflag (edge-triggered mode)?
When epoll_wait(2) hands you an event, take that as a hint that the fd is ready for the I/O operation you asked about. You've gotta treat it as ready until the next (nonblocking) read/write gives you EAGAIN. When and how you use that file descriptor is entirely your call, bestie. ๐
For packet/token-oriented files (think: datagram socket, or a terminal in canonical mode), the only way to detect that you've hit the end of the read/write I/O space is to keep reading/writing until you get EAGAIN. no shortcuts, sorry ๐ญ.
For stream-oriented files (think: pipe, FIFO, stream socket), you can also detect that the read/write I/O space is exhausted by checking how many bytes got read from / written to the target file descriptor. Like, if you call read(2) asking for a certain amount of data and read(2) comes back with fewer bytes, you can be sure you've exhausted the read I/O space on that fd. Same energy when you're writing with write(2). (Skip this second technique if you can't guarantee that the monitored file descriptor always refers to a stream-oriented file. fr fr.)
๐ณ๏ธ Possible pitfalls and ways to avoid them ๐ณ๏ธ
- Starvation (edge-triggered) ๐
If there's a huge amount of I/O space, then trying to drain all of it can mean the other files never get processed โ and that's starvation. (Notably, this problem is not specific to epoll. it's a whole genre.)
The fix is to maintain a ready list and mark the file descriptor as ready inside its associated data structure. That way the application remembers which files still need processing, but it still round robins among all the ready files. Bonus: this also supports ignoring the subsequent events you receive for file descriptors that are already ready. ๐
- If using an event cache... ๐
If you use an event cache, or you store every file descriptor that came back from epoll_wait(2), then make sure you have some way to mark its closure dynamically (i.e., closure caused by a previous event's processing). Picture this: you receive 100 events from epoll_wait(2), and while handling event #47 something causes event #13 to be closed. If you remove the structure and close(2) the file descriptor for event #13, your event cache might still be out here saying there are events waiting for that file descriptor โ and that's just confusion, bestie. ๐ตโ๐ซ
One solution: while you're processing event 47, call epoll_ctl(EPOLL_CTL_DEL) to delete file descriptor 13 and close(2) it, then mark its associated data structure as removed and link it onto a cleanup list. Then if you stumble across another event for file descriptor 13 during your batch processing, you'll find out that the file descriptor was previously removed โ and there's no confusion at all. slay.
๐ VERSIONS
Some other systems serve up similar mechanisms โ for example, FreeBSD has kqueue, and Solaris has /dev/poll. not epoll but the same vibe.
๐ STANDARDS
Linux. that's it. that's the whole section. ๐
๐ฐ๏ธ HISTORY
Linux 2.5.44. glibc 2.3.2.
๐ NOTES
- The set of file descriptors being monitored through an epoll file descriptor can be viewed via the entry for the epoll file descriptor inside the process's
/proc/pid/fdinfodirectory. Peepproc(5)for more deets. ๐ - The
kcmp(2)KCMP_EPOLL_TFDoperation can be used to test whether a file descriptor is present in an epoll instance. cute little detective move ngl.
๐ SEE ALSO
epoll_create(2), epoll_create1(2), epoll_ctl(2), epoll_wait(2), ioctl_eventpoll(2), poll(2), select(2)
And that's a wrap on this page, bestie ๐ซถ โ this was Linux man-pages 6.20, dated 2026-02-08, and yeah, you just read the whole epoll(7). iconic behavior honestly ๐โจ
๐ฆ where this page comes from
- arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
- nixpkgs unstable: man-pages 6.20
- ๐ the page's license: GPL-2.0-or-later
- Copyright 2003, Davide Libenzi <davidel@xmailserver.org>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms