epoll
目录
epoll的接口
epoll_create/epoll_create1
epoll_ctl
epoll_wait
epoll的原理
ET/LT模式
epoll的接口
epoll的目的是多个fd(io)事件的等待机制,达到事件派发的目的,就是当事件到达网卡的时候,每个 socket 的 struct sock
中有一个等待队列(如 sk->sk_sleep
)。当 socket 收到数据(TCP 数据到达网卡 → 内核协议栈 → socket 接收缓冲区),内核会唤醒该 socket 的等待队列。epoll 通过回调机制(ep_poll_callback
)被触发,将就绪事件通知给用户空间,完成就绪事件的通知机制。
epoll_create/epoll_create1
epoll_create1()
是 Linux 2.6.27 起新增的系统调用,用来创建一个新的 epoll 实例并返回其文件描述符。它与早期 epoll_create(int size)
的唯一区别就在于 多了一个 flags
参数,从而可以携带额外的创建选项。我们要使用epoll进行多路转接的提醒机制首先需要先创建一个epoll模型。
epoll_create(int size)
是 最早期的 epoll 实例创建接口,出现于 Linux 2.5.44,2.6 内核后正式稳定。
这个flag就是填写的如上两个宏,填写非0的那个更安全
这个size已经失去作用了,所以size随便填一个值都可以的,我们一般填256
epoll_ctl
epoll_ctl
是 epoll 三部曲(create → ctl → wait)中唯一用来增删改被监控 fd 的系统调用。
一句话:把 socket(或其他 fd)挂到 epoll 红黑树上,或从树上摘除。
epoll_ctl的作用就是向一个epoll模型中添加fd和关心的事件,告诉内核你要帮我关心哪些fd上的哪些事件(可读,可写)
我们通过op宏操作将后面的event挂到挂满event的红黑树上,然后在event里面的data设置提示是哪个fd,然后events是位图设置宏要关心哪个事件。在event里面设置fd是为了在 epoll_wait
返回后,快速知道是哪个 fd 就绪,避免再查表,在外独立传一次fd是为了告诉内核要关心哪个fd。这个两个fd的作用不一样的。
所以epoll_ctl的作用就是设置关心的fd及其关心事件。epoll_ctl
成功返回 0,失败返回 -1 并设置 errno
epoll_wait
epoll_wait
是 epoll 事件循环的“最后一公里”:让内核把已经就绪的文件描述符一次性交给你。
event是一个输出参数,就是内核告诉你历史上哪些fd上的哪些事件已经就绪了。说明之前epoll_ctl联入红黑树的关联fd节点,当fd准备就绪时其同时属于输出数组上的节点。
这个maxevents是你给 epoll_wait
的“输出缓冲区”长度,填多少完全取决于你能一次性“扛”多少事件,别搞溢出就行。如果就绪事件 > MAX
,这次只拿 MAX 个,剩下的下次 epoll_wait
再给你,其值必须大于0。
timeout同poll
epoll的原理
入上图,epoll作为内核的一种一个回调callback方式,当有事件通过网卡的时候,内核会回调存在进程的void* private data里面的epoll,来处理提醒关心这个io事件,这时进程创建一个epoll模型,然后基于这个epoll模型调用epoll_ctl方法将用户指定的event通过op宏方法联入/删除等等操作调入早已经生成的储存要关心的事件的红黑树,为什么用红黑树呢,因为增删查的效率一般情况下都比数组高,然后接着当有事件就绪的时候,调用epoll_wait将其联入输出数组中,将用户指定长度从0开始交付给用户。
细节1:对于epoll来说储存fd的红黑树相当于辅助数组。
细节2:fd节点是共同存在红黑树和就绪队列中的,不需要考虑迁移的问题
细节3:红黑树中的key值是fd的值
细节4:Linux 内核把“epoll 实例”实现成一个 特殊的匿名文件(anon-inode 文件),
而 fd 是用户空间访问任何内核文件对象的统一句柄——这就是epoll模型必须返回 fd 的根本原因Linux下一切皆文件
epoll为什么高效,因为用户只需要注册进去,os自己驱动,就绪之后也不需要轮询判断,自动传出就绪的事件了。
ET/LT模式
LT(默认):只要 fd 上的事件“仍然成立”,epoll_wait 就每次都会告诉你,所以这种情况可以随意的反复的根据epoll就绪信号反复读取,这个也是epoll模型就默认形式。
ET:只有 fd 上的事件“从无到有”变化的那一刻才通知一次,之后不管数据是否剩余,都不再通知,直到下一次“新数据到达”再次触发。这时就逼着我们去一次性反复的读取并且通过协议判断是否读完。
记住上面的三,四。
由于ET模式算fd上需要关心的事件,所以将fd的读/写事件设置成ET模式,需要在原本关心的事件上|EPOLLET。LT+非阻塞+循环读取=ET
LT作为基本情况的使用,ET是基于高吞吐量的io设计的。