【转】 Linux下epoll是如何实现高效处理百万句柄的

做者:陶辉linux

原文地址:http://blog.csdn.net/russell_tao/article/details/7160071windows




开发高性能网络程序时,windows开发者们言必称iocp,linux开发者们则言必称epoll。你们都明白epoll是一种IO多路复用技术,能够很是高效的处理数以百万计的socket句柄,比起之前的select和poll效率高大发了。咱们用起epoll来都感受挺爽,确实快,那么,它到底为何能够高速处理这么多并发链接呢?网络


先简单回顾下如何使用C库封装的3个epoll系统调用吧。数据结构


[cpp] view plain copy并发

  1. int epoll_create(int size);  socket

  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  ide

  3. 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,再也不监控它等等。spa

epoll_wait在调用时,在给定的timeout时间内,当在监控的全部句柄中有事件发生时,就返回用户态的进程。


从上面的调用方式就能够看到epoll比select/poll的优越之处:由于后者每次调用时都要传递你所要监控的全部socket给select/poll系统调用,这意味着须要将用户态的socket列表copy到内核态,若是以万计的句柄会致使每次都要copy几十几百KB的内存到内核态,很是低效。而咱们调用epoll_wait时就至关于以往调用select/poll,可是这时却不用传递socket句柄给内核,由于内核已经在epoll_ctl中拿到了要监控的句柄列表。


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


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


epoll在被内核初始化时(操做系统启动),同时会开辟出epoll本身的内核高速cache区,用于安置每个咱们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是创建连续的物理内存页,而后在之上创建slab层,简单的说,就是物理上分配好你想要的size的内存对象,每次使用时都是使用空闲的已分配好的对象。


[cpp] view plain copy

  1. static int __init eventpoll_init(void)  

  2. {  

  3.     ... ...  

  4.   

  5.     /* Allocates slab cache used to allocate "struct epitem" items */  

  6.     epi_cache = kmem_cache_create("eventpoll_epi"sizeof(struct epitem),  

  7.             0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,  

  8.             NULL, NULL);  

  9.   

  10.     /* Allocates slab cache used to allocate "struct eppoll_entry" */  

  11.     pwq_cache = kmem_cache_create("eventpoll_pwq",  

  12.             sizeof(struct eppoll_entry), 0,  

  13.             EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL);  

  14.   

  15.  ... ...  


epoll的高效就在于,当咱们调用epoll_ctl往里塞入百万个句柄时,epoll_wait仍然能够飞快的返回,并有效的将发生事件的句柄给咱们用户。这是因为咱们在调用epoll_create时,内核除了帮咱们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储之后epoll_ctl传来的socket外,还会再创建一个list链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据便可。有数据就返回,没有数据就sleep,等到timeout时间到后即便链表没数据也返回。因此,epoll_wait很是高效。


并且,一般状况下即便咱们要监控百万计的句柄,大多一次也只返回不多量的准备就绪句柄而已,因此,epoll_wait仅须要从内核态copy少许的句柄到用户态而已,如何能不高效?!


那么,这个准备就绪list链表是怎么维护的呢?当咱们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上以外,还会给内核中断处理程序注册一个回调函数,告诉内核,若是这个句柄的中断到了,就把它放到准备就绪list链表里。因此,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。


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


最后看看epoll独有的两种模式LT和ET。不管是LT和ET模式,都适用于以上所说的流程。区别是,LT模式下,只要一个句柄上的事件一次没有处理完,会在之后调用epoll_wait时次次返回这个句柄,而ET模式仅在第一次返回。


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



                            做者:陶辉

                            原文地址:http://blog.csdn.net/russell_tao/article/details/7160071

相关文章
相关标签/搜索