开发高性能网络程序时。windows开发人员们言必称iocp,linux开发人员们则言必称epoll。大家都明确epoll是一种IO多路复用技术,能够很高效的处理数以百万计的socket句柄,比起曾经的select和poll效率高大发了。

我们用起epoll来都感觉挺爽,确实快,那么。它究竟为什么能够快速处理这么多并发连接呢?

先简单回想下怎样使用C库封装的3个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对象。參数size是内核保证可以正确处理的最大句柄数。多于这个最大数时内核可不保证效果。

epoll_ctl能够操作上面建立的epoll,比如,将刚建立的socket增加到epoll中让其监控。或者把 epoll正在监控的某个socket句柄移出epoll。不再监控它等等。

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的内存对象,每次使用时都是使用空暇的已分配好的对象。

  1. static int __init eventpoll_init(void)
  2. {
  3. ... ...
  4. /* Allocates slab cache used to allocate "struct epitem" items */
  5. epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),
  6. 0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,
  7. NULL, NULL);
  8. /* Allocates slab cache used to allocate "struct eppoll_entry" */
  9. pwq_cache = kmem_cache_create("eventpoll_pwq",
  10. sizeof(struct eppoll_entry), 0,
  11. EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL);
  12. ... ...

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返回的。

最新文章

  1. vim /vi中对字符串的查找并替换
  2. 有了jsRender,妈妈再也不用担心我用jq拼接DOM拼接的一团糟了、页面整齐了、其他伙伴读代码也不那么费劲了
  3. node.js 基础学习笔记3 -express
  4. [BZOJ3872][Poi2014]Ant colony
  5. spring4 文件下载功能
  6. 解决chrome同步问题
  7. 关于硬盘和几种RAID
  8. Android(java)学习笔记142:使用Sqlite基本流程
  9. 采用xml的方式保存数据
  10. Codeforces 474 E. Pillars
  11. Silverlight类百度文库在线文档阅读器(转)
  12. JavaScript深入浅出补充——(二)语句和严格模式,对象
  13. float浮动属性的基本常识
  14. [Python Study Notes]with的使用
  15. foo的出现
  16. [Swift]LeetCode873. 最长的斐波那契子序列的长度 | Length of Longest Fibonacci Subsequence
  17. SQL 操作字符串
  18. 利用Fiddler拦截接口请求并篡改数据
  19. 设计模式之桥接模式 c++11
  20. c++数组的引用

热门文章

  1. MFC_1.3 控件子类化 消息反射
  2. JavaScript 实现页面中录音功能
  3. ThinkPHP---TP功能类之邮件
  4. 若要允许 GET 请求,请将 JsonRequestBehavior 设置为 AllowGet(转载)
  5. Oracle 11g 字符集修改
  6. 在Java中调用带参数的存储过程
  7. 10 Minutes to pandas中文版
  8. Linux有几种安装软件的方式?????
  9. CCF201612-2 工资计算 java(100分)
  10. Linux文件/目录,权限相关