io深层理解
1.内核态
一个进程会涉及多文件的修改,比如说。那么在内核态就会维护一个表,这个表叫文件描述符bitmap,这个表会传递给内核态,当然肯定传递的是地址咯!
- 用户态关心的文件,执行select,等待网络io回来。。。
- 那么用户态将关心的fd_map 取地址传递给内核,内核扫描这个fd_map,当然select的是一个最大为1024的bit数组,这个fd_map,对应索引为fd_id的位置上,就会置为1,传递给内核后,内核空间会轮训这个map,判断是否有数据到来,并从网卡拷贝到了内核的数据缓存队列。
2.用户态
- 用户态执行select之后,会阻塞,等待数据到来,注意此时进程内部的线程是挂起的,进程也会挂起,也就是说,目前的线程是服从操作系统的调度,可以干其他任务的。而当数据到来之后,操作系统恢复上下文,恢复寄存器变量等,继续执行业务代码。
图文说明:
3. select IO多路复用执行原理
- 将当前进程的所有文件描述符,一次性地从用户态拷贝到内核态
- 在内核中快速无差别地遍历每个id,判断是否有数据到来
- 将所有fd状态,从内核态拷贝到用户态,并返回已就绪的fd的个数
- 在用户态遍历判断具体哪个fd已就绪,然后进行相应的事件处理
4. select io多路复用限制和不足
- 文件描述符表为bitmap,有长度限制1024的限制
- fdset无法做到重用,每次循环必须重新创建
- 频繁的用户态和内核态的拷贝,性能开销很大
- 需要对文件描述符进行遍历,O(n)的轮训时间复杂度
poll模式下:
poll 模式下
- 有一个结构体:pollfd文件描述符结构体
- 创建socket服务端
- 维护一个pollfd数组
- 用户态会给polldf数组的fd设置上对应的文件描述符id
- 并设置上关注的监听事件
- 然后发起poll调用,这里就会阻塞
- 当数据到来之后,数据从内核环形缓存区根据文件描述符id拷贝到其数据接收队列
- 返回pollfd数组,用户态遍历这个数组,找到revents为1的,对数据做处理,并将这个状态设为0
5. poll IO多路复用执行原理
- 将当前进程的所有文件描述符,一次性从用户态拷贝内核态
- 在内核中快速无差别地遍历每个fd,判断是否有数据到达
- 将所有fd状态,从内核态拷贝到用户态,并返回已就绪fd的个数
- 在用户态遍历判断具体哪个fd就绪,然后进行相应事件处理
6. poll io多路复用限制和不足
- poll采用的是pollfd结构数组,解决了select中的1024个文件描述符限制
- 但是仍然存在频繁地用户态和内核态拷贝,性能开销较大
- 需要对文件描述符进行遍历,O(n)的轮询时间复杂度。
epoll 模式:
7. epoll IO多路复用执行原理(linux2.6诞生)
- 用户态有:epoll_create、epoll_ctl、epoll_wait三个方法
- epoll_create 会创建eventpoll结构:包含rdyList、rbr、wq(已就绪文件描述符列表、双链表)、红黑树 等待用户进程放进来所有socket连接、等待进程和回调函数;
- epoll_ctl: 将客户端封住成为一个对象epitem,会保存到红黑树中去
- epoll_wait:阻塞方法,会去监控,当发送数据过去之后,会存储到一个已就绪双向链表里面,内核态从阻塞进程中查找对应文件描述符的进程与回调函数,唤醒进程并执行回调函数。
8. epoll 用空间换时间的策略
在epoll_ctl函数中,为每个文件描述符都制定了回调函数,基于回调函数把就绪事件放到就绪队列中,因此把时间复杂度从O(n)降到了O(0)
2. 只需在epoll_ctl时传递一次文件描述符,epoll_wait不需要再次传递文件描述符
3. epoll基于红黑树 + 双向链表存储事件,没有最大连接的限制,不存在C10k的问题。