zoukankan      html  css  js  c++  java
  • IO多路复用--总结

    select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

    1 select实现

    select的调用过程如下所示:

    (1)使用copy_from_user从用户空间拷贝fd_set到内核空间;

    (2)注册回调函数__pollwait;

    (3)内核态,遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll);

        以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数。

        __pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到

        等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒。

    (4)poll方法返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。

    (5)如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。

    (6)把fd_set从内核空间拷贝到用户空间。

    select的几大缺点:

    (1)每次调用select,都要把重新把fd集合从用户态拷贝到内核态,并将当前进程(current)挂在到fd对应设备的等待队列等准备工作,这个开销在fd很多时会很大;

    (2)同时每次调用select都需要在内核遍历传递进来的所有fd看是否有事件发生,这个开销在fd很多时也很大;

    (3)select支持的文件描述符数量太小了,默认是1024;

    (4)对于返回到用户空间的fd_set,仍需要遍历fd_set找到有事件发生的fd;

    2 poll实现

    poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构;poll没有最大文件描述符数量的限制。

    3 epoll实现

    epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。epoll提供了三个函数:

    • epoll_create: 是创建一个epoll实例,并返回fd句柄;epoll实例主要包含结构体--红黑树(用于事件注册),双向就绪链表rdlist(保存callback函数返回的就绪事件);
    • epoll_ct: 注册要监听的事件类型;
    • epoll_wait:等待事件的发生;

      对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll实例中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只需拷贝一次

      对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第5步是类似的)。

      对于第三个缺点,epoll没有类似select的文件描述符数量限制;

      对于第四个缺点,返回到用户的是就绪的事件;

    epoll_wait的工作流程:

    (1)epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒。

    (2)文件fd状态改变时(buffer由不可读变为可读或由不可写变为可写),相应fd上的回调函数ep_poll_callback()被调用。

    (3)ep_poll_callback将相应fd对应epitem加入rdlist,导致rdlist不空,进程被唤醒,epoll_wait得以继续执行。

    (4)ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。

    (5)ep_send_events函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对用的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。 

    4 总结

    系统调用

    select

    poll

    epoll

    事件集合

    用户通过3个参数分别传入感兴趣的可读,可写及异常等事件,

    内核通过对这些参数的在线修改来反馈其中的就绪事件这使得

    用户每次调用select都要重置这3个参数

    统一处理所有事件类型,因此只需要一个事件集参数。

    用户通过pollfd.events传入感兴趣的事件,内核通过

    修改pollfd.revents反馈其中就绪的事件

    内核通过一个事件表直接管理用户感兴趣的所有事件。

    因此每次调用epoll_wait时,无需反复传入用户感兴趣

    的事件。epoll_wait系统调用的参数events仅用来反馈就绪的事件

    应用程序索引就绪文件描述符的时间复杂度

    O(n)

    O(n)

    O(1)

    最大支持文件描述符数

    一般有最大值限制

    65535

    65535

    工作模式

    LT

    LT

    支持ET高效模式

    内核实现和工作效率 采用轮询方式检测就绪事件,时间复杂度:O(n)

    采用轮询方式检测就绪事件,时间复杂度:O(n)

    采用回调方式检测就绪事件,时间复杂度:O(1)

      需要注意的是:epoll并不是在所有的应用场景都会比select和poll高很多。尤其是当活动连接比较多的时候,回调函数被触发得过于频繁的时候,epoll的效率也会受到显著影响!所以,epoll特别适用于连接数量多,但活动连接较少的情况。

     参考:

    select、poll、epoll之间的区别总结[整理]

    Linux下的I/O复用与epoll详解

    Linux下多路复用接口

    select(poll)系统调用实现解析

  • 相关阅读:
    luogu p1268 树的重量——构造,真正考验编程能力
    luogu p2330[SCOI05] 繁忙的都市——瓶颈生成树
    生成树的个数——基尔霍夫定理(Matrix-Tree Theorem)
    子序列最大和
    有关pascal的填充语句小技巧
    P2320 [HNOI2006]鬼谷子的钱袋
    DP专题——括号序列
    简单的迷宫(bfs)noj1793
    G:献给阿尔吉侬的花束(可能超时)
    ytu 2335: 0-1背包问题
  • 原文地址:https://www.cnblogs.com/harvyxu/p/7487778.html
Copyright © 2011-2022 走看看