IO过程
- IO调用请求
用户进程向操作系统发起IO调用请求
- 数据准备阶段(DMA拷贝)
操作系统准备数据(DMA设备代替了CPU进行拷贝),把IO外部设备的数据,加载到内核缓冲区
- 数据复制阶段 (CPU拷贝)
操作系统拷贝数据,将内核缓存区的数据(DMA缓冲区)拷贝到用户进程缓冲区(CPU拷贝)
BIO 阻塞模型
IO请求,数据准备阶段和数据复制阶段都是同步阻塞
NIO 非阻塞模型
- 数据准备阶段——同步非阻塞
频繁的通过用户进程发起数据状态询问,引入用户态和内核态的状态切换
- 数据复制阶段——同步阻塞
数据复制阶段占用CPU资源,从内核缓冲区复制到用户空间
IO多路复用模型(JAVA NIO)(异步阻塞IO模型)
不再通过用户进程发起多次数据状态询问,而是调用一次内核函数,让内核去遍历询问,减少了内核切换
select、poll和epoll区别
| select | poll | epoll | |
|---|---|---|---|
| 底层数据结构 | 数组 | 链表 | 红黑树和双链表 |
| 获取就绪的fd | 遍历 | 遍历 | 事件回调 |
| 事件复杂度 | O(n) | O(n) | O(1) |
| 最大连接数 | 1024 | 无限制 | 无限制 |
| fd数据拷贝 | 每次调用select,需要将fd数据从用户空间拷贝到内核空间 | 每次调用poll,需要将fd数据从用户空间拷贝到内核空间 | 使用内存映射(mmap),不需要从用户空间频繁拷贝fd数据到内核空间 |
图5 IO多路复用
如图5所示,通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。
信号驱动模型
- 数据准备阶段——异步非阻塞
数据准备好之后,通过信号通知用户进程
- 数据拷贝阶段——同步阻塞
数据准备好之后的拷贝过程,还是同步阻塞
AIO异步IO模型
用户进程发起IO调用请求后,数据准备以及数据拷贝全部完成后,通知用户进行。完全异步非阻塞
参考