unix IO模型

首先I/O模型分为操作系统与设备之间的IO,还有各进程与操作系统之间的I/O模型。

操作系统与设备之间的IO

(1)轮询方式

CPU主动在各种设备中轮询检查状态,有数据就IO。

(2)中断方式

设备有数据的时候,发出中断,由CPU决定要不要响应中断,然后中断,去处理设备的IO。CPU不用经常轮询设备状态。被动接收中断就行。

(3)DMA直接存储器访问方式

如果1个字节的数据中断一次,传1KB的数据得中断1024次,太浪费CPU时间,于是有了DMA方式,CPU只需要把开头和结束的地址告诉DMA,中间由DMA完成数据IO。CPU从字节干预解放到数据块的干预。

(4)通道控制方式

DMA方式只能控制一个设备的一块数据,多块数据还是要CPU干预多次。于是有了通道来控制IO,它比DMA更强大,能控制多块数据,多个设备的IO,更加解放了CPU参与IO过程。

上面只是简单谈了操作系统与设备之间的IO方式,详细看b站王道操作系统第五章第三节的内容

进程与操作系统之间的I/O

一个某进程所要求的输入操作通常包括两个阶段:

  • 等待数据准备好
  • 从内核向进程复制数据

对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。

Unix 下有五种 I/O 模型:

  • 阻塞式 I/O
  • 非阻塞式 I/O
  • I/O 复用(select 和 poll)
  • 信号驱动式 I/O(SIGIO)
  • 异步 I/O(AIO)

阻塞式 I/O

应用进程被阻塞,直到数据复制到应用进程缓冲区中才返回。

应该注意到,在阻塞的过程中,其它程序还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其他程序还可以执行,因此不消耗 CPU 时间,这种模型的执行效率会比较高。只是进程阻塞,等待着数据!

![image-20221112204105800](unix Io模型/image-20221112204105800.png)

非阻塞式 I/O

应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成,这种方式称为轮询(polling)。

由于 CPU 要处理更多的系统调用,因此这种模型是比较低效的。

​ ![image-20221112204309220](unix Io模型/image-20221112204309220.png)

I/O 复用

使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读,这一过程会被阻塞,当某一个套接字可读时返回。之后再使用 recvfrom 把数据从内核复制到进程中。

它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O。难道是select管理多个io,那个io有数据了,就通知要这个数据的进程)

![image-20221112204332249](unix Io模型/image-20221112204332249.png)

信号驱动 I/O

应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。

相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高。

![image-20221112204353277](unix Io模型/image-20221112204353277.png)

异步 I/O

进行 aio_read 系统调用会立即返回,应用进程继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。

异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。

![image-20221112204418036](unix Io模型/image-20221112204418036.png)

I/O 模型比较

同步 I/O 与异步 I/O

  • 同步 I/O: 应用进程在调用 recvfrom 操作时会阻塞。
  • 异步 I/O: 不会阻塞。

阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O,虽然非阻塞式 I/O 和信号驱动 I/O 在等待数据阶段不会阻塞,但是在之后的将数据从内核复制到应用进程这个操作会阻塞。

五大 I/O 模型比较

前四种 I/O 模型的主要区别在于第一个阶段,而第二个阶段是一样的: 将数据从内核复制到应用进程过程中,应用进程会被阻塞。

![image-20221112204504616](unix Io模型/image-20221112204504616.png)

select,epoll,kqueue 原理

已知的情况

内核中有注册 socket 的 IO 中断处理的回掉函数,有 IO 了会回调该函数。

在Unix/Linux系统下,一个socket句柄,可以看做是一个文件,在socket上收发数据,相当于对一个文件进行读写,所以一个socket句柄,通常也用表示文件句柄的fd来表示。

select

select 管理多个 socket,select 收到一个来自网卡 IO 中断就返回,不知道这个中断对应是哪个 socket fd 的。需要用户线程遍历判断。

epoll

epoll 收到一个 IO 中断,会去查找这个中断对应哪个 socket fd。

epoll 中建立一个红黑树(平衡二叉树的一种),红黑树查找很高效。

用户注册感兴趣的 socket 事件,就是把这个 socket fd 插入到红黑树中,用中断号做key,可以理解为(中断号,socket fd)的二元组。

用户移除事件就是,删除树上的某个节点。

然后收到一个IO中断,epoll 把网卡数据拷贝到内核cache,根据中断号在红黑树中查找对应的 fd,把 fd 加入到就绪链表中,准备返回给用户线程。用户直接得到就绪的 fd。

kqueue

收到 socket IO 中断去哈希表中查找对应的 socket fd,再把它放到一个链表里,返回。

用户注册一个感兴趣的事件,就是往哈希表中添加一个 fd


unix IO模型
https://lililib.github.io/unix Io模型/
作者
煨酒小童
发布于
2022年10月8日
许可协议