poll()函数介绍
系统调用 poll()与 select()函数很相似,但函数接口有所不同。在 select()函数中,我们提供三个 fd_set 集合,在每个集合中添加我们关心的文件描述符;而在 poll()函数中,则需要构造一个 struct pollfd 类型的数组,每个数组元素指定一个文件描述符以及我们对该文件描述符所关心的条件(数据可读、可写或异常情况)。poll()函数原型如下所示:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
函数参数含义如下:
fds:指向一个 struct pollfd 类型的数组,数组中的每个元素都会指定一个文件描述符以及我们对该文件描述符所关心的条件,稍后介绍 struct pollfd 结构体类型。
nfds:参数 nfds 指定了 fds 数组中的元素个数,数据类型 nfds_t 实际为无符号整形。
timeout:该参数与 select()函数的 timeout 参数相似,用于决定 poll()函数的阻塞行为,具体用法如下:
- 如果 timeout 等于-1,则 poll()会一直阻塞(与 select()函数的 timeout 等于 NULL 相同),直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号时返回。
- 如果 timeout 等于 0,poll()不会阻塞,只是执行一次检查看看哪个文件描述符处于就绪态。
- 如果 timeout 大于 0,则表示设置 poll()函数阻塞时间的上限值,意味着 poll()函数最多阻塞 timeout 毫秒,直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号为止。
struct pollfd
结构体 struct pollfd 结构体如下所示:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
fd 是一个文件描述符,struct pollfd 结构体中的 events 和 revents 都是位掩码,调用者初始化 events 来指定需要为文件描述符 fd 做检查的事件。当 poll()函数返回时,revents 变量由 poll()函数内部进行设置,用于说明文件描述符 fd 发生了哪些事件(注意,poll()没有更改 events 变量),我们可以对 revents 进行检查,判断文件描述符 fd 发生了什么事件。
应将每个数组元素的 events 成员设置为下表中所示的一个或几个标志,多个标志通过位或运算符 ( | )组合起来,通过这些值告诉内核我们关心的是该文件描述符的哪些事件。同样,返回时,revents 变量由内核设置为表中所示的一个或几个标志。
表中第一组标志(POLLIN、POLLRDNORM、POLLRDBAND、POLLPRI、POLLRDHUP)与数据可读相关;第二组标志(POLLOUT、POLLWRNORM、POLLWRBAND)与可写数据相关;而第三组标志(POLLERR、POLLHUP、POLLNVAL)是设定在 revents 变量中用来返回有关文件描述符的附加信息, 如果在 events 变量中指定了这三个标志,则会被忽略。
如果我们对某个文件描述符上的事件不感兴趣,则可将 events 变量设置为 0;另外,将 fd 变量设置为文件描述符的负值(取文件描述符 fd 的相反数-fd),将导致对应的 events 变量被 poll()忽略,并且 revents 变量将总是返回 0,这两种方法都可用来关闭对某个文件描述符的检查。
在实际应用编程中,一般用的最多的还是 POLLIN 和 POLLOUT。对于其它标志这里不再进行介绍了。
poll()函数返回值
poll()函数返回值含义与 select()函数的返回值是一样的,有如下几种情况:
- 返回-1 表示有错误发生,并且会设置 errno。
- 返回 0 表示该调用在任意一个文件描述符成为就绪态之前就超时了。
- 返回一个正整数表示有一个或多个文件描述符处于就绪态了,返回值表示 fds 数组中返回的 revents 变量不为 0 的 struct pollfd 对象的数量。
下面代码演示了使用 poll()函数来实现 I/O 多路复用操作,同时读取键盘和鼠标。其实就是将上次select()代码进行了修改,使用 poll 替换 select。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#define MOUSE "/dev/input/event3"
int main(void){
char buf[100];
int fd, ret = 0, flag;
int loops = 5;
struct pollfd fds[2];
/* 打开鼠标设备文件 */
fd = open(MOUSE, O_RDONLY | O_NONBLOCK);
if (-1 == fd) {
perror("open error");
exit(-1);
}
/* 将键盘设置为非阻塞方式 */
flag = fcntl(0, F_GETFL); //先获取原来的 flag
flag |= O_NONBLOCK; //将 O_NONBLOCK 标准添加到 flag
fcntl(0, F_SETFL, flag); //重新设置 flag
/* 同时读取键盘和鼠标 */
fds[0].fd=0;
fds[0].events = POLLIN;
fds[0].revents = 0;
fds[1].fd = fd;
fds[1].events = POLLIN;
fds[1].revents = 0;
while (loops--) {
ret = poll(fds, 2, -1);
if (0 > ret) {
perror("poll error");
goto out;
}
else if (0 == ret) {
fprintf(stderr, "poll timeout.\n");
continue;
}
/* 检查键盘是否为就绪态 */
if(fds[0].revents&POLLIN){
ret = read(0, buf, sizeof(buf));
if (0 < ret)
printf("键盘: 成功读取<%d>个字节数据\n", ret);
}
/* 检查鼠标是否为就绪态 */
if(fds[1].revents&POLLIN) {
ret = read(fd, buf, sizeof(buf));
if (0 < ret)
printf("鼠标: 成功读取<%d>个字节数据\n", ret);
}
}
out:
/* 关闭文件 */
close(fd);
exit(ret);
}
struct pollfd 结构体的 events 变量和 revents 变量都是位掩码,所以可以使用"revents & POLLIN"按位与的方式来检查是否发生了相应的 POLLIN 事件,判断鼠标或键盘数据是否可读。测试结果:
总结
在使用 select()或 poll()时需要注意一个问题,当监测到某一个或多个文件描述符成为就绪态(可以读或写)时,需要执行相应的 I/O 操作,以清除该状态,否则该状态将会一直存在;调用 select()函数监测鼠标和键盘这两个文件描述符,当 select()返回时,通过 FD_ISSET()宏判断文件描述符上 是否可执行 I/O 操作;如果可以执行 I/O 操作时,应在应用程序中对该文件描述符执行 I/O 操作,以清除文件描述符的就绪态,如果不清除就绪态,那么该状态将会一直存在,那么下一次调用 select()时,文件描述符已经处于就绪态了,将直接返回。
同理对于 poll()函数来说亦是如此,当 poll()成功返回时,检查文件描述符是否称为就绪态,如果文件描述符上可执行 I/O 操作时,也需要对文件描述符执行 I/O 操作,以清除就绪状态。