select / poll / epoll

Three generations of multiplexed I/O — waiting on multiple file descriptors simultaneously without blocking on any one of them.

select(2)

The original POSIX interface. Takes a bitmask (fd_set) of up to FD_SETSIZE (typically 1024) FDs. Returns which are ready.

fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd1, &rfds);
FD_SET(fd2, &rfds);
select(maxfd + 1, &rfds, NULL, NULL, NULL);
if (FD_ISSET(fd1, &rfds)) { /* fd1 is readable */ }

Limitation: O(n) scan; FD number limit (1024 by default); fd_set must be rebuilt on every call.

poll(2)

Replaces the bitmask with an array of struct pollfd. No FD number limit. Still O(n) scan — the kernel copies the array on every call.

epoll(7) (Linux)

Kernel-side event registration. Add FDs once with epoll_ctl(); retrieve only the ready ones with epoll_wait(). O(1) for the wait regardless of how many FDs are watched. The foundation of every high-performance Linux server (nginx, Redis, Node.js event loop).

int efd = epoll_create1(0);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = fd };
epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev);
// later:
struct epoll_event events[MAX];
int n = epoll_wait(efd, events, MAX, -1);

In the curriculum

Covered in m08-sockets-networking lesson 8-2 (“Handling Multiple Clients — select and poll”) and the PDF ch09 Going Deeper section (fd_set internals, FD_SETSIZE, O(n) scan, poll vs epoll). Also introduced in m09-concurrency as the event-driven alternative to threading.

See also

sockets, file-descriptors, threads