poll() System Call Alternative I/O Models

 

poll() System Call
Chapter 63 – Alternative I/O Models | Linux Programming Interface
Topic
poll() Basics
Level
Intermediate
Part
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.

select() vs poll() — How you describe file descriptors

select() — Three bit-sets
read_fds
write_fds
except_fds
Each set is a bitmask. You set bit N to watch fd N. Max fd is limited (FD_SETSIZE = 1024).

poll() — Array of structures
fd events revents
3 POLLIN POLLIN
7 POLLOUT 0
12 POLLIN POLLERR
No fd limit. You can add/remove fds by editing the array — no need to rebuild sets.

Key Terms in This Tutorial

poll() struct pollfd events revents POLLIN POLLOUT POLLHUP POLLERR POLLPRI POLLRDHUP nfds_t I/O multiplexing

The poll() Function Signature

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)

The struct pollfd — How You Tell poll() What to Watch

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) */
};

Memory layout of struct pollfd
fd
int (4 bytes)
e.g. 5
events
short (2 bytes)
POLLIN | POLLOUT
revents
short (2 bytes)
set by kernel
← You write this before poll()                                  Kernel writes this after poll() →

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.

poll() Event Flags — The Complete List

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.
Practical Rule: For most Linux programs you only need these six flags: POLLIN, POLLOUT, POLLPRI, POLLRDHUP, POLLHUP, POLLERR. The rest are either synonyms or STREAMS-specific (Linux doesn’t have STREAMS).

Tricks: Temporarily Disabling a File Descriptor in the List

A great feature of poll() is that you can disable monitoring of one fd without rebuilding the whole array. Two ways to do this:

Trick 1: Set events = 0
Set the events field to 0. poll() will skip this fd and always return revents = 0.
/* Temporarily stop watching fd 5 */
pollFd[2].events = 0;
Trick 2: Negate the fd value
Store the negative of the fd in the 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;
Why this matters: With select(), if you want to remove an fd from monitoring you have to call FD_CLR() and recalculate the max fd. With poll() you just flip a field in your array — much simpler.

Feature Test Macros — Getting All poll() Constants

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>
POLLRDHUP is very useful for server code. Without it, when a client closes the TCP connection, you only know about it when you try to read and get 0 bytes. With POLLRDHUP, poll() notifies you immediately that the peer shut down — you can close the socket right away and not waste a read() call.

Basic Example — Watching stdin and a Pipe with poll()

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;
}
Pattern to remember:

  1. Fill fds[i].fd and fds[i].events for each fd you want to watch.
  2. Call poll(fds, nfds, timeout).
  3. Loop through the array. If fds[i].revents != 0, check the bits and handle the event.
  4. Always check for POLLERR and POLLHUP even if you didn’t request them.

Interview Questions — poll() Basics
Q1. What is the difference between poll() and select()?

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.

Q2. What does the revents field tell you? Can you set it before calling poll()?

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.

Q3. Why are POLLERR and POLLHUP not valid in the events field?

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.

Q4. How do you temporarily stop monitoring one fd in the poll array without removing it?

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().

Q5. What is POLLRDHUP and why is it useful?

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.

Q6. What feature test macro do you need to use POLLRDHUP?

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.

Next: Timeout & Example → All Tutorials

Leave a Reply

Your email address will not be published. Required fields are marked *