「操作系统」IO多路复用

本文主要介绍在IO多路复用模型中, 第一阶段使用到的四个函数: select, poll, epoll, kqueue

  1. select

    1. select函数

      1. 定义

        select用来监视多个文件描述符, 当文件描述符所对应事件状态不改变时, select会阻塞. 当某个文件描述符所对应事件状态改变后, select会返回

      2. Python案例

        readable_list, writeable_list, execable_list = select.select(read_list, write_list, exce_list, timeout) 

        当read_list的fd满足可读条件时, 则将发生变化的fd添加到readable_list中. 当write_list的fd满足可写条件时, 则将发生变化的fd添加到writeable_list中. 当exce_list的fd发生错误时, 则将该发生错误的fd添加到execable_list

    2. 原理

      1. select通过内核来监视一个由多个文件描述符(fd)组成的数组. 当select返回后, 数组中就绪的文件描述符会被内核修改标记位, 进程便可以通过遍历数组, 来获得这些文件描述符并从而进行后续的读写操作

      2. 进程会指定内核监听哪些文件描述符的哪些事件, 当没文件描述符所监听的事件发生时, 进程被阻塞, 当一个或者多个文件描述符事件发生时, 进程则被唤醒

      3. 文件描述符数组: 位图(bitmap), 最大一般为1024位, 即最大可监听1024个文件描述符. 当有事件发生时, 对应的位会被标记为1. 当下一次监听时, 位图需全部置为0

    3. select被调用时的过程

      1. 上下文切换, 将用户态切换为内核态, 并将fd数组从用户空间复制到内核空间

      2. 内核遍历fd数组, 查看这些文件描述符对应事件是否发生. 如果这些文件描述符无对应事件发生, 进程将阻塞. 当设备驱动产生中断或者timeout时间后, 进程唤醒并再次进行遍历. 持续该过程直至有事件发生

      3. 内核标记发生事件的fd, 并将fd从内核空间复制到用户空间, 并从内核态切换为用户态

    4. select缺点

      1. 单个进程能够监视的fd数量存在限制, 在linux上为1024

      2. 当fd数组很大时, 因每次调用select都需要把fd数组从用户空间拷贝到内核空间, 所以开销很大

      3. 当fd数组很大时, 内核对fd数组的遍历浪费时间

      4. 下一次监听时, 位图需全部置为0

  2. poll

    1. 原理

      1. 结构

             struct pollfd {
               int fd;  # 文件描述符
               short events;  # 该文件描述符注册的事件集合
               short revents;  # 该文件描述符事件状态发生变化的事件集合
             }

         

      2. poll通过内核监视多个pollfd组成的数组, 当任一监听的pollfd变化时, revents会被标记并返回. 且下一次监听时, events不用重新置位. poll本质上和select无区别, 只是没有最大连接数的限制, 原因是它基于链表存储

    2. poll缺点

      1. 当fd数组很大时, 因为每次调用poll都需要把pollfd数组从用户空间拷贝到内核空间, 所以开销很大

      2. 当fd数组很大时, 内核对pollfd数组的遍历浪费时间

  3. epoll

    1. 在freeBSD系统上没有epoll, 请使用kqueue. kqueue与epoll的不同之处: kqueue的一个fd的read/write事件需要分开注册, 而epoll则是一个fd一次注册read/write事件

    2. 结构

      struct epollfd {
      ​	int fd;  # 文件描述符
      ​	short events;  # 该文件描述符注册的事件集合
      }

       

    3. 函数

      1. epoll_create: 内核空间中建立红黑树, 双向就绪链表

      2. epoll_ctl

        1. 向内核空间的红黑树中塞入epollfd

        2. 为该事件文件描述符注册一个回调函数. 当该事件文件描述符监听的事件就绪后, 该回调函数会把就绪的epollfd加入到双向就绪链表中

      3. epoll_wait: 检查双向就绪链表中是否有元素, 没有就睡眠, 有就将epollfd从内核空间拷贝至用户空间, 并返回

    4. 高效理由

      1. 对于select缺点一, epoll所支持的最大fd数量是系统中可打开文件的最大数目, 一般该数目和内存有较大关系

      2. 对于select缺点二, epoll_ctl会将epollfd从用户空间拷贝到内核空间, 之后每次调用epoll_wait只是检查就绪链表, 返回已就绪的, 不像select, poll返回所有的

      3. 对于select缺点三, 因为注册了回调函数, 故不需要内核遍历

    5. 通知模型

      1. 水平触发(LT)

        1. 文件描述符上有数据变化时, 触发一个事件. 可以只读该事件有关的一部分数据, 未读完则下次检查时会再触发事件来通知

        2. 优点是稳定可靠, 缺点是当就绪的文件描述符过多时效率低

      2. 边缘触发(ET)

        1. 文件描述符上有数据变化时, 触发一个事件. 需要一次读完该事件有关的数据, 否则之后不会再触发事件来通知

        2. 优点是高效, 缺点是不可靠, 实现复杂

      3. select, poll仅支持水平触发; epoll支持水平触发和边缘触发, 默认采用水平触发; nginx采用的是边缘触发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值