内容简介:一直没搞明白 epoll 的机制,以前看不明白 epoll 资料就放弃了。最近重新看这些资料,感觉看明白了大部分。记一下,省的以后又糊涂了。以下内容都是各种资料的小结,以后翻阅省事一点。阻塞模式下,一个线程很难处理多个 I/O 流。比如一个线程要读两个 I/O 事件流,可能 read 第一个I/O 时,因为数据没有就绪,所以整个线程都阻塞了,而第二个 I/O 数据虽然已经就绪,却得不到处理。具体原因个人理解是,线程不知道哪个 I/O 事件已经就绪,只能一个个试。第二个原因是阻塞模式下,如果事件没有就绪,系统
一直没搞明白 epoll 的机制,以前看不明白 epoll 资料就放弃了。最近重新看这些资料,感觉看明白了大部分。记一下,省的以后又糊涂了。以下内容都是各种资料的小结,以后翻阅省事一点。
I/O 模型与 epoll
I/O 流
阻塞模式下,一个线程很难处理多个 I/O 流。比如一个线程要读两个 I/O 事件流,可能 read 第一个I/O 时,因为数据没有就绪,所以整个线程都阻塞了,而第二个 I/O 数据虽然已经就绪,却得不到处理。具体原因个人理解是,线程不知道哪个 I/O 事件已经就绪,只能一个个试。第二个原因是阻塞模式下,如果事件没有就绪,系统调用会阻塞,导致整个线程都阻塞。
非阻塞模式,线程可以通过忙轮询处理多个 I/O 流,同样因为无法知道哪个 I/O 流是否已经就绪,导致很多系统调用都是无效的,效率非常低下。
I/O 多路复用
如果有一个代理,帮助管理多个 I/O 流,当没有可用的 I/O,线程继续阻塞,I/O 就绪时,唤醒线程处理 I/O,效率会大大提高。在 Linux 平台上,select,poll,epoll 就是这个代理。它们之间具体的优缺点就不讲了,这里只讲 epoll 的机制。
I/O 相关的机制,可以参考知乎上面的讨论 I/O与epoll
epoll 基础
以下内容基本上来自 The method to epoll’s madness 和 manpage。
内核内部用数据结构来维护 epoll 的相关信息,epoll 的三个 API 分别操作这些数据结构。
epoll_create
epoll_create 在内核创建 epoll instance(图中下方褐色方块),返回指向这个 epoll instance 的 file descriptor。
epoll_ctl
epoll_ctl 可以让 file descriptor 注册到 epoll instance 中,这些 file descriptor 称为 epoll set(图中 INTEREST LIST)。当 epoll set 里面的 file descriptor 有 I/O 就绪情况下,这些 file descriptor 会放到 READY LIST 里面(图中蓝色部分)。
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- epfd - epoll_create 返回的 epoll file descriptor
- fd - epoll instance 要监听的 file descriptor
- op - 对 file descriptor 的操作
- EPOLL_CTL_ADD - 注册 fd 到 epoll instance,fd 成为 epoll set 一员
- EPOLL_CTL_DEL - 把 fd 从 epoll set 删除,删除后进程无法得到 fd 任何事件的通知。如果 fd 注册到多个epoll instance 中,fd 关闭将导致 fd 从所有 epoll set 中删除
- EPOLL_CTL_MOD - 修改监听 fd 的事件
- event - 事件信息,具体如下所示
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
其中事件类型通过 uint32_t events 的 bit 表示,epoll_data 一般存放发生事件的 fd。
epoll_wait
线程调用 epoll_wait 会一直阻塞,直到 epoll set 里面有 fd 的 I/O 就绪。当 epoll_wait 返回后,线程遍历 evlist,处理 READY LIST 里面的就绪的 I/O 事件。
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
LT & ET
对于 write 来说,当内核缓冲区非满(包括空和有部分数据数据),LT 模式下 EPOLLOUT 会一直触发,当缓冲区从满到非满,ET 模式下 EPOLLOUT 才会触发。对于 read 来说,当缓冲区非空(包括满和有部分数据),LT 模式下 EPOLLIN 会一直触发,当缓冲区从空到非空,ET 模式下 EPOLLIN 才会触发。默认触发方式是 LT,如果是 ET,在 epoll_ctl 函数里面设置参数 event.events | EPOLLET 。
因为 LT & ET 触发方式不同,处理事件的逻辑也不同。先看 manpage 里面的一个例子
1. The file descriptor that represents the read side of a pipe (rfd) is registered on the epoll instance. 2. A pipe writer writes 2 kB of data on the write side of the pipe. 3. A call to epoll_wait(2) is done that will return rfd as a ready file descriptor. 4. The pipe reader reads 1 kB of data from rfd. 5. A call to epoll_wait(2) is done.
在 ET 模式下,这种情况可能导致进程一直阻塞。
- 假设 pipe 刚开始是空的,A端发送 2KB,然后等待B端的响应。
- 步骤2完成后,缓冲区从空变成非空,ET 会触发 EPOLLIN 事件
- 步骤3 epoll_wait 正常返回
- B开始读操作,但是只从管道读 1KB 数据
- 步骤5调用 epoll_wait 将一直阻塞。因为 ET 下,缓冲区从空变成非空,才会触发 EPOLLIN 事件,缓冲区从满变成非满,才会触发 EPOLLOUT 事件。而当前情况不满足任何触发条件,所以 epoll_wait 会一直阻塞。
如何解决呢,一个办法就是步骤4一直读,直到数据全部读完,但是在 blocking IO 下会出现另外一个问题,如果某次读完内核缓冲区后,再次调用 read 时,线程将会阻塞。所以需要设置 fd 是非阻塞的,当调用 read 或者 write 时,当返回 EAGIN/EWOULDBLOCK 后才去调用 epoll_wait。
在LT模式下,步骤4结束后,缓冲区还有数据,所以步骤5的 epoll_wait 不会阻塞,因为 EPOLLIN 事件不会丢失,会一直触发。但是也有一个问题,如果一次读的数据太少,将导致多次调用 epoll_wait,所以效率会有所下降。
为了减少 epoll_wait 调用次数,也可以采用ET的模式,使用非阻塞 IO,然后读写直到返回 EAGIN/EWOULDBLOCK。
LT/ET 在非阻塞处理有一点点不同,具体参考网络大神的总结 epoll LT/ET 深度剖析
reference
- 《Unix环境高级编程》
- I/O与epoll
- epoll LT/ET 深度剖析
- The method to epoll’s madness
以上所述就是小编给大家介绍的《epoll的那些事》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Adobe Dreamweaver CS5中文版经典教程
Adobe公司 / 陈宗斌 / 人民邮电 / 2011-1 / 45.00元
《Adobe Dreamweaver CS5中文版经典教程》由Adobe公司的专家编写,是AdobeDreamweavelCS5软件的官方指定培训教材。全书共分为17课,每一课先介绍重要的知识点,然后借助具体的示例进行讲解,步骤详细、重点明确,手把手教你如何进行实际操作。全书是一个有机的整体,它涵盖了Dreamweavercs5的基础知识、HTML基础、CSS基础、创建页面布局、使用层叠样式表、使......一起来看看 《Adobe Dreamweaver CS5中文版经典教程》 这本书的介绍吧!