I/O复用–select、poll、epoll
select, poll, epoll 这三组系统调用用以实现I/O复用。
共同点
- 可以同时监听多个文件描述符。
- 都支持等待时间 timeout,直到有一个或者多个文件描述符上有时间发生时返回;等于-1,永远阻塞,直到有事件发生;等于0,不阻塞,立即返回。
- 返回值就是就绪的文件描述符的数量。
不同点
这三者的区别主要是具体实现、工作模式、支持的最大文件描述符数目限制以及事件集四个方面的不同:
1. 具体实现:
select 和 poll 采用 轮询 方式。每次调用都要扫描整个已经注册的文件描述符集,将就绪的文件描述符返回给调用者。时间复杂度为 O(n)。
epoll (准确地说,是epoll_wait()的实现) 采用的是 回调 方式。当内核检测到就绪的文件描述符时,将会触发回调函数,再有回调函数将该文件描述符上对应的事件插入到内核就绪事件队列,最后,内核再将就绪事件队列中的内容拷贝到用户空间。所以 epoll 无需检测整个文件描述符集。时间复杂度为 O(1)。
2. 工作模式
select 和 poll 只能工作在相对低效的 LT 模式;
epoll 可以工作在高效的 ET 模式。
什么是 LT、ET 模式?
epoll 对文件描述符的操作有两种模式:LT(Level Trigger,电平触发)模式和ET (Edge Trigger,边缘触发)模式。LT模式是默认地工作模式。
对于文件描述符fd,如果fd采用 LT模式,当epoll_wait()检测到该文件描述符上有事件发生时,程序不会立即处理该事件。当程序下一次调用epoll_wait()时,epoll_wait()会再次响应通告该事件,直到该事件被处理。
如果fd采用 ET模式,当epoll_wait()检测到该文件描述符上有事件发生时,程序会立即处理该事件。当程序下一次调用epoll_wait()时,epoll_wait()不会再次响应通告该事件。
那么很容易得出,ET 模式的确效率要高,因为它很大程度上降低了同一个事件被重复触发的次数。
3. 支持的最大文件描述符数目限制
poll 和 epoll_wai 支持系统所能打开的最大最大文件描述符数目(cat /proc/sys/fs/filemax);
select 允许监听的最大文件描述符数目通常是有限制的,虽然用户可以修改,但是系统不保证结果。
4. 事件集
select, poll, epoll 都是通过结构体变量告诉内核监听哪些文件描述符上的哪些事件,并使用结构体类型的参数返回内核处理结果。具体不同:
select 参数类型 fd_set 没有把事件和文件描述符绑定,只提供了一个文件描述符集合, 需要向 select 提供可读、可写、异常等事件。内核对 fd_set 集合同时修改,因此程序要对其进行专门的重置。
poll 的参数类型 pollfd 将事件和文件描述符绑定。所有事件统一处理,内核每次修改 revents 成员,对于 events 不变,因此也就无需重置了。
epoll 则是采用了在内核中维护一个事件表,并且提供单独的系统调用控制事件的添加、删除、修改。epoll_wait 直接从内核中取得用户注册事件,不需要反复从用户空间获取事件,效率更高。
总结
通过比较,似乎 epoll 就是完胜,但是不然。
epoll 只是适用于连接数目多,但是活动连接少的情况,如果活动连接太多,回调函数的触发就会非常频繁,效率还不如 poll 和 select。