epoll底层实现过程,为什么epoll时间复杂度是O(1)

1.epoll底层数据结构:双向链表和红黑树

每个epollfd在内核中有一个对应的eventpoll结构对象.其中关键的成员是一个就绪队列readylist(eventpoll:rdllist),和一棵红黑树(eventpoll:rbr).就绪列表应是一种能够快速插入和删除的数据结构。双向链表就是这样一种数据结构,epoll使用双向链表来实现就绪队列。
就绪列表用来存储已经发生注册事件的文件描述符的集合,红黑树存储所监控的文件描述符的节点数据,为监听的所有文件描述符提供一个快速增删查的索引。

2.epoll的底层实现过程

首先看这三个函数:

1 int epoll_create(int size);
2 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
3 int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

首先epoll_create创建一个epoll文件描述符,底层同时创建一个红黑树,和一个就绪链表;红黑树存储所监控的文件描述符的节点数据,就绪链表存储就绪的文件描述符的节点数据;epoll_ctl将会添加新的描述符,首先判断是红黑树上是否有此文件描述符节点,如果有,则立即返回。如果没有, 则在树干上插入新的节点,并且告知内核注册回调函数。当接收到某个文件描述符过来数据时,会产生一个中断,linux系统在中断程序中将该节点插入到就绪链表里面。epoll_wait将会接收到消息,并且将数据拷贝到用户空间,清空链表。对于LT模式epoll_wait清空就绪链表之后会检查该文件描述符是哪一种模式,如果为LT模式,且必须该节点确实有事件未处理,那么就会把该节点重新放入到刚刚删除掉的且刚准备好的就绪链表,epoll_wait马上返回。ET模式不会检查,只会调用一次。
一个fd被添加到epoll中之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象.epitem被添加到eventpoll的红黑树中.红黑树的作用是使用者调用EPOLL_MOD的时候可以快速找到fd对应的epitem。
调用epoll_wait的时候,将readylist中的epitem出列,将触发的事件拷贝到用户空间.之后判断epitem是否需要重新添加回readylist.

3.epoll返回后的处理过程

调用epoll_wait的时候,将readylist中的epitem出列,将触发的事件拷贝到用户空间.之后判断epitem是否需要重新添加回readylist.

epitem重新添加到readylist必须满足下列条件:

  1. epitem上有用户关注的事件触发.

  2. epitem被设置为水平触发模式(如果一个epitem被设置为边界触发则这个epitem不会被重新添加到readylist中,在什么时候重新添加到readylist请继续往下看).

注意,如果epitem被设置为EPOLLONESHOT模式,则当这个epitem上的事件拷贝到用户空间之后,会将这个epitem上的关注事件清空(只是关注事件被清空,并没有从epoll中删除,要删除必须对那个描述符调用EPOLL_DEL),也就是说即使这个epitem上有触发事件,但是因为没有用户关注的事件所以不会被重新添加到readylist中.

epitem被添加到readylist中的各种情况(当一个epitem被添加到readylist如果有线程阻塞在epoll_wait中,那
个线程会被唤醒):
1)对一个fd调用EPOLL_ADD,如果这个fd上有用户关注的激活事件,则这个fd会被添加到readylist.
2)对一个fd调用EPOLL_MOD改变关注的事件,如果新增加了一个关注事件且对应的fd上有相应的事件激活,
则这个fd会被添加到readylist.
3)当一个fd上有事件触发时(例如一个socket上有外来的数据)会调用ep_poll_callback,如果触发的事件是用户关注的事件,则这个fd会被添加到readylist中.

了解了epoll的执行过程之后,可以回答一个在使用边界触发时常见的疑问.在一个fd被设置为边界触发的情况下,调用read/write,如何正确的判断那个fd已经没有数据可读/不再可写.epoll文档中的建议是直到触发EAGAIN错误.而实际上只要你请求字节数小于read/write的返回值就可以确定那个fd上已经没有数据可读/不再可写.
最后用一个epollfd监听另一个epollfd也是合法的,epoll通过调用eventpoll::ep_eventpoll_poll来判断一个epollfd上是否有触发的事件(只能是读事件).

4.epoll总结

epoll不会让每个 socket的等待队列都添加进程A引用,而是在等待队列,添eventPoll对象的引用。
当socket就绪时,中断程序会操作eventPoll,在eventPoll中的就绪列表(rdlist),添加scoket引用。
这样的话,进程A只需要不断循环遍历rdlist,从而获取就绪的socket。
从代码来看每次执行到epoll_wait,其实都是去遍历 rdlist。
如果rdlist为空,那么就阻塞进程。
当有socket处于就绪状态,也是发中断信号,再调用对应的中断程序。此时中断程序,会把socket加到rdlist,然后唤醒进程。进程再去遍历rdlist,获取到就绪socket。
总之: poll是翻译轮询的意思,我们可以看到poll和epoll都有轮询的过程。
不同点在于:
poll轮询的是所有的socket。
而epoll只轮询就绪的socket。
所有,poll和select的时间复杂度为O(n):每个监听的文件描述符都遍历一遍。
epoll的时间复杂度为O(1):只遍历就绪的socket,在内核中断程序中添加就绪文件描述符到就绪列表。

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值