彻底理解 IO 多路复用实现机制( 三 )


(4)以tcp_poll为例 , 其核心实现就是__pollwait , 也就是上面注册的回调函数 。
(5)__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中 , 不同的设备有不同的等待队列 , 对于tcp_poll来说 , 其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了) 。 在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后 , 会唤醒设备等待队列上睡眠的进程 , 这时current便被唤醒了 。
(6)poll方法返回时会返回一个描述读写操作是否就绪的mask掩码 , 根据这个mask掩码给fd_set赋值 。
(7)如果遍历完所有的fd , 还没有返回一个可读写的mask掩码 , 则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠 。 当设备驱动发生自身资源可读写后 , 会唤醒其等待队列上睡眠的进程 。 如果超过一定的超时时间(schedule_timeout指定) , 还是没人唤醒 , 则调用select的进程会重新被唤醒获得CPU , 进而重新遍历fd , 判断有没有就绪的fd 。
(8)把fd_set从内核空间拷贝到用户空间 。
select函数接口#include #include #define FD_SETSIZE 1024#define NFDBITS (8 * sizeof(unsigned long))#define __FDSET_LONGS (FD_SETSIZE/NFDBITS)// 数据结构 (bitmap)typedef struct {unsigned long fds_bits[__FDSET_LONGS];} fd_set;// APIint select(int max_fd,fd_set *readset,fd_set *writeset,fd_set *exceptset,struct timeval *timeout)// 返回值就绪描述符的数目FD_ZERO(int fd, fd_set* fds)// 清空集合FD_SET(int fd, fd_set* fds)// 将给定的描述符加入集合FD_ISSET(int fd, fd_set* fds)// 判断指定描述符是否在集合中 FD_CLR(int fd, fd_set* fds)// 将给定的描述符从文件中删除select使用示例int main() {/** 这里进行一些初始化的设置 ,* 包括socket建立 , 地址的设置等,*/fd_set read_fs, write_fs;struct timeval timeout;int max = 0;// 用于记录最大的fd , 在轮询中时刻更新即可// 初始化比特位FD_ZERO(FD_ZERO(int nfds = 0; // 记录就绪的事件 , 可以减少遍历的次数while (1) {// 阻塞获取// 每次需要把fd从用户态拷贝到内核态nfds = select(max + 1,// 每次需要遍历所有fd , 判断有无读写事件发生for (int i = 0; i <= max++i) {if (i == listenfd) {--nfds;// 这里处理accept事件FD_SET(i, //将客户端socket加入到集合中}if (FD_ISSET(i,// 这里处理read事件}if (FD_ISSET(i,// 这里处理write事件}}}select缺点select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理 。 这样所带来的缺点是:

  • 单个进程所打开的FD是有限制的 , 通过 FD_SETSIZE 设置 , 默认1024 ;
  • 每次调用 select , 都需要把 fd 集合从用户态拷贝到内核态 , 这个开销在 fd 很多时会很大;
需要维护一个用来存放大量fd的数据结构 , 这样会使得用户空间和内核空间在传递该结构时复制开销大
  • 对 socket 扫描时是线性扫描 , 采用轮询的方法 , 效率较低(高并发)
当套接字比较多的时候 , 每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍 。 这会浪费很多CPU时间 。 如果能给套接字注册某个回调函数 , 当他们活跃时 , 自动完成相关操作 , 那就避免了轮询 , 这正是epoll与kqueue做的 。
pollpoll本质上和select没有区别 , 它将用户传入的数组拷贝到内核空间 , 然后查询每个fd对应的设备状态 ,但是它没有最大连接数的限制 , 原因是它是基于链表来存储的.
poll函数接口#include // 数据结构struct pollfd {int fd;// 需要监视的文件描述符short events;// 需要内核监视的事件short revents;// 实际发生的事件};// APIint poll(struct pollfd fds[], nfds_t nfds, int timeout);


推荐阅读