本文转载自epoll 原理

导语

以前经常被人问道 select、poll、epoll 的区别,基本都是靠死记硬背的,最近正好复习 linux 相关的内容,就把这一块做个笔记吧,以后也能方便查阅。

epoll 是 linux 2.6 之后新出的一种 I/O 多路复用方式,与传统的 select、poll 相比,有着很大的优势。一些开源的软件如 nginx 也采用了 epoll 的设计思路。因此,学习 epoll 对于我们在 linux 环境下编程是很有帮助的。

本文是 epoll 的复习笔记,主要讲一下 epoll 与传统的 select、poll 区别,实现的原理,以及应用。

为什么要 I/O 多路复用

前文说过了,epoll 是一个优秀的 I/O 多路复用方式。所以,在讲解 epoll 之前,我们先来看一下为什么需要 I/O 多路复用。

阻塞 OR 非阻塞

我们知道,对于 linux 来说,I/O 设备为特殊的文件,读写和文件是差不多的,但是 I/O 设备因为读写与内存读写相比,速度差距非常大。与 cpu 读写速度更是没法比,所以相比于对内存的读写,I/O 操作总是拖后腿的那个。网络 I/O 更是如此,我们很多时候不知道网络 I/O 什么时候到来,就好比我们点了一份外卖,不知道外卖小哥们什么时候送过来,这个时候有两个处理办法:

  • 第一个是我们可以先去睡觉,外卖小哥送到楼下了自然会给我们打电话,这个时候我们在醒来取外卖就可以了。
  • 第二个是我们可以每隔一段时间就给外卖小哥打个电话,这样就能实时掌握外卖的动态信息了。

第一种方式对应的就是阻塞的 I/O 处理方式,进程在进行 I/O 操作的时候,进入睡眠,如果有 I/O 时间到达,就唤醒这个进程。第二种方式对应的是非阻塞轮询的方式,进程在进行 I/O 操作后,每隔一段时间向内核询问是否有 I/O 事件到达,如果有就立刻处理。

线程池 OR 轮询

在现实中,我们当然选择第一种方式,但是在计算机中,情况就要复杂一些。我们知道,在 linux 中,不管是线程还是进程都会占用一定的资源,也就是说,系统总的线程和进程数是一定的。如果有许多的线程或者进程被挂起,无疑是白白消耗了系统的资源。而且,线程或者进程的切换也是需要一定的成本的,需要上下文切换,如果频繁的进行上下文切换,系统会损失很大的性能。一个网络服务器经常需要连接成千上万个客户端,而它能创建的线程可能之后几百个,线程耗光就不能对外提供服务了。这些都是我们在选择 I/O 机制的时候需要考虑的。这种阻塞的 I/O 模式下,一个线程只能处理一个流的 I/O 事件,这是问题的根源。

这个时候我们首先想到的是采用线程池的方式限制同时访问的线程数,这样就能够解决线程不足的问题了。但是这又会有第二个问题了,多余的任务会通过队列的方式存储在内存只能够,这样很容易在客户端过多的情况下出现内存不足的情况。

还有一种方式是采用轮询的方式,我们只要不停的把所有流从头到尾问一遍,又从头开始。这样就可以处理多个流了。

代理

采用轮询的方式虽然能够处理多个 I/O 事件,但是也有一个明显的缺点,那就是会导致 CPU 空转。试想一下,如果所有的流中都没有数据,那么 CPU 时间就被白白的浪费了。

为了避免CPU空转,可以引进了一个代理。这个代理比较厉害,可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流。

这就是 select 与 poll 所做的事情,可见,采用 I/O 复用极大的提高了系统的效率。

为什么需要 epoll

select 与 poll 的缺陷

上文中我们发现,实现一个代理来帮助我们处理 I/O 时间能够极大的提高工作效率,select 与 poll 就是这样的代理。

但是它们也不是完美的,从上文中我们可以发现,我们能够从 select 中知道是只是有 I/O 事件发生了。但是我们不知道那一个事件发生,每一个 I/O 事件发生的时候,都需要轮询所有的流,这样的时间复杂度 O(N)。但是很多情况下,发生 I/O 时间的只是少数的几个。通过轮询所有的找出少数的几个发生 I/O 的流显然效率非常低下,因此 select 和 epoll 通常只能处理几千个并发连接。

