前面写过 select 和 poll 的文章,在 Linux 下有一种更高效的 I/O 多路机制,那就是 epoll。epoll的高效和它的结构有关系(本文默认读者已经了解 select),首先 epoll 会把 select 的过程分成3个部分, epoll_create(), epoll_ctl() 和 epoll_wait。在 epoll_create 的过程中,会创建一个 eventpoll 结构体,这个结构体的部分定义如下

struct eventpoll{
    ...  
    struct rb_root rbr; //红黑树,存储了所有添加到 epoll 中的事件
    struct list_head rdllist; // 双向链表保存通过 epoll_wait 返回给用户的满足条件的事件
    ...
}

这里的红黑树 rbr 存储了所有已经添加到 epoll 中的事件,如果使用 epoll_ctl 进行事件操作的时候,会在红黑树中进行查找,这个效率是很高的(红黑树是一颗自平衡二叉搜索树,查找事件 O(lgn))。双向链表 rdllist 则保存将要返回给用户的满足条件的事件。
然后所有添加的事件都会和设备(如网卡)驱动程序建立回调关系,一旦相应事件发生就回调用这里的回调函数,然后回调函数就会把事件添加到上面的双向链表中去。因为最后返回时只需要查看链表是否有数据,这个就比 select 要高效很多
然后最后是 epoll_wait.调用这个函数的时候,我们会等待一段时间(这段时间是由自己设置),这段时间过去之后,epoll 会自动返回双向链表中的事件,如果双向链表不为空,就把这里的事件复制到用户态内存中,同时将事件数量返回给用户。
epoll 的基本功能差不多就这些,当然还有一个叫做触发模式的,epoll 分为两种触发模式{水平触发,边缘触发},区别就是水平触发的话,如果某一次没有处理,那么下一次还会返回给用户,但是边缘触发的话,只在事件发生时返回给用户一次,如果用户忽略掉了,那么后面就不会再返回给用户了。
至于为什么 epoll 会比 select 要好用,大致有如下几个原因
1. select 用的是 FD_SET进行操作,而 FD_SET 有上限限制(可以通过自己改源码进行修改),但是 epoll 没有这个限制
2. select 会对所有的感兴趣的 fd 一个个去检查是否就绪,这样就行成了一个轮询,这个是比较慢的,而 epoll 则通过设置回调函数,在有事件发生的时候,将事件添加到双向链表中,最后只需要检查双向链表是否为空即可,这个也是很高效的。
3. 还有 epoll_ctl 对事件进行操作时,会在红黑树中先查找是否存在,查找的过程也是很高效的。
这样 epoll 就可以轻松处理百万级的并发处理了。
epoll 的东西大致就这么一些,至于实际应用,这个需要看实际的情况了,这个没有经验,不敢妄谈。

Comments