poll() Basics
Intermediate
1 of 2
What is poll()?
When you write a server or any program that needs to watch multiple file descriptors at once (sockets, pipes, terminals), you have a problem: you cannot just call read() on each one because read() will block and you will miss events on the others.
poll() solves this by letting you hand a list of file descriptors to the kernel and say: “Tell me when any of these are ready.” The kernel checks all of them and returns a count of how many are ready. You then loop through your list and handle only the ones that have events.
poll() vs select(): Both do the same job. The key difference is how you describe what to watch. With select() you use three separate bit-sets (read set, write set, error set). With poll() you use a single array of structures, each holding the fd and a bitmask of the events you care about. This makes poll() easier to use when you have many file descriptors.
| fd | events | revents |
|---|---|---|
| 3 | POLLIN | POLLIN |
| 7 | POLLOUT | 0 |
| 12 | POLLIN | POLLERR |
Here is the poll() system call declaration:
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
/*
* Returns:
* Positive number = number of ready file descriptors
* 0 = timeout expired, nothing is ready
* -1 = error (check errno)
*/
The three arguments are:
| Argument | Type | What it means |
|---|---|---|
fds |
struct pollfd[] |
Array of file descriptors to watch, each with requested events |
nfds |
nfds_t |
Number of elements in the fds array (unsigned integer) |
timeout |
int |
How long to wait in milliseconds (-1 = wait forever, 0 = don’t wait) |
Each element in the fds[] array is a struct pollfd. You fill in the fd and events fields before the call. After poll() returns, you read the revents field to know what actually happened.
struct pollfd {
int fd; /* The file descriptor to watch */
short events; /* What events YOU want to watch (set before calling poll) */
short revents; /* What events ACTUALLY happened (filled in by kernel) */
};
Important: events and revents are bit masks. This means you can combine multiple event flags using the bitwise OR operator (|). The kernel also sets multiple bits in revents if multiple events occur at the same time.
These are the bit-mask constants you use in events and that the kernel sets in revents. Understanding these is critical for any I/O multiplexing code.
| Flag | In events? | In revents? | What it means |
|---|---|---|---|
| INPUT EVENTS | |||
| POLLIN | ✔ | ✔ | Data is available to read (the main flag you will use) |
| POLLRDNORM | ✔ | ✔ | Same as POLLIN on Linux. Defined separately for POSIX compatibility. |
| POLLRDBAND | ✔ | ✔ | Priority band data readable. Unused on Linux (STREAMS-specific). Ignore it. |
| POLLPRI | ✔ | ✔ | High-priority / out-of-band data is available (e.g. TCP urgent data) |
| POLLRDHUP | ✔ | ✔ | Peer closed the connection or shut down the writing half. Linux-specific (kernel ≥ 2.6.17). Requires _GNU_SOURCE. |
| OUTPUT EVENTS | |||
| POLLOUT | ✔ | ✔ | Writing is possible now without blocking (the main output flag) |
| POLLWRNORM | ✔ | ✔ | Same as POLLOUT on Linux. |
| POLLWRBAND | ✔ | ✔ | Priority data can be written. Meaningful only on STREAMS. Ignore on Linux. |
| STATUS / ERROR FLAGS (only in revents, kernel sets these — you cannot request them) | |||
| POLLERR | — | ✔ | An error occurred on the file descriptor. Always check for this in your result loop. |
| POLLHUP | — | ✔ | Hangup on the fd. For pipes: write end was closed. For sockets: connection dropped. |
| POLLNVAL | — | ✔ | The fd is not a valid open file descriptor (e.g. you already closed it). |
| POLLMSG | — | — | Unused on Linux. Ignore. |
POLLIN, POLLOUT, POLLPRI, POLLRDHUP, POLLHUP, POLLERR. The rest are either synonyms or STREAMS-specific (Linux doesn’t have STREAMS).A great feature of poll() is that you can disable monitoring of one fd without rebuilding the whole array. Two ways to do this:
events field to 0. poll() will skip this fd and always return revents = 0./* Temporarily stop watching fd 5 */
pollFd[2].events = 0;
fd field. poll() ignores negative fds and sets revents = 0./* Disable by negating the fd */
pollFd[2].fd = -pollFd[2].fd;
/* Re-enable later */
pollFd[2].fd = -pollFd[2].fd;
FD_CLR() and recalculate the max fd. With poll() you just flip a field in your array — much simpler.Some poll() constants are not visible by default. You must define feature test macros before including <poll.h> to unlock them.
/* To get POLLRDNORM, POLLRDBAND, POLLWRNORM, POLLWRBAND */
#define _XOPEN_SOURCE 600
#include <poll.h>
/* To get POLLRDHUP (Linux-specific, kernel >= 2.6.17) */
#define _GNU_SOURCE
#include <poll.h>
/* In practice, defining _GNU_SOURCE gives you everything */
#define _GNU_SOURCE
#include <poll.h>
This example shows the typical pattern for using poll(). We watch two file descriptors at the same time: standard input (fd 0) and the read end of a pipe (fd 3).
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
int pipefd[2];
char buf[256];
/* Create a pipe */
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
/* Write something into the pipe so we have something to read */
write(pipefd[1], "hello from pipe", 15);
/*
* Set up the pollfd array.
* We watch two fds:
* fds[0] = stdin (fd 0) — watch for input
* fds[1] = pipefd[0] — watch for input
*/
struct pollfd fds[2];
fds[0].fd = STDIN_FILENO; /* fd 0 */
fds[0].events = POLLIN; /* we want to know when data arrives */
fds[1].fd = pipefd[0]; /* read end of pipe */
fds[1].events = POLLIN;
printf("Calling poll()...\n");
/* timeout = 5000 ms = 5 seconds */
int ready = poll(fds, 2, 5000);
if (ready == -1) {
perror("poll");
exit(EXIT_FAILURE);
}
if (ready == 0) {
printf("Timeout: nothing became ready in 5 seconds\n");
return 0;
}
printf("poll() returned %d ready fd(s)\n", ready);
/* Check each fd */
for (int i = 0; i < 2; i++) {
if (fds[i].revents == 0)
continue; /* nothing happened on this fd */
printf("fd %d: revents = 0x%x\n", fds[i].fd, fds[i].revents);
if (fds[i].revents & POLLIN) {
int n = read(fds[i].fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf(" Read %d bytes: %s\n", n, buf);
}
}
if (fds[i].revents & POLLERR)
printf(" Error on fd %d\n", fds[i].fd);
if (fds[i].revents & POLLHUP)
printf(" Hangup on fd %d\n", fds[i].fd);
}
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
- Fill
fds[i].fdandfds[i].eventsfor each fd you want to watch. - Call
poll(fds, nfds, timeout). - Loop through the array. If
fds[i].revents != 0, check the bits and handle the event. - Always check for
POLLERRandPOLLHUPeven if you didn’t request them.
Both are I/O multiplexing calls that let you monitor multiple file descriptors. The difference is in how you specify fds. select() uses three bit-set arrays (read, write, except) and has a hard limit of FD_SETSIZE (1024) fds. poll() uses an array of struct pollfd — no hard fd limit. You also do not need to rebuild the fd sets after every call as you do with select(), making poll() easier to manage for large numbers of fds.
revents is written by the kernel after poll() returns. It tells you which events actually occurred on that fd. You should not set it before calling poll() — the kernel overwrites it. Only fd and events are inputs. revents is the output.
Because the kernel always reports errors and hangups regardless of whether you ask for them. They are status conditions, not selectable events. If you put them in events, poll() ignores them there. But you must still check for them in revents after every poll() call.
Two ways: (1) set fds[i].events = 0, or (2) negate the fd value: fds[i].fd = -fds[i].fd. Both cause poll() to skip that entry and return revents = 0 for it. You can re-enable it without rebuilding the array, which is an advantage over select().
POLLRDHUP is a Linux-specific flag (kernel ≥ 2.6.17, requires _GNU_SOURCE). It is set in revents when the peer closes the write side of a TCP connection (i.e. sends FIN). Without it, you only discover a closed connection after calling read() and getting 0 bytes. With POLLRDHUP you know immediately from poll() itself, making server cleanup faster and cleaner.
You must define _GNU_SOURCE before including <poll.h>. Without it, the POLLRDHUP constant is not defined and your code will not compile. For the POSIX constants POLLRDNORM, POLLRDBAND, POLLWRNORM, and POLLWRBAND you need _XOPEN_SOURCE.
Continue to Part 2
Learn about the timeout argument, return values, and a complete working example program.
