来自《Linux高性能服务器编程》
一,poll系统调用和select系统调用类似,也是在一段时间内轮询看文件描述符上是否有时间就绪。
poll的原型如下:
#include<poll.h>
int poll( struct pollfd * fds,nfds_t nfds,int timeout);
fds是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读可写异常事件。pollfd结构体的定义如下:
struct pollfd
{ int fd; //文件描述符
short events; //注册的事件(每个位表示一个事件,与事件按位与,就能知道发生了哪个事件)
short reevents; //实际发生的事件,由内核填充(通知应用程序fd上实际发生了哪些事件)
};
poll支持的事件比较多,包括如下:
POLLIN | 数据可读(包括普通数据和优先数据) |
POLLRDNORM | 普通数据可读 |
POLLRDBAND | 优先级带外数据可读(Linux不支持) |
POLLPRI | 高级优先级数据可读,比如tcp带外数据 |
POLLOUT | 数据(包括普通数据和优先级数据)可写 |
POLLWRNORM | 普通数据可写 |
POLLWRBAND | 优先级带外数据可写 |
POLLRDHUP | TCP连接被对方关闭,或者对方关闭了写操作 |
POLLERR | 错误码 |
POLLHUP | 挂起,比如管道的写端被关闭后,该端描述符上将收到POLLHUP事件 |
POLNVAL |
文件描述符没有打开 |
nfds参数指定被监听事件集合fds的大小,类型nfds_t的定义如下:
typedef unsigned long int fnfds_n;
timeout指定超时时间,当timeout返回-1时,poll调用将永远阻塞,直到某个事件发生;当timeout返回为0时,poll调用将立即返回。
poll调用的返回值为就绪的文件描述符个数,返回0表示超时时间内没有文件描述符就绪,失败时返回-1.
二,epoll是Linux特有的I/O复用,他在实现上和select和poll有很大差异。它使用一组函数完成任务,而不是单个函数。
为什么要epoll:select和poll有以下几点不足
每轮循环都需要从用户空间往内核空间拷贝数据;内核轮询检测索引就绪事件;I/O返回后需要遍历描述符找到有事件就绪的描述符。epoll解决了以上三个问题。
epoll把用户关心的文件描述符上的事件放在内核的一个事件表中,,从而无需像select和poll那样每次调用都要重复传入文件描述符集或事件集。但是epoll需要一个额外的文件描述符,来唯一标识内核中的这个事件。这个文件描述符由epoll_create函数来创建:
#include<sys/epoll.h>
int epoll_create( int size);//由于内核事件表是一个树,所以这个size的大小无意义;
下面的函数来操作内核事件表:
#include<sys/epoll.h>
int epoll_ctl( int epfd, int op, int fd, struct epoll_event * event);
fd是要操作的文件描述符,op参数指定操作类型,操作类型有如下三种:
EPOLL_CTL_ADD:往事件表中注册fd上的事件;
EPOLL_CTL_MOD:修改fd上注册的事件;
EPOLL_CTL_DEL:删除fd上注册的时事件;
event参数指定事件,他是epoll_event结构指针类型,定义如下:
struct epoll_event
{
_uint32_t events; //epoll事件
epoll_data_t data; //用户数据
};
epoll支持的事件类型与poll支持的事件类型基本相同,表示epoll事件的类型的宏就是在poll事件的宏前面加上E;但是epoll有两个额外的时间类型:EPOLLET和EPOLLONESHOT;
epoll_ctl成功时返回0,失败时返回-1.
epll体统调用的主要接口是epoll_wait函数,它在一段超时时间内等待一组文件描述符上的事件。原型如下:
#include<sys/epoll.h>
int epoll_wait( int epfd, struct epoll_event * events, int maxevents, int timeout);
此函数成功返回就绪的文件描述符个数,失败时返回-1.
epfd指定内核事件表,maxevents参数指定最多监听的事件个数,必须大于零。events数组只用于输出epoll_wait检测到的就绪事件。
三,LT模式和ET模式
LT也叫电平触发模式,是默认的工作模式,在这种模式下epoll相当于一个效率较高的poll。当往epfd的内核事件表中注册一个EPOLLET事件时,epoll将以ET模式(边沿触发)来操作该文件描述符,ET模式是epoll的高效工作模式。select和poll都属于LT工作模式,epoll具有LT和ET两种模式。
简单来说,LT模式下,epoll_wait检测到文件描述符上面有事件就绪时,应用程序可以不立即处理该事件,这样,当应用程序下一次调用epoll_wait时,它还会向应用程序通知此事件,直到事件被处理为止。而ET模式下,epoll_wait将文件描述符上的就绪事件通知应用程序后,应用程序必须立即处理该事件,因为后续epoll_wait将不再通知。
需要注意的是,ET模式的文件描述符应该是非阻塞的,如果文件描述符没有设置非阻塞,那么读或写操作将会因为没有后续事件而一直处于阻塞状态。设置非阻塞用fcntl。
四,三组I/O函数的比较
系统调用 | select | poll | epoll |
事件集合 | 用户通过三个参数分别传入感兴趣的可读可写异常事件,内核通过对这些函数的在线修改来反馈其中的就绪事件,这使得用户每次调用select都需要重置这三个参数 | 统一处理所有事件类型,因此只需要传入一个事件集参数用户通过pollfd.events传入感兴趣的事件,内核通过修改pollfd.events反馈其中就绪的事件。 | 内核通过一个事件表直接管理用户感兴趣的所有事件,因此每次调用epoll_wait时不需要反复传入。epoll_wait的events参数仅用来反馈就绪的事件。 |
应用程序索引就绪文件描述符的时间复杂度 | O(n) | O(n) | O(1) |
最大支持文件描述符数 | 一般有最大值限制 | 65535 | 65535 |
工作模式 | LT | LT | 支持ET高效模式 |
内核实现和工作效率 | 轮询检测就绪事件,O(n) | 轮询检测就绪事件,O(n) | 回调检测就绪事件, O(1) |