高效地对海量用户提供服务,必须要让进程能同时处理很多个tcp连接。假设一个进程保持了10000条连接,如何发现哪条连接上有数据可读、可写?

实现:循环遍历来发现IO事件?效率太低了。

目录

IO模型

同步阻塞IO模型(Blocking IO)

同步非阻塞IO模型(nonblocking IO)

IO多路复用机制(IO multiplexing)

信号驱动式IO(signal-driven IO)

异步IO模型(asynchronous IO)

Epoll原理

epoll_create()

epoll_ctl()

epoll_wait()

数据到来

epoll的工作模式

epoll更高效的原因


IO模型

首先先看不同的IO模型,有以下五种:

同步阻塞IO模型(Blocking IO)

阻塞IO的执行过程是进程进行系统调用,等待内核将数据准备好并复制到用户态缓冲区后,进程放弃使用CPU并一直阻塞在此,直到数据准备好。

同步非阻塞IO模型(nonblocking IO)

每隔一段时间应用程序就去询问内核是否有数据准备好。如果就绪,就进行拷贝操作;如果未就绪,就不阻塞程序,内核直接返回未就绪的返回值,等待用户程序下一个轮询。

IO多路复用机制(IO multiplexing)

IO多路复用机制,是通过系统内核缓冲IO数据,让单个进程可以监视多个文件描述符,一旦某个描述符就绪(读就绪或写就绪),能够通知程序进行相应的读写操作。

【注意】:IO多路复用,复用的是线程,普通的同步非阻塞一个线程每次只能操作一个socket,多路复用一个线程可以操作多个。复用的是线程,多路指的是多个连接,也就是socket。

信号驱动式IO(signal-driven IO)

首先我们允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

异步IO模型(asynchronous IO)

相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。

Epoll原理

epoll 是 select 和 poll 的升级版,改进了工作方式,更加的高效。select/poll 低效的原因之一是将 “添加 / 维护待检测任务” 和 “阻塞进程 / 线程” 两个步骤合二为一。大多数场景中需要监听的socket是固定的,不需要每次都进行修改。epoll 将这两个操作分开,先用 epoll_ctl 维护等待队列,再调用 epoll_wait 阻塞进程。

epoll的通俗解释,当文件描述符的内核缓冲区非空的时候,发出可读信号进行通知,当写缓冲区不满的时候,发出可写信号通知的机制。

epoll中三个API函数:

#include <sys/epoll.h>
// 创建epoll实例,通过一棵红黑树管理待检测集合
int epoll_create(int size);
// 管理红黑树上的文件描述符(添加、修改、删除)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 检测epoll树中是否有就绪的文件描述符
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll_create()

epoll_create() 函数的作用是创建一个epoll实例,把它关联到当前进程的已打开文件列表里,通过一棵红黑树管理待检测集合。epoll的结构:

// file:fs/eventpoll.c
struct eventpoll {//sys_epoll_wait用到的等待队列wait_queue_head_t wq;//接收就绪的描述符都会放到这里struct list_head rdllist;//每个epoll对象中都有一颗红黑树struct rb_root rbr;......
}

wq: 等待队列链表。软中断数据就绪的时候会通过 wq 来找到阻塞在 epoll 对象上的用户线程。
rbr: 一棵红黑树。为了支持对海量连接的高效查找、插入和删除,epoll 内部使用了一棵红黑树。通过这棵树来管理用户进程下添加进来的所有 socket 连接。
rdllist: 就绪的描述符的链表。当有的连接就绪的时候,内核会把就绪的连接放到 rdllist链表里。这样应用进程只需要判断链表就能找出就绪描述符,而不用去遍历整棵树。

epoll_ctl()

epoll_ctl() 函数的作用是管理红黑树实例上的节点,可以进行添加、删除、修改操作。

假如我们与客户的连接socket已经创建好了,也通过epoll_create()创建好了epoll对象,那么通过调用epoll_ctl()函数来注册每一个socket的时候,内核会做以下事情:①分配一个红黑树节点对象epitem②添加等待事件到socket的等待队列中,并注册其回调函数为ep_poll_callback③将epitem加入到epoll对象的红黑树中

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);epfd:epoll实例op:枚举值,表示操作。EPOLL_CTL_ADD:添加新节点;EPOLL_CTL_MOD:修改节点;EPOLL_CTL_DEL:删除节点fd:文件描述符,即要添加 / 修改 / 删除的文件描述符event:epoll事件。events:委托epoll检测的事件EPOLLIN:读事件,接收数据,检测读缓冲区,如果有数据该文件描述符就绪EPOLLOUT:写事件,发送数据,检测写缓冲区,如果可写该文件描述符就绪EPOLLERR:异常事件

epoll_wait()

