epoll过人之处

来源:互联网 发布:tensorflow 编辑:程序博客网 时间:2024/06/14 13:54

select 和 epoll的任务

关键词:应用程序、 文件句柄、 用户态、 内核态、 监控者

要比较epoll 相较select 高效在什么地方,就需要比较二者做相同的事情的方法。
要完成对 I/O 流的复用需要完成如下几个事情:

  • 用户态怎么将文件句柄传递到内核态?

    • select:select创建3个文件描述符集,并将这些文件描述符拷贝到内核中,这里限制了文件句柄的最大数量为1024(注意是全部拷入—>第一次拷入);

    • epoll:首先执行epoll_create 在内核专属于epoll 的高速cache 区,并在该缓冲区建立红黑树和就绪链表,用户态对传入的文件句柄将被放到红黑树中(第一次拷入);

  • 用户态怎么判断 I/O 流可读可写?

    • select:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作和select无关;

    • epoll:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作与epoll无关;

  • 内核怎么通知监控者有I/O流可读可写?

    • select:内核在检测到文件句柄可读/可写时就产生中断通知监控者select,select被内核触发之后,就返回可读可写的文件句柄总数;

    • epoll:epoll_ctl执行add 动作时除了将文件句柄放到红黑树上之外,还向内核注册了该句柄的回调函数,内核在检测到某句柄可读可写时则调用回调函数, 回调函数将句柄放到就绪链表;

  • 监控者如何找到可读可写的I/O流并传递给用户态应用程序?

    • select:select会将之前传递给内核的文件句柄再次从内核态传到用户态(第二次拷贝),select返回给用户态的只是可读可写的文件内句柄总数,再使用FD_ISSET宏函数来检测哪些文件I/O可读可写(遍历);

    • epoll_wait:epoll_wait只监控就绪链表就可以,如果就绪链表有文件句柄,则表示该文件句柄可读可写,并返回到用户态(少量的拷贝);

  • 继续循环时监控者怎么重复上述步骤?

    • select:select对于事件的监控是建立在内核的修改之上的,也就是说经过一次监控之后,内核会修改位,因此再次监控时需要再次从用户态到内核态进行拷贝(第N次拷贝);

    • epoll:由于内核不修改文件句柄的位,所以只需要第一次传入就可以重复监控,直到epoll_ctl删除,否则不需要重新传入,因此无多次拷贝;

简单说:epoll是继承了select/poll的I/O复用的思想,并在两者基础上从监控I/O流,查找I/O事件等角度来提高效率,具体地说就是内核句柄列表,红黑树、就绪list链表来实现的。

epoll详解

先回顾一下如何使用 C 库封装的3个epoll系统调用吧。

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

使用起来很清晰:

  • epoll_create建立一个epoll 对象,参数size 是内核保证能够正确处理的最大句柄数,多于这个最大数内核可不保证效果;

  • epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll 中让其监控,或者把epoll正在监控的某个socket句柄移出epoll,不再监控它等等(也就是将I/O流放到内核);

  • epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程(也就是在内核层面捕获可读写的I/O事件);

从上面的调用方式就可以看到 epoll 比 select/poll 的优越之处:

因为后者每次调用时都将传递所要监控的所有socket给 select/poll 系统调用,这意味着需要将用户态的socket列表拷贝到内核态,如果以万计的句柄会导致每次都要拷贝几十几百KB的内存到内核,非常低效。而我们调用epoll_wait 时就相当于以往调用 select/poll,但是这时却不传递socket 句柄给内核,因为内核已经在epoll_ctl 中拿到了要监控的句柄列表;

select监控的句柄列表在内核态,每次调用都需要从用户态将句柄拷贝到内核态,但是epoll中句柄就是建立在内核中的,这样就减少了内核和用户态的拷贝,高效的原因之一

所以,实际在你调用epoll_create后,内核就已经在内核态开始帮你准备要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄;

在内核里,一切皆文件。所以,epoll向内核注册了一个文件系统,用于存储上述的被监控socket,当你调用epoll_create时,就会在这个虚拟的epoll文件系统创建一个file结点,当然这个file不是普通文件,它只服务于epoll。

epoll在被内核初始化时,同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是建立连续的物理内存区,然后在之上建立slab层,简单的说,就是物理上分配好你想要的size的内存对象,每次使用时都是使用空闲的已分配号的对象;

epoll高效的原因

这里由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建立了file结点,在内核cache里建立了红黑树用于存储以后epoll_ctl传来的socket外,还会建立一个list链表,用于存储就绪的事件;

epoll_wait调用时,仅仅观察这个list链表有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到达后即使链表没有数据也返回,所以epoll非常高效。而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪的句柄而已,所以,epoll_wait仅需要从内核态拷贝少量的句柄到用户态而已;

那么,这个准备就绪list链表是怎么维护的呢?

当我们执行epoll_ctl 时,除了把socket放到epoll文件系统的file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据拷贝到内核中后就把socket插入到准备就绪list链表里面;

epoll综合的执行过程

如此,一颗红黑树,一张准备就绪链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在则立即返回,不存在添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据,执行epoll_wait时,立刻返回准备就绪链表的数据即可。

epoll水平触发和边缘触发的实现

当一个socket句柄上有事件时,内核会把该句柄插入到上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表,最后,epoll_wait干了件事,就是检查这些socket,如果不是ET模式(就是LT模式的句柄了),并且这些socket上确实有未处理的事件时,又把这些句柄放回到刚刚清空的准备就绪链表了,所以,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式下的句柄,除非有新中断到,即使socket上的事件没有处理完,也是不会次次从epoll_wait返回的。

每个epollfd在内核中有一个对应的eventpoll结构对象.其中关键的成员是一 readylist(eventpoll:rdllist) 和一棵红黑树(eventpoll:rbr)。

一个fd被添加到epoll之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象 .epitem 被添加到eventpoll 的红黑树中,红黑树的作用是使用者调用EPOLL_MOD的时候可以快速找到fd 对应的epitem。调用epoll_wait时,将将readylist中的epitem出列,将触发的事件拷贝到用户空间。之后判断epitem是否需要重新添加回readylist。

EPOLLONESHOT

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

区别就在于epoll_wait将socket返回到用户态时是否清空就绪链表。

epoll高效的本质

  • 减少用户态和内核态之间的文件句柄拷贝;
  • 减少了对可读可写文件句柄的遍历;

本文参考:

  • epoll原理

深入学习可参考:

  • The Implementation of epoll (1)
  • The Implementation of epoll (2)
  • The Implementation of epoll (3)
  • The Implementation of epoll (4)
原创粉丝点击