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)系统调用实现解析

  • 相关阅读:
    1451. Rearrange Words in a Sentence
    1450. Number of Students Doing Homework at a Given Time
    1452. People Whose List of Favorite Companies Is Not a Subset of Another List
    1447. Simplified Fractions
    1446. Consecutive Characters
    1448. Count Good Nodes in Binary Tree
    709. To Lower Case
    211. Add and Search Word
    918. Maximum Sum Circular Subarray
    lua 时间戳和时间互转
  • 原文地址:https://www.cnblogs.com/harvyxu/p/7487778.html
Copyright © 2011-2022 走看看