本文主要介绍在IO多路复用模型中, 第一阶段使用到的四个函数: select, poll, epoll, kqueue
-
select
-
select函数
-
定义
select用来监视多个文件描述符, 当文件描述符所对应事件状态不改变时, select会阻塞. 当某个文件描述符所对应事件状态改变后, select会返回
-
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
-
-
原理
-
select通过内核来监视一个由多个文件描述符(fd)组成的数组. 当select返回后, 数组中就绪的文件描述符会被内核修改标记位, 进程便可以通过遍历数组, 来获得这些文件描述符并从而进行后续的读写操作
-
进程会指定内核监听哪些文件描述符的哪些事件, 当没文件描述符所监听的事件发生时, 进程被阻塞, 当一个或者多个文件描述符事件发生时, 进程则被唤醒
-
文件描述符数组: 位图(bitmap), 最大一般为1024位, 即最大可监听1024个文件描述符. 当有事件发生时, 对应的位会被标记为1. 当下一次监听时, 位图需全部置为0
-
-
select被调用时的过程
-
上下文切换, 将用户态切换为内核态, 并将fd数组从用户空间复制到内核空间
-
内核遍历fd数组, 查看这些文件描述符对应事件是否发生. 如果这些文件描述符无对应事件发生, 进程将阻塞. 当设备驱动产生中断或者timeout时间后, 进程唤醒并再次进行遍历. 持续该过程直至有事件发生
-
内核标记发生事件的fd, 并将fd从内核空间复制到用户空间, 并从内核态切换为用户态
-
-
select缺点
-
单个进程能够监视的fd数量存在限制, 在linux上为1024
-
当fd数组很大时, 因每次调用select都需要把fd数组从用户空间拷贝到内核空间, 所以开销很大
-
当fd数组很大时, 内核对fd数组的遍历浪费时间
-
下一次监听时, 位图需全部置为0
-
-
-
poll
-
原理
-
结构
struct pollfd { int fd; # 文件描述符 short events; # 该文件描述符注册的事件集合 short revents; # 该文件描述符事件状态发生变化的事件集合 }
-
poll通过内核监视多个pollfd组成的数组, 当任一监听的pollfd变化时, revents会被标记并返回. 且下一次监听时, events不用重新置位. poll本质上和select无区别, 只是没有最大连接数的限制, 原因是它基于链表存储
-
-
poll缺点
-
当fd数组很大时, 因为每次调用poll都需要把pollfd数组从用户空间拷贝到内核空间, 所以开销很大
-
当fd数组很大时, 内核对pollfd数组的遍历浪费时间
-
-
-
epoll
-
在freeBSD系统上没有epoll, 请使用kqueue. kqueue与epoll的不同之处: kqueue的一个fd的read/write事件需要分开注册, 而epoll则是一个fd一次注册read/write事件
-
结构
struct epollfd { int fd; # 文件描述符 short events; # 该文件描述符注册的事件集合 }
-
函数
-
epoll_create: 内核空间中建立红黑树, 双向就绪链表
-
epoll_ctl
-
向内核空间的红黑树中塞入epollfd
-
为该事件文件描述符注册一个回调函数. 当该事件文件描述符监听的事件就绪后, 该回调函数会把就绪的epollfd加入到双向就绪链表中
-
-
epoll_wait: 检查双向就绪链表中是否有元素, 没有就睡眠, 有就将epollfd从内核空间拷贝至用户空间, 并返回
-
-
高效理由
-
对于select缺点一, epoll所支持的最大fd数量是系统中可打开文件的最大数目, 一般该数目和内存有较大关系
-
对于select缺点二, epoll_ctl会将epollfd从用户空间拷贝到内核空间, 之后每次调用epoll_wait只是检查就绪链表, 返回已就绪的, 不像select, poll返回所有的
-
对于select缺点三, 因为注册了回调函数, 故不需要内核遍历
-
-
通知模型
-
水平触发(LT)
-
文件描述符上有数据变化时, 触发一个事件. 可以只读该事件有关的一部分数据, 未读完则下次检查时会再触发事件来通知
-
优点是稳定可靠, 缺点是当就绪的文件描述符过多时效率低
-
-
边缘触发(ET)
-
文件描述符上有数据变化时, 触发一个事件. 需要一次读完该事件有关的数据, 否则之后不会再触发事件来通知
-
优点是高效, 缺点是不可靠, 实现复杂
-
-
select, poll仅支持水平触发; epoll支持水平触发和边缘触发, 默认采用水平触发; nginx采用的是边缘触发
-
-