经过几天的学习,我了解了Linux条件下几个实现I/O复用的系统调用关键函数,分别是select、poll与epoll,本次就学习到的关于几个函数的知识点总结一下。
这三个函数调用其实实现的功能都是一样的,但是它们的使用又是有一些区别的。
1、select
其调用原型如下所示:
int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval *timeout)
nfds参数表示被监听的文件描述符的个数。 readfds、writefds、exceptfds分别表示三个指针,主要是指向可读、可写、异常等事件所对应的文件描述符的集合。 timout参数主要是设置select函数的超时时间。
用途:使用轮询的方式在一段时间内,监听特定的文件描述符上的可读、可写与异常事件。
select函数调用成功时返回就绪文件描述符的个数。若在超时时间内没有文件描述符就绪,则返回0。函数调用失败时返回-1同时设置errno。若在等待期间收到信号,则立即返回-1,并设置errno为EINTR。
select基本上在所有平台都能够使用,可跨平台使用是它的一个巨大的优点。但是它在单进程中可监视的文件描述符数量有限制,一般为1024(LInux条件下),但是可以通过宏定义更改限制。
2、poll
其调用原型如下所示:
int poll(struct pollfd *fds , nfds_t nfds , int timeout);
struct pollfd
{
int fd; //文件描述符
short events; //注册的事件
short revents; //实际发生的事件,由内核填充
};
fds参数是一个数组,指定我们所感兴趣的文件描述符上所发生的事件。 nfds参数表示监听事件集合的大小。 timeout参数表示poll函数的超时时间。
poll的系统调用与select的用法比较相似,主要也是使用轮询的方式在一定的时间段内轮询一定数量的文件描述符,测试其是否有已经就绪的文件描述符。
poll函数的返回值含义与select相同。
3、epoll
其调用原型如下:
int epoll_ctl(int epfd , int op , int fd , struct epoll_event *event);
struct epoll_event
{
uint32_t events; //epoll事件描述符类型
epoll_data_t data; //存储用户数据
};
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint32_t u64;
}epoll_data_t;
op参数表示文件的操作类型。 fd参数表示所要操作的文件描述符。 event参数表示指定的事件。
epoll的实现和使用与前两个系统调用有很大的区别。它把用户感兴趣的事件放入一个内核文件表里,不需要向上面两个函数那样每一次调用都须重传事件集。并且是使用一组函数来处理事件,而不是单个函数。
epoll函数的系统调用主要是依据epoll_wait函数,其原型如下:
int epoll_wait(int epfd , struct epoll_event *events , int maxevents , int timeout);
maxevents参数表示最大可监听事件个数。 timeout参数的意义同上。 epoll_wait函数若检测到事件,就将就绪时间从内核事件表(由epfd参数指定)中复制到events参数所指向的数组中,此数组只用于输出epoll_wait函数检测到的就绪事件。
epoll_wait函数在一段超时时间内等待一组文件描述符上的事件,若成功则返回文件描述符的个数,失败则返回-1ing设置errno。
epoll函数中对文件的操作有两种模式,分别是LT模式与ET模式。LT模式为默认工作模式,这种模式下epoll相当于一个效率较高的poll。 ET模式是epoll的高效工作模式,ET模式很大程度上降低了同一个epoll事件被重复触发的次数,所以效率比ET模式高。
epoll中的LT模式与ET模式的区别
LT模式 事件就绪后,若应用程序没有处理或者没有处理完,i/o函数会一直提醒,直到处理完成 阻塞和非阻塞都可以
ET模式 事件就绪后,i/o函数只提醒一次,所以应用程序必须一次将事件处理完成 只能非阻塞
4、总结
select、poll产生的问题:
(1)select、poll每一次循环调用时,都需要从循环空间拷贝所有描述符和事件到内核空间
(2)在内核上以轮询的方式检查描述符上的时候有时间产生,时间复杂度O(n)
(3)select、poll返回后,需要遍历所有的描述符才能找到所有就绪的,时间复杂度 O(n)
epoll解决了这三个问题
(1)不是每次都拷贝数据到内核中
(2)注册回调函数的方式实现 ,时间复杂度O(1)
(3)只返回就绪的描述符,时间复杂度O(1)
当文件描述符数目较多时,可以使用epoll
系统调用 | select | poll | epoll |
事件集合 | 用户通过三个参数传入事件,内核通过在线对参数进行修改来反馈就绪事件。但是用户每一次调用该函数时都须重置三个参数。 | 该函数设置为统一处理事件类型,所以只需一个事件参数。用户通过poll.events传入事件,内核通过修改poll.events来反馈就绪的事件。 | 内核通过一个事件表管理所有事件。每一次调用epoll_wait函数时,无须再传入事件。epoll_wait函数函数中的events会反馈就绪的事件 |
搜索文件描述符所需的时间复杂度 | O(n) | O(n) | O(1) |
工作模式 | LT模式 | LT模式 | LT模式与ET模式 |
内核实现 | 轮询方式 | 轮询方式 | 回调方式 |
算法时间复杂度 | O(n) | O(n) | O(1) |