epoll 的优势

说了这么多,总算引出了我们的主人公 epoll 了。不同于忙轮询和无差别轮询,epoll 会把哪个流发生了怎样的 I/O 事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(k),k为产生 I/O 事件的流的个数。

epoll 原理

epoll 操作

epoll 在 linux 内核中申请了一个简易的文件系统,把原先的一个 select 或者 poll 调用分为了三个部分:调用 epoll_create 建立一个 epoll 对象(在 epoll 文件系统中给这个句柄分配资源)、调用 epoll_ctl 向 epoll 对象中添加连接的套接字、调用 epoll_wait 收集发生事件的连接。这样只需要在进程启动的时候建立一个 epoll 对象,并在需要的时候向它添加或者删除连接就可以了,因此,在实际收集的时候,epoll_wait 的效率会非常高,因为调用的时候只是传递了发生 IO 事件的连接。

epoll 实现

我们以 linux 内核 2.6 为例,说明一下 epoll 是如何高效的处理事件的。

当某一个进程调用 epoll_create 方法的时候,Linux 内核会创建一个 eventpoll 结构体,这个结构体中有两个重要的成员。

  • 第一个是 rb_root rbr,这是红黑树的根节点,存储着所有添加到 epoll 中的事件,也就是这个 epoll 监控的事件。
  • 第二个是 list_head rdllist 这是一个双向链表,保存着将要通过 epoll_wait 返回给用户的、满足条件的事件。

每一个 epoll 对象都有一个独立的 eventpoll 结构体,这个结构体会在内核空间中创造独立的内存,用于存储使用 epoll_ctl 方法向 epoll 对象中添加进来的事件。这些事件都会挂到 rbr 红黑树中,这样就能够高效的识别重复添加的节点。

所有添加到 epoll 中的事件都会与设备(如网卡等)驱动程序建立回调关系,也就是说,相应的事件发生时会调用这里的方法。这个回调方法在内核中叫做 ep_poll_callback,它把这样的事件放到 rdllist 双向链表中。在 epoll 中,对于每一个事件都会建立一个 epitem 结构体。

当调用 epoll_wait 检查是否有发生事件的连接时,只需要检查 eventpoll 对象中的 rdllist 双向链表中是否有 epitem 元素,如果 rdllist 链表不为空,则把这里的事件复制到用户态内存中的同时,将事件数量返回给用户。通过这种方法,epoll_wait 的效率非常高。epoll-ctl 在向 epoll 对象中添加、修改、删除事件时,从 rbr 红黑树中查找事件也非常快。这样,epoll 就能够轻易的处理百万级的并发连接。

epoll 工作模式

epoll 有两种工作模式,LT(水平触发)模式与 ET(边缘触发)模式。默认情况下,epoll 采用 LT 模式工作。两个的区别是:

  • Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait() 时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你。如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。
  • Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait() 会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你。这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

当然,在 LT 模式下开发基于 epoll 的应用要简单一些,不太容易出错,而在 ET 模式下事件发生时,如果没有彻底地将缓冲区的数据处理完,则会导致缓冲区的用户请求得不到响应。注意,默认情况下 Nginx 采用 ET 模式使用 epoll 的。

参考资料

https://www.cnblogs.com/ajianbeyourself/p/5859989.html

https://www.zhihu.com/question/20122137