epoll_wait() 函数的作用是检测创建的 epoll 实例中有没有就绪的文件描述符。

当它被调用时它观察 epoll->rdllist 链表里有没有数据即可。有数据就返回,没有数据就创建一个等待队列项,将其添加到 epoll 的等待队列上,然后把自己阻塞掉(进入睡眠状态)就完事。

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);epfd:epoll实例events:传出参数,这是一个结构体数组的地址,里边存储了已就绪的文件描述符的信息maxevents:修饰第二个参数,结构体数组的容量(元素个数)timeout:如果检测的 epoll 实例中没有已就绪的文件描述符,该函数阻塞的时长,单位ms毫秒0:函数不阻塞,不管 epoll 实例中有没有就绪的文件描述符,函数被调用后都直接返回大于 0:如果 epoll 实例中没有已就绪的文件描述符,函数阻塞对应的毫秒数再返回-1:函数一直阻塞,直到 epoll 实例中有已就绪的文件描述符之后才解除阻塞

数据到来

socket->sock->sk_data_ready 设置的就绪处理函数是 sock_def_readable
在 socket 的等待队列项中,其回调函数是 ep_poll_callback。
在 eventpoll 的等待队列项中,回调函数是 default_wake_function。其 private 指向的是等待该事件的用户进程。

①当数据到来的时候,先将接收的数据放入socket的接收队列上。

②当 socket 上数据就绪时候,唤醒在 socket上等待的用户进程,内核会找到 epoll_ctl 添加 socket 时在其上设置的回调函数 ep_poll_callback

③接着软中断就会调用这个回调函数ep_poll_callback,它会根据等待任务队列项上的额外的 base 指针可以找到 epitem, 进而也可以找到 epoll对象。

④首先它做的第一件事就是把自己的 epitem 添加到 epoll 的就绪队列中

⑤接着它又会查看 epoll对象上的等待队列里是否有等待项(epoll_wait 执行的时候会设置,上面提到了)。

⑥如果没有,执行软中断的事情就做完了。如果有等待项,那就查找到等待项里设置的回调函数default_wake_function。

⑦在default_wake_function 中找到等待队列项里的进程描述符,然后唤醒之。

但实际生活中,活会很多的,所以基本上epoll_wait不会阻塞进程,而是不断地让用户进程干活,直到实在没有活需要干的时候才会让出cpu进入该进程等待队列。

epoll的工作模式

水平模式

内核通知使用者哪些文件描述符已经就绪,之后就可以对这些已就绪的文件描述符进行IO操作了。如果我们不作任何操作,内核还是会继续通知使用者。

读事件:如果文件描述符对应的读缓冲区还有数据,读事件就会被触发,epoll_wait()解除阻塞

写事件:如果文件描述符对应的写缓冲区可写,写事件就会被触发,epoll_wait()解除阻塞

读数据是被动的,必须要通过读事件才能知道有数据到达了,因此对于读事件的检测是必须的。写数据是主动的,并且写缓冲区一般情况下都是可写的(缓冲区不满),因此对于写事件的检测不是必须的。

边沿模式

当文件描述符从未就绪变为就绪时,内核会通过epoll通知使用者。然后它会假设使用者知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知(only once)。如果我们对这个文件描述符做 IO 操作,从而导致它再次变成未就绪,当这个未就绪的文件描述符再次变成就绪状态,内核会再次进行通知,并且还是只通知一次。

读事件:当读缓冲区有新的数据进入,读事件被触发一次,没用新数据不会触发该事件。如果数据没用被全部读走,并且没用新数据进入,读事件不会再次触发,只通知一次;如果数据被全部读走或者只读走一部分,此时有新数据进入,读事件被触发,并且只通知一次。

写事件:当写缓冲区状态可写,写事件只会触发一次。写缓冲区从不满到被写满,期间写事件只会被触发一次;写缓冲区从满到不满,状态变为可写,写事件只会被触发一次。

综上:epoll 的边沿模式下 epoll_wait () 检测到文件描述符有新事件才会通知,如果不是新的事件就不通知,通知的次数比水平模式少,效率比水平模式要高。

epoll更高效的原因

对比select、poll

select poll epoll
操作方式 遍历 遍历 回调
底层实现 数组 链表 红黑树
IO效率 每次调用都进行线性遍历,时间复杂度为O(n) 每次调用都进行线性遍历,时间复杂度为O(n) 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到就绪队列里面,时间复杂度O(1)
最大连接数 1024(x86)或2048(x64) 无上限 无上限
fd拷贝 每次调用select,都需要把fd集合从用户态拷贝到内核态 每次调用poll,都需要把fd集合从用户态拷贝到内核态 调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

