IO系列之(四)epoll详解

epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,可以理解为消息就绪处理机制,或者多路复用机制。相对于select和poll来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。优点如下:
1.没有最大并发连接的限制,能打开的fd上限远大于1024(1G的内存能监听约10万个端口)
2.采用回调的方式,效率提升。只有活跃可用的fd才会调用callback函数,也就是说 epoll 只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。
3.内存拷贝。使用mmap()文件映射内存来加速与内核空间的消息传递,减少复制开销

epoll对文件描述符的操作有两种模式:LT(level trigger,水平触发)和ET(edge trigger)。

水平触发:默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件。

边缘触发:当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时通知一次)。
ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。

Linux中提供的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);  

流程即为:
1. 调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句 柄数,多于这个最大数时内核可不保证效果。
2. epoll_ctl可以操作上面建立的epoll,将刚建立的socket加入到epoll中让其监控,或者把epoll正在监控的某个socket句柄移出epoll,不再监控它等等。
3. epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时, 就返回用户态的进程。

流程分析:
当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。eventpoll结构体如下所示:

1.  struct eventpoll{  
2.      ....  
3./*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/  
4.      struct rb_root  rbr;  
5./*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/  
6.      struct list_head rdlist;  
7.      ....  
8.  };  

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。
而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:

1.  struct epitem{  
2.      struct rb_node  rbn;//红黑树节点  
3.      struct list_head    rdllink;//双向链表节点  
4.      struct epoll_filefd  ffd;  //事件句柄信息  
5.      struct eventpoll *ep;    //指向其所属的eventpoll对象  
6.      struct epoll_event event; //期待发生的事件类型  
7.  }  

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

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

这里写图片描述

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值