IO 多路复用模式
IO 多路复用是通过一种机制, 让单个进程可以监视多个文件描述符, 一旦某个描述符就绪, 就能够通知程序进行相应的读写操作。
select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
与多进程和多线程技术相比, IO多路复用技术的最大优势是系统开销小, 系统不必创建进行/线程, 也不必维护这些进程/线程, 从而大大减小了系统的开销。
1. Select
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
该函数的作用是:通过一个文件描述符集合, 来达到同时监视多个文件描述符是否发生了读、写、异常这三类 IO 事件。
调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有exception),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。
函数中的fd_set 类型的参数 readset, writeset, exceptset
, 通过这三个参数指定我们要让内核读、写、异常的文件描述符集合。
缺点
- 每次调用 select, 都要把 fd_set 从用户态拷贝到内核态。
- 每次调用select 都需要在内核遍历传进来的所有 fd_set, 用于检查文件描述符的就绪状态
- select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历
2. Poll
poll 的实现和 select 非常相似, 只是描述 fd 集合的方式不同, poll 使用链表存储文件描述符, 因此没有最大连接数限制。
poll 和 select 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
3. epoll
epoll 通过三个函数完成整个功能。分别是 epoll_create, epoll_ctl, epoll_wait。
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 通过 epoll_create 创建了一个句柄
- 通过 epoll_ctl 向内核添加、修改或删除要监控的文件描述符
- 通过 epoll_wait 发起类似 select 的调用
3.1 原理详解
当某一进程调用 epoll_create 方法时, 内核空间会创建一个 evnetpoll 结构体,结构体核心变量如下所示:
struct eventpoll {
....
//红黑树的根节点, 存储着所有添加到 epoll 中的需要监控的事件
struct rb_root rbr;
//双链表中存放着将要通过 epoll_wait 返回给用户的满足条件的事件
struct list_head rdlist;
....
}
- 通过 epoll_ctl 方法将新添加的监控事件 event 加入到 红黑树中。 还会给内核一个回调函数, 告诉内核,如果这个文件描述符就绪了, 就把它放到准备就绪 rdlist 链表中。
- 当调用 epoll_wait 方法检查是否有事件发生时, 只需要检查 eventpoll 对象中 rdlist 中是否有元素即可。
3.2 优点:
- epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
- epoll 使用事件的就绪通知方式,通过epoll_ctl注册 fd ,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait 便可以收到通知。
- 当调用 epoll_wait 方法检查是否有事件发生时, 只需要检查就绪事件链表即可。
3.3 工作模式
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式(水平触发):当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件
。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式(边缘触发):当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件
。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
4. 总结
select | poll | epoll | |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
底层实现 | 数组 | 链表 | 红黑树 |
IO 效率 | 线性遍历, 时间复杂度为O(n) | 线性遍历, 时间复杂度为O(n) | 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1) |
最大连接数 | 1024(x86)或2048(x64) | 无上限 | 无上限 |
fd拷贝 | 每次调用select,都需要把fd集合从用户态拷贝到内核态 | 每次调用select,都需要把fd集合从用户态拷贝到内核态 | 调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝 |
5. 参考链接
- https://juejin.cn/post/6927076356136108046#heading-0
- https://mp.weixin.qq.com/s/CMWlDywI1zbgJSoeGTBmuw
- https://www.jianshu.com/p/397449cadc9a
- https://www.cnblogs.com/liliuguang/p/15040258.html
- https://juejin.cn/post/6850037276085321736