epoll 原理的更多相关文章

  1. select/poll/epoll原理探究及总结

    select,poll,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作.但select ...

  2. epoll原理详解及epoll反应堆模型

    本文转载自epoll原理详解及epoll反应堆模型 导语 设想一个场景:有100万用户同时与一个进程保持着TCP连接,而每一时刻只有几十个或几百个TCP连接是活跃的(接收TCP包),也就是说在每一时刻 ...

  3. epoll原理解释(转)

    转自:http://yaocoder.blog.51cto.com/2668309/888374   首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象. ...

  4. epoll原理与本质

    我们知道IO模型中有一个NIO模型,像我们平时接触到的dubbo类的RPC框架底层基于Netty作为通讯框架,而Netty实现的IO模型就是NIO模型.而NIO模型中 底层技术就用到了Linux的ep ...

  5. EPOLL原理详解(图文并茂)

    文章核心思想是: 要清晰明白EPOLL为什么性能好. 本文会从网卡接收数据的流程讲起,串联起CPU中断.操作系统进程调度等知识:再一步步分析阻塞接收数据.select到epoll的进化过程:最后探究e ...

  6. Socket编程实践(11) --epoll原理与封装

    常用模型的特点 Linux 下设计并发网络程序,有典型的Apache模型(Process Per Connection,PPC), TPC(Thread Per Connection)模型,以及 se ...

  7. redis epoll 原理梗概

    redis 是一个单线程却性能非常好的内存数据库, 主要用来作为缓存系统. redis 采用网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量.为什么 Redis 中要使用 I/O 多路复用这 ...

  8. select和epoll原理和区别

    对于select和poll,其主要原理跟epoll不同 poll和select的共同点就是,对全部指定设备(fd)都做一次poll,当然这往往都是还没有就绪的,那就会通过回调函数把当前进程注册到设备的 ...

  9. epoll原理

    系统调用说明 epoll_create:在内核中创建epoll结构 epoll_ctl:add 1. 调用监听的文件的poll方法,设置callback 2. 设备就绪时唤醒等待队列上的进程,此时会调 ...

随机推荐

  1. Spring,Spring MVC,MyBatis,Hibernate总结

    将之前学习的框架知识进行了UML图总结,若有错误或不当之处,劳烦朋友们指正,会及时作出修改和补充: [toc] Spring Spring MVC MyBatis,Hibernate

  2. Docker容器管理平台Rancher高可用部署——看这篇就够了

    记得刚接触Rancher时,看了官方文档云里雾里,跟着官网文档部署了高可用Rancher,发现不管怎么折腾都无法部署成功(相信已尝试的朋友也有类似的感觉),今天腾出空来写个总结,给看到的朋友留个参考( ...

  3. Java JDBC 模糊查询 避免输入_,%返回全部数据

    Java JDBC 模糊查询 避免输入_,%返回全部数据 "SELECT * FROM employees WHERE INSTR(first_name,?)>0 " 仅供参 ...

  4. linux(10)linux vi/vim

    前言 所有的 Unix Like 系统都会内建 vi 文书编辑器,其他的文书编辑器则不一定会存在. 但是目前我们使用比较多的是vim编辑器. vim 具有程序编辑的能力,可以主动的以字体颜色辨别语法的 ...

  5. Pytest(7)自定义用例顺序pytest-ordering

    前言 测试用例在设计的时候,我们一般要求不要有先后顺序,用例是可以打乱了执行的,这样才能达到测试的效果. 有些同学在写用例的时候,用例写了先后顺序, 有先后顺序后,后面还会有新的问题(如:上个用例返回 ...

  6. Kwp2000协议的应用(程序后续篇)

    作者:良知犹存 转载授权以及围观:欢迎添加微信:becom_me 总述 接上篇文章,本篇继续对基于PID解析数据,如何依据J1979的标准进行解析数据 先给昨天的文章补上一张故障码对照表,昨天分析了如 ...

  7. P6686 混凝土数学

    哈哈哈!我爱月赛. 第一次月赛拿到分呢. (卡掉卡掉) 题目描述 你正在看混凝土数学,这时旁边的工地开工了,你觉得看他们施工更有意思,于是你向窗外望去,注意到了一些长度不同的木棍.具体而言,你看到了  ...

  8. Manthan, Codefest 19 (open for everyone, rated, Div. 1 + Div. 2) E. Let Them Slide(数据结构+差分)

     题意:问你有n个长度总和为n的数组 你可以移动数组 但不能移出长度为w的矩形框 问你每一列的最大值是多少? 思路:只有一次询问 我们可以考虑差分来解决 然后对于每一行数组 我们可以用数据结构维护一下 ...

  9. 2019牛客暑期多校训练营(第三场)A.Graph Games (分块)

    题意:给你n个点 m条边的一张图 现在有q次操作 每次操作可以选择反转l~r的边号 也可以询问S(l)和S(r) 连接成的点集是否相同 思路:我们把m条边分块 用一个S数组维护每块对一个点的贡献 然后 ...

  10. 2018 ccpc吉林 The Tower

    传送门:HDU - 6559 题意 在一个三维空间,给定一个点和他的三维速度,给定一个圆锥,问这个点最早什么时候能撞上圆锥. 题解 本来一直想着怎么求圆锥的方程,然后....队友:这不是二分吗!然后问 ...