介绍

本篇为 Linux I/O 事件通知机制系列第二篇,介绍 poll。 其他两篇为:

Table of Contents

poll, ppoll - wait for some event on a file descriptor

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <poll.h>

int ppoll(struct pollfd *fds, nfds_t nfds,
        const struct timespec *timeout_ts, const sigset_t *sigmask);

Description

poll() 执行与 select(2) 类似的任务: 等待一系列文件描述符中任意一个为 I/O 读写准备就绪。

提到的文件描述符集合使用 fds 参数指定, 是一个下面结构体的数组:

struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};

调用者应该用 nfds 来指定  fds 数组的大小。

成员 fd 包含了一个打开的文件描述符。如果为负,则对应的 events 成员会被忽略,并且 revents 成员返回0。 (这就为在单次 poll() 调用中忽略一个文件描述符提供了一种简单方法:可以把 fd 成员设为负值。)

events 成员是个输入参数,使用位掩码指定了应用对文件描述符fd感兴趣的事件。如果这个成员为0,则fd的所有事件被忽略而且 revents 返回0

revents 成员是个输出参数,是由内核按照实际发生的事件填充的。由 revents 返回的位中可以包含任何由 events 指定的, 或者 POLLERRPOLLHUP 和 POLLNVAL 三者之一。 (这三位在 events 成员中是无意义的, 当对应的情况为真的时候 revents 成员中对应的位会被设置。)

If none of the events requested (and no error) has occurred for any of the file descriptors, then poll() blocks until one of the events occurs.

timeout 参数指定了 poll() 将会阻塞的最小毫秒值。 (这个区间大小会按照系统时钟粒度向上取整,另外内核调度延时意味着可能会超过阻塞间隔一点点。) 指定一个负值给 timeout 意味着不存在超时。给 timeout 取0值会使 poll() 即使没有文件描述符就绪也立即返回。

The bits that may be set/returned in events and revents are defined in «a href=”http://linux.die.net/include/poll.h” rel=”nofollow”>poll.h</a>>:

POLLIN
有数据要读
POLLPRI
有紧急数据等待读取(e.g., out-of-band data on TCP socket; pseudoterminal master in packet mode has seen state change in slave).
POLLOUT
现在写则不会阻塞
POLLRDHUP (since Linux 2.6.17)
Stream socket peer closed connection, or shut down writing half of connection. 必须定义 _GNU_SOURCE (在包含任何头文件之前) 特性宏来获得这个定义
POLLERR
Error condition (output only)
POLLHUP
Hang up (output only)
POLLNVAL
无效请求: fd 未打开 (output only)

When compiling with _XOPEN_SOURCE defined, one also has the following, which convey no further information beyond the bits listed above:

POLLRDNORM

等同于 POLLIN

POLLRDBAND
Priority band data can be read (generally unused on Linux)

POLLWRNORM

等同于 POLLOUT

POLLWRBAND
Priority data may be written

Linux 了解但不使用 POLLMSG

ppoll()

poll() 和 ppoll() 的关系类似于 select(2) 和 pselect(2) 的关系: 如同 pselect(2), ppoll() 允许一个应用安全的等待直到任意一个文件描述符变为就绪状态或者捕捉到一个信号。而不是 timeout 参数的精度不同,下面的 ppoll() 调用:
ready = ppoll(&fds, nfds, timeout_ts, &sigmask);

等同于原子执行下面的调用:

sigset_t origmask;
int timeout;

timeout = (timeout_ts == NULL) ? -1 :
           (timeout_ts.tv_sec * 1000 + timeout_ts.tv_nsec / 1000000);
 sigprocmask(SIG_SETMASK, &amp;sigmask, &amp;origmask);
 ready = poll(&amp;fds, nfds, timeout);
 sigprocmask(SIG_SETMASK, &amp;origmask, NULL);

之所以 ppoll() 是必须的的原因请参见 pselect(2) 的描述。如果 sigmask 参数指定为 NULL, 信号掩码操作就不会被执行 (这样的话 ppoll() 和 poll() 就只有 timeout 参数的精度不同了).

timeout_ts 参数指定了ppoll() 将会阻塞的时间上限。这个参数是一个指向下面结构的指针:

