I/O 多路复用之poll

概述

poll() 系统调用是System V 的I/O多路复用的解决方案。它解决了一些 select() 的不足,不过 select() 还是被频繁的使用(还是出于习惯或可移植性的考虑)。

Poll()

       #include <poll.h>

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

select() 使用了基于文件描述符的三位掩码的解决方案,其效率不高;和它不同,poll() 使用了有 ndfs 个 pollfd 结构体构成的数组,fds 指针指向该数组。pollfd 结构体定义如下:


       #include <poll.h>

       struct pollfd {

              int fd;                  /* file descriptor */

              short events;       /* requested events to watch */

              short revents;      /* returned events witnessed */

        };


每个 pollfd 结构体指定一个被监视的文件描述符。可以给 poll() 传递多个 pollfd 结构体,使它能够监视多个文件描述符。每个结构体的 events 变量是要监视的文件描述符的事件的位掩码。用户可以设置该变量。 revents 变量是该文件描述符的结果事件的掩码。内核在返回时设置 revents 变量。events 变量中请求的所有事件都可能在 revents 变量中返回。以下是合法的 events 变量:


POLLIN                             有数据可读

POLLRDNORM                  有普通数据可读

POLLRDBAND                   有优先数据可读

POLLPRI                           有高优先级数据可读

POLLOUT                          写操作不会阻塞

POLLWRNORM                 写普通数据不会阻塞

POLLBAND                       写优先数据不会阻塞

POLLMSG                         有SIGPOLL 消息可用


此外,revents 变量可能会返回如下事件:

POLLER                             给定的文件描述符出现错误

POLLHUP                          给定的文件描述符有挂起事件

POLLNVAL                        给定的文件描述符非法


对于 events 变量,这些事件没有意义,events 参数不要传递这些变量,它们会在 revents 变量中返回。 poll() 和 select() 不同,不需要显示请求异常报告。


POLLIN | POLLPRI 等价于 select() 的读事件,而 POLLOUT | POLLWRBAND 等价于 select() 的写事件。POLLIN 等价于 POLLRDNORM | POLLRDBAND, 而 POLLOUT 等价于 POLLWRNORM。


举个例子,要监视某个文件描述符是否可读写,需要把 events 设置成 POLLIN | POLLOUT 。返回时,会检查 revents 中是否有相应的标志位。如果设置了 POLLIN, 文件描述符可非阻塞读;如果设置了POLLOUT,文件描述符可非阻塞写。标志位并不是相互排斥的;可以同时设置,表示可以在该文件描述符上读写,而且都不会阻塞。


timeout 参数指定要等待的时间长度,单位是毫秒,不论是否有 I/O 就绪, poll() 调用都会返回。如果 timeout 值为负数,表示永远等待; timeout 为 0 表示 poll() 调用立即返回,并给出所有 I/O 未就绪的文件描述符列表,不会等待更多事件。在这种情况下,poll() 调用如同其名,轮询一次后立即返回。


返回值和错误码

poll() 调用成功时,返回 revents 变量不为 0 的所有文件描述符个数;如果没有任何事件发生且未超时时,返回 0。失败时,返回 -1,并相应设置errno值如下:

EBADF          一个或多个结构体中存在非法文件描述符
EFAULT       fds 指针指向的地址超出了进程地址空间
EINTR         在请求事件发生前收到了一个信号,可以重新发起调用
EINVAL       nfds 参数超出了RLIMIT_NOFILE值
ENOMEN     可用内存不足,无法完成请求

poll() 示例

poll()示例程序,它同时检测 stdin 读和 stdout 写是否会发生阻塞:
#include <stdio.h>
#include <unistd.h>
#include <poll.h>

#define TIMEOUT 5             /* poll timeout, in seconds */

int main(void)
{
	struct pollfd fds[2];
	int    ret;

	/* watch stdin for input */
	fds[0].fd = STDIN_FILENO;
	fds[0].events = POLLIN;

	/* watch stdout for ability to write (almost always true) */
	fds[1].fd = STDOUT_FILENO;
	fds[1].events = POLLOUT;

	/* All set, block! */
	ret = poll(fds, 2, TIMEOUT * 1000);
	if (ret == -1) {
		perror("poll");
		return 1;
	}

	if (!ret) {
		printf("%d seconds elapsed.\n", TIMEOUT);
		return 0;
	}

	if (fds[0].revents & POLLIN) {
		printf("stdin is readable.\n");
	}

	if (fds[1].revents & POLLOUT) {
		printf("stdout is writable.\n");
	}

	return 0;
}

运行后,生成结果如下(和期望一致):
         $ ./poll
         stdout is writable.
再次运行,这次把一个文件重定向到标准输入,可以看到两个事件:
        $ ./poll < c++.txt 
        stdin is readable.
        stdout is writable.
如果在实际应用中使用 poll(), 不需要在每次调用时都重新构建 pollfd 结构体。该结构体可能会被重复传递多次,内核会在必要的时候把 revents 清空。

ppoll() 

类似于 pselect() 和select(),Linux 也为 poll() 提供了 ppoll()。然而,和 pselect() 不同,ppoll() 是Linux特有的调用:

        #define _GNU_SOURCE
        #include <poll.h>

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

类似于 pselect(), timeout 参数指定的超时时间是秒和纳秒,sigmask 参数提供了一组等待处理的信号。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值