IO模型:Select,Poll,ePoll ------ 理解

参考文章:
epoll IO多路复用
8分钟深入浅出搞懂BIO、NIO、AIO
epoll详解——从功能到内核
彻底学会使用epoll(一)——ET模式实现分析
Linux IO模式及 select、poll、epoll详解
Linux下的I/O复用与epoll详解
大话 Select、Poll、Epoll


总结一下要点:

  • select,poll,epoll都属于NIO的实现方式,epoll也有人成为"伪AIO"

  • 对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:

    1. 等待数据准备 (Waiting for the data to be ready) — 就是先填充缓存区
    2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
  • select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。


  • select缺点

    • 可以从函数参数列表上来看,select只能监听读、写、异常这三个事件
    • select监听的描述符是有最大值限制的,在Linux内核中是1024
    • select的实现是每次将待检测的描述符放在位数组中,全部传给内核进行监听,内核监听之后会返回一个就绪描述符个数,并且修改了监听的事件值,以表示该事件就绪。内核再将修改后的数组传给用户空间。用户空间只能通过遍历所有描述符来处理就绪的描述符,之后再将描述符传给内核继续监听…很明显,这样在监听的描述符少的情况下并不影响效率,但是监听的描述符数量特别大的情况下,每次又只有少数描述符上有事件就绪,大量的换入换出会使得效率十分低下
  • poll的优缺点

    • 解决了select前两个问题,监听的描述符数量没有严格限制(因为存储描述符方式相对于select由数组换成了链表),监听的事件不止读、写、异常,但是第三个缺点依然存在,存在大量的换入换出。

  • epoll内核剖析

  • epoll_create

    • epoll_create的做法是创建出一个内核事件表,实际上就是创建文件,这其中包括文件描述符的分配、文件实体的分配等等,在这里我们记住文件描述符中有一个域很重要,就是:private_data域,该域才是epoll的核心,其中有内核时间表、就绪描述符队列等信息。
  • epoll_ctl(低频调用)

    • 该函数主要是对内核事件表的操作,涉及插入(添加监听描述符)、删除(删除被监听的描述符)、修改(修改被监听的描述符)。主要有以下步骤:
      • 遍历内核事件表,看该描述符是否在内核事件表中。
      • 判断所要做的操作:插入、删除或是修改
      • 根据操作做相应处理
    • 注意:此处注意的一点是在插入的时候,对相应的描述符注册了回调函数,即当该描述符上有数据就绪时,自动调用回调函数将该描述符加入就绪队列
  • epoll_wait(高频调用,是一个死循环,当rdlist为空时挂起,rdlist不为空时被回调唤醒)

    • 在看epoll_wait之前,我们说一下内核事件表和组织就绪描述符的数据结构。内核事件表的底层数据结构是红黑树,就绪描述符的底层数据结构是链表。
    • epoll_wait的功能就是不断查看就绪队列中有没有描述符,如果没有就一直检查、直到超时。如果有就绪描述符,就将就绪描述符通知给用户。此处有关ET和LT模式就是在给用户空间返回就绪描述符的时候体现的。

epoll对fds集合拷贝问题的解决

对于IO多路复用,有两件事是必须要做的(对于监控可读事件而言):1. 准备好需要监控的fds集合;2. 探测并返回fds集合中哪些fd可读了。细看select或poll的函数原型,我们会发现,每次调用select或poll都在重复地准备(集中处理)整个需要监控的fds集合。然而对于频繁调用的select或poll而言,fds集合的变化频率要低得多,我们没必要每次都重新准备(集中处理)整个fds集合。

于是,epoll引入了epoll_ctl系统调用,将高频调用的epoll_wait和低频的epoll_ctl隔离开。同时,epoll_ctl通过(EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL)三个操作来分散对需要监控的fds集合的修改,做到了有变化才变更,将select或poll高频、大块内存拷贝(集中处理)变成epoll_ctl的低频、小块内存的拷贝(分散处理),避免了大量的内存拷贝。同时,对于高频epoll_wait的可读就绪的fd集合返回的拷贝问题,epoll通过内核与用户空间mmap(内存映射)同一块内存来解决。mmap将用户空间的一块地址和内核空间的一块地址同时映射到相同的一块物理内存地址(不管是用户空间还是内核空间都是虚拟地址,最终要通过地址映射映射到物理地址),使得这块物理内存对内核和对用户均可见,减少用户态和内核态之间的数据交换。

另外,epoll通过epoll_ctl来对监控的fds集合来进行增、删、改,那么必须涉及到fd的快速查找问题,于是,一个低时间复杂度的增、删、改、查的数据结构来组织被监控的fds集合是必不可少的了。在linux 2.6.8之前的内核,epoll使用hash来组织fds集合,于是在创建epoll fd的时候,epoll需要初始化hash的大小。于是epoll_create(int size)有一个参数size,以便内核根据size的大小来分配hash的大小。在linux 2.6.8以后的内核中,epoll使用红黑树来组织监控的fds集合,于是epoll_create(int size)的参数size实际上已经没有意义了。


回顾一下ET模式和LT模式:ET模式是高效模式,就绪描述符只通知用户一次,如果用户没做处理内核将不再进行通知;LT模式比较稳定,如果用户没有处理就绪的描述符,内核会不断通知。

接下来我们看一下具体是怎么实现的。当为ET模式时,上边我们提到就绪描述符是用链表组织的,因此只需将就绪部分断链发给用户,而在LT模式下,用户没有处理就绪描述符时,内核会再次将未处理的就绪描述符加入到就绪队列中重复提醒用户空间。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值