struct timespec {
    long    tv_sec;         /* seconds */
    long    tv_nsec;        /* nanoseconds */
};

如果 timeout_ts 参数设为 NULL, ppoll() 将会一直(

indefinitely
adv. 不确定地,无限期地;模糊地,不明确地

)阻塞。

返回值

成功时返回正值,代表具有非0 revents 成员的结构体的数目(换言之, 那些描述符有事件或者错误报告的)。 0表示超时,没有文件描述符就绪事件。错误的时候返回-1,且适当设置 errno。

Errors

EFAULT
作为参数的数组不在调用程序的地址空间中。
EINTR
在请求的事件发生之前有信号发生。参见 signal(7).
EINVAL
nfds 值超过了 RLIMIT_NOFILE 值。
ENOMEM
没有足够的内存分配给文件描述符表。

Conforming To

poll() conforms to POSIX.1-2001. ppoll() is Linux-specific.

Notes

有些实现中定义了非标准常亮 INFTIM 为-1,用作  poll() 的 timeout 参数值。glibc 中没有提供这个常量。

关于如果在另一个线程中关闭了 poll() 监视的文件描述符的情况,请参考 select(2)

Linux notes

Linux ppoll() 系统调用会修改 timeout_ts 参数。但是, glibc wrapper function 通过使用一个timeout的局部变量传递给系统调用而隐藏了这一行为。因此,glibc ppoll() function 不修改 timeout_ts 参数。

Bugs

请参考 select(2) BUGS 一节中有关假就绪通知的讨论。

实例程序

使用poll做简单服务器模型

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

void errpro(int condition, const char *errmsg) {
	if (condition) {
		perror(errmsg);
		exit(EXIT_FAILURE);
	}
}

#define PORT	8888U
#define BACKLOG	16U
#define BUFLEN	64U
#define MAXCON	128U

int main() {
	char buf[BUFLEN+1];
	struct sockaddr_in server_addr, client_addr;
	int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	errpro(-1 == listen_fd, "socket");
	memset(&amp;server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	errpro(-1 == bind(listen_fd, (struct sockaddr *)&amp;server_addr,
				sizeof(server_addr)), "bind");
	errpro(-1 == listen(listen_fd, BACKLOG), "listen");
	struct pollfd fds[MAXCON];
	fds[0].fd = listen_fd;
	fds[0].events = POLLIN;
	int nfds = 1;
	int i;
	for (i = 1; i < MAXCON; ++i) {
		fds[i].fd = -1;
	}
	int concnt = 0; // current connections count
	static int loop = 0;
	for (;;) {
		printf("loop %dn", ++loop);
		int ret = poll(fds, nfds, 10000);
		errpro(-1 == ret, "poll");
		if (0 == ret) {
			printf("timeoutn");
			break;
		}
		if (fds[0].revents &amp; POLLIN) {
			int sockfd = accept(listen_fd, NULL, NULL);
			errpro(-1 == sockfd, "accept");
			++concnt;
			for (i = 0; i < MAXCON; ++i) {
				if (fds[i].fd < 0) {
					fds[i].fd = sockfd;
					fds[i].events = POLLIN;
					printf("accept fds[%d] = %dn", i, sockfd);
					break;
				}
			}
			errpro(MAXCON == i, "too many connections");
			if (i+1 > nfds) {
				nfds = i+1;
			}
		}

		// read data
		for (i = 1; i < nfds; ++i) {
			if (fds[i].fd < 0) {
				printf("skippedn");
			} else if (fds[i].revents &amp; POLLIN) {
				int ret = recv(fds[i].fd, buf, BUFLEN, 0);
				errpro(-1 == ret, "recv");
				if (0 == ret) {
					printf("close fds[%d]n", i);
					close(fds[i].fd);
					fds[i].fd = -1;
				}
				buf[ret] = '';
				printf("message from %d:n%s", i, buf);
				while (BUFLEN == ret) {
					ret = recv(fds[i].fd, buf, BUFLEN, 0);
					errpro(-1 == ret, "recv");
				}
			} else if (fds[i].revents &amp; POLLERR) {
				printf("fds[%d] errorn", i);
				errpro(EXIT_FAILURE, "POLLERR");
			}
		} // end of read data
	}

	return 0;
}

References