①对于待检测集合select和poll是基于线性方式处理的,epoll是基于红黑树来管理待检测集合的。
②select和poll每次都会线性扫描整个待检测集合,集合越大速度越慢,epoll使用的是回调机制,效率高,处理效率也不会随着检测集合的变大而下降
③select和poll工作过程中存在内核/用户空间数据的频繁拷贝问题,在epoll中内核和用户区使用的是共享内存(基于mmap内存映射区实现),省去了不必要的内存拷贝。
④程序员需要对select和poll返回的集合进行判断才能知道哪些文件描述符是就绪的,通过epoll可以直接得到已就绪的文件描述符集合,无需再次检测。

IO多路复用机制——epoll相关推荐

  1. Socket IO多路复用: epoll原理图解

    目录 一.accept 创建新 socket 1.1 初始化 struct socket 对象 1.2 为新 socket 对象申请 file 1.3 接收连接 1.4 添加新文件到当前进程的打开文件 ...

  2. Redis IO 多路复用机制

    Redis IO 多路复用机制 基于linux select/epoll select:最大支持1024个文件描述符,在描述符较多情况下性能较差,水平触发 poll:poll与select基本相同,只 ...

  3. IO多路复用机制详解

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking IO):默认创建的s ...

  4. IO多路复用之epoll总结 http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html

    IO多路复用之epoll总结 http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html

  5. IO多路复用之epoll模型

    一.IO多路复用:一个线程监测多个IO操作 基本思想:先构造一张有关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已经准备好进行I/O函数时才返回.函数返回时告诉进程哪个描述符已经就绪, ...

  6. linux io复用命令,Linux中IO多路复用机制

    之前的面试有问到主线程在 ActivityThread 里初始化 Looper 后调用了 Looper.loop() 这个死循环为什么不会阻塞主线程,当时回答因为在 Looper.loop() 方法里 ...

  7. linux IO多路复用 select epoll

    概念 IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程 通俗理解(摘自网上一大神) 这些名词比较绕口,理解涵义就好.一个epoll场景:一个酒吧服务员(一个线程),前 ...

  8. IO多路复用之epoll总结

    1.基本知识 epoll是在2.6内核中提出的,是之前的select和poll的增强版本.相对于select和poll来说,epoll更加灵活,没有描述符限制.epoll使用一个文件描述符管理多个描述 ...

  9. selector多路复用_python selectors模块实现 IO多路复用机制的上传下载

    import selectors import socket import os,time base_dir = os.path.dirname(os.path.abspath(__file__)) ...

最新文章

  1. DIV+CSS规范命名大全集合
  2. Java堆内存分配与回收策略
  3. jvm性能调优实战 - 34十万QPS的社交APP 如何优化GC性能提升3倍?
  4. linux 移植qt,Linux下移植QT(2)---移植QT
  5. Nginx配置单项SSL以及双向SSL
  6. CentOS 7主机名修改与查看命令详述
  7. 电脑计算器_哪几种计算器可以携带入考场!注会考试忘带计算器了怎么办?
  8. stm32F4的ADC+DMA+Timer,实现2MHz连续采样。1LSB分辨率,极低噪声。
  9. webpack3+node+react+babel实现热加载(hmr)
  10. Python:笨方法学 Python3-课程 41 笔记
  11. html通过拼音首字母定位,input+div 实现输入拼音首字母或汉字自动检索上拉列表...
  12. mysql的exe文件怎么打开_exe是什么文件格式?exe文件怎么打开?
  13. eviews如何处理缺失数据填补_缺失值的处理
  14. UML学习_1_模型
  15. lq99:分分巧克力
  16. [Azure]使用Powershell输出某台ASM虚拟机的NSG和ACL
  17. 计算机未连接到网络,电脑一直提示未连接到internet怎么办?
  18. PDF怎么拆分/合并? 3款 PDF 拆分和合并工具分享
  19. 随机变量与随机过程详解
  20. Django Xadmin 官方文档 之五 Xadmin 插件制作

热门文章

  1. 物联网的四大支撑技术(简述总结)
  2. opencv3 java开发手册_介绍一本opencv不错的书-OpenCV3使用Java开发手册
  3. tp5 自定义分页详解
  4. 爬取b站“开启一个时代”周杰伦mv《可爱女人》弹幕,以及词云制作
  5. 论文分享 | MnTTS2: 开源的多说话人蒙古语TTS数据集
  6. hdu2894// 算法竞赛——进阶指南——acwing 400. 太鼓达人 欧拉回路经典题 //欧拉回路的建模小结
  7. java实现上传zip解压及判断压缩包文件夹功能
  8. Welcome Vc
  9. SQL Server2012清除数据库日志
  10. 超准!一个国际标准智商测试题!测测你是天才吗? (答案)