Topic
poll() API
Part
1 of 3
Level
Intermediate
What is poll()?
When you write a program that talks to many file descriptors at the same time โ like a server handling multiple clients โ you cannot just call read() on each one and wait. That would block your whole program.
poll() is a system call that lets you watch a list of file descriptors and tells you which ones are ready for reading or writing โ without blocking on any single one. It is part of the “Alternative I/O Models” in Linux, alongside select() and epoll.
Think of poll() like a security guard watching many doors at once. Instead of standing at one door and waiting, the guard checks all doors together and tells you which ones have activity.
Key Terms
The poll() function prototype is defined in <poll.h>:
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
/* Returns:
> 0 โ number of file descriptors that are ready
0 โ timeout expired, no fd is ready
-1 โ error (check errno)
*/
Parameters explained:
- fds[] โ array of
pollfdstructures, one per file descriptor you want to watch - nfds โ number of items in the array
- timeout โ how long to wait in milliseconds. Use
-1to block forever,0to return immediately
Each entry in the array is a struct pollfd:
struct pollfd {
int fd; /* File descriptor to watch */
short events; /* What events we are interested in (INPUT) */
short revents; /* What events actually happened (OUTPUT) */
};
You fill in fd and events before calling poll(). After poll() returns, the kernel fills in revents for you.
all fds
These are the flags you use in events (what you want to watch) and that the kernel sets in revents (what happened):
| Flag | Used in events? | Can appear in revents? | Meaning |
|---|---|---|---|
| POLLIN | Yes | Yes | Data available for reading |
| POLLOUT | Yes | Yes | Writing is possible without blocking |
| POLLPRI | Yes | Yes | Exceptional/out-of-band data (e.g. TCP OOB) |
| POLLHUP | No (ignored) | Yes | Hang-up: peer closed the connection |
| POLLERR | No (ignored) | Yes | Error condition on fd |
| POLLNVAL | No (ignored) | Yes | fd is not open / invalid |
| POLLRDHUP | Yes (Linux only) | Yes | Peer shut down writing half of stream socket |
Note: POLLHUP, POLLERR, and POLLNVAL are always reported in revents even if you did not ask for them in events. You do not need to set them โ the kernel sets them automatically when those conditions occur.
The classic use case from TLPI: create N pipes, write to some random pipes, then use poll() to find out which pipe read-ends have data ready. This is a great exercise to understand how poll() works in practice.
/* poll_pipes.c - monitor multiple pipes with poll() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <time.h>
int main(int argc, char *argv[])
{
int numPipes, numWrites;
int **pfds; /* 2D array: pfds[i][0]=read, pfds[i][1]=write */
struct pollfd *pollFd;
int ready, j, randPipe;
if (argc < 2) {
fprintf(stderr, "Usage: %s num-pipes [num-writes]\n", argv[0]);
exit(EXIT_FAILURE);
}
numPipes = atoi(argv[1]);
numWrites = (argc > 2) ? atoi(argv[2]) : numPipes;
/* Allocate arrays */
pfds = calloc(numPipes, sizeof(int *));
pollFd = calloc(numPipes, sizeof(struct pollfd));
if (!pfds || !pollFd) { perror("calloc"); exit(EXIT_FAILURE); }
for (j = 0; j < numPipes; j++) {
pfds[j] = malloc(2 * sizeof(int));
if (pipe(pfds[j]) == -1) { perror("pipe"); exit(EXIT_FAILURE); }
}
/* Write 'a' to random pipes */
srandom(time(NULL));
for (j = 0; j < numWrites; j++) {
randPipe = random() % numPipes;
printf("Writing to fd: %3d (read fd: %3d)\n",
pfds[randPipe][1], pfds[randPipe][0]);
if (write(pfds[randPipe][1], "a", 1) == -1) {
perror("write"); exit(EXIT_FAILURE);
}
}
/* Build pollfd array: watch read-ends for POLLIN */
for (j = 0; j < numPipes; j++) {
pollFd[j].fd = pfds[j][0]; /* read end */
pollFd[j].events = POLLIN; /* want to know if data is ready */
}
/* Call poll() โ timeout=-1 means block until at least one is ready */
ready = poll(pollFd, numPipes, -1);
if (ready == -1) { perror("poll"); exit(EXIT_FAILURE); }
printf("\npoll() returned: %d ready descriptor(s)\n\n", ready);
/* Check which pipes have data */
for (j = 0; j < numPipes; j++) {
if (pollFd[j].revents & POLLIN)
printf("Readable: pipe[%d], fd=%3d\n", j, pollFd[j].fd);
}
/* Cleanup */
for (j = 0; j < numPipes; j++) {
free(pfds[j]);
close(pfds[j][0]);
close(pfds[j][1]);
}
free(pfds);
free(pollFd);
return 0;
}
Compile and run:
gcc -o poll_pipes poll_pipes.c
./poll_pipes 5 3
Sample output:
Writing to fd: 5 (read fd: 4)
Writing to fd: 9 (read fd: 8)
Writing to fd: 5 (read fd: 4)
poll() returned: 2 ready descriptor(s)
Readable: pipe[0], fd= 4
Readable: pipe[2], fd= 8
pipe(). Each pipe gives two fds: [0] for reading, [1] for writing.write(pfds[randPipe][1], "a", 1). Some pipes get data, others remain empty.fd to each pipe’s read-end and events = POLLIN.revents & POLLIN is true for each fd.Pattern 1: poll() in a loop (typical server)
/* Typical server loop with poll() */
#include <poll.h>
#include <stdio.h>
#define MAX_FDS 100
void server_loop(int listen_fd)
{
struct pollfd fds[MAX_FDS];
int nfds = 0;
int i, ready;
/* Add listen socket */
fds[0].fd = listen_fd;
fds[0].events = POLLIN;
nfds = 1;
for (;;) {
ready = poll(fds, nfds, -1); /* block forever */
if (ready == -1) { perror("poll"); break; }
for (i = 0; i < nfds; i++) {
if (fds[i].revents == 0)
continue; /* nothing on this fd */
if (fds[i].revents & POLLIN) {
if (fds[i].fd == listen_fd) {
/* New incoming connection */
int conn_fd = accept(listen_fd, NULL, NULL);
if (nfds < MAX_FDS) {
fds[nfds].fd = conn_fd;
fds[nfds].events = POLLIN;
nfds++;
}
} else {
/* Data from existing client */
char buf[256];
int n = read(fds[i].fd, buf, sizeof(buf));
if (n == 0) {
/* Client disconnected */
close(fds[i].fd);
fds[i].fd = -1; /* mark as unused */
} else {
/* process data */
}
}
}
if (fds[i].revents & POLLHUP) {
printf("Hangup on fd %d\n", fds[i].fd);
close(fds[i].fd);
fds[i].fd = -1;
}
}
}
}
Pattern 2: Setting fd = -1 to skip a slot
/* poll() ignores entries where fd is set to -1 */
/* This is the clean way to remove an fd from monitoring */
pollFd[i].fd = -1; /* tells poll() to skip this slot */
/* Later when you add a new fd, reuse this slot */
for (j = 0; j < nfds; j++) {
if (pollFd[j].fd == -1) {
pollFd[j].fd = new_fd;
pollFd[j].events = POLLIN;
break;
}
}
Pattern 3: Timeout usage
int ret = poll(fds, nfds, 5000); /* wait up to 5 seconds */
if (ret == 0) {
printf("Timeout: no fd became ready in 5 seconds\n");
} else if (ret > 0) {
printf("%d fd(s) ready\n", ret);
} else {
perror("poll");
}
A: events is an input field โ you set it before calling poll() to tell the kernel what events you are interested in (e.g., POLLIN for readable). revents is an output field โ the kernel fills it after poll() returns to tell you what actually happened on that fd. You never set revents yourself; only the kernel does.
A: poll() returns 0 when the timeout expires without any fd becoming ready. It returns a positive number equal to the count of ready fds when at least one fd has an event, and -1 on error.
A: Set the fd field of that pollfd entry to -1. poll() automatically ignores entries where fd is -1. This avoids having to compact the array on every removal.
A: No. POLLHUP, POLLERR, and POLLNVAL are always checked by the kernel and reported in revents regardless of what you put in events. You should always check for these in your revents handling loop.
A:
-1โ block indefinitely until at least one fd is ready0โ return immediately (non-blocking check)>0โ wait up to that many milliseconds, then return 0 if still no fd is ready
A: Yes, poll() is commonly used in a server loop. Unlike select(), you do NOT need to reinitialize the fd set before each call. poll() uses separate events and revents fields โ events stays unchanged between calls, and the kernel just overwrites revents each time. With select(), the fd_set is modified on return, so you must rebuild it every iteration.
A: POLLNVAL is set in revents when the fd in that pollfd entry is not a valid open file descriptor at the time of the poll() call. This is different from POLLERR, which indicates an error on a valid fd. POLLNVAL means the fd itself is invalid (already closed or never opened).
A: #include <poll.h>
Continue Learning
Next: When is a File Descriptor Ready? (select & poll readiness rules)
