Linux的五种IO模型

众所周知,出于对 OS 安全性的考虑,用户进程是不能直接操作 I/O 设备的。必须通过系统调用请求操作系统内核来协助完成 I/O 动作。

下图展示了 Linux I/O 的过程。
在这里插入图片描述
操作系统内核收到用户进程发起的请求后,从 I/O 设备读取数据到 kernel buffer 中,再将 buffer 中的数据拷贝到用户进程的地址空间,用户进程获取到数据后返回给客户端。

在 I/O 过程中,对于输入操作通常有两个不同的阶段:

  1. 等待数据准备好
  2. 将数据从内核缓冲区拷贝到用户进程

根据这两个阶段等待方式的不同,可以将 Linux I/O 分为 5 种模式:

  1. blocking I/O,阻塞式 I/O
  2. nonblocking I/O,非阻塞式 I/O
  3. I/O multiplexing(select and poll),I/O 多路复用
  4. signal driven I/O(SIGIO),信号驱动 I/O
  5. asynchronous I/O(the POSIX aio_functions),异步 I/O

对于 Socket 上的输入操作:

  1. 第 1 步通常是等待网络上的数据到达。当数据包到达时,它被复制到内核的缓冲区中。
  2. 第 2 步是从内核缓冲区复制数据到应用程序缓冲区。

下面详细介绍 Linux 中的 5 种 I/O 模式。
1. Blocking I/O

默认情况下,所有的 Socket 都是阻塞式的。下图展示了一个基于 UDP 的网络数据获取流程。

用户进程调用了 recvfrom 系统调用,此后一直处于等待状态,直到数据包到达并被拷贝到应用程序缓冲区,或者发生 error 才返回。整个过程从开始 recvfrom 调用到它返回一直处于阻塞状态。当 recvfrom 调用返回后,应用进程才能处理数据。
在这里插入图片描述

2. Nonblocking I/O

可以设置 Socket 为非阻塞模式。这种设置相当于告诉内核“当 I/O 操作时,如果请求是不可能完成的,不要把进程进入睡眠状态,返回一个错误即可“。下图展示了整个流程:在前三次调用 recvfrom 系统调用时,没有就绪的数据返回,所以内核立即返回 EWOULDBLOCK 错误。第四次调用 recvfrom 时,数据报已经准备好,它被复制到应用程序缓冲区中,然后 recvfrom 成功返回。最后应用进程对数据进行处理。当应用程序在一个非阻塞描述符上循环调用 recvfrom 系统调用时,这种方式也被称为轮询。应用程序不断轮询内核,以查看是否有某些操作准备好了。很明显,这通常会浪费 CPU 时间,但这种模式偶尔也会被使用。通常在专门用于一个功能的系统上使用。
在这里插入图片描述

3. I/O Multiplexing

I/O 多路复用通常使用 select 或者 poll 或者 epoll 系统调用。这种方式下的阻塞只是被 select 或者 poll 或者 epoll 系统调用阻塞,而不会阻塞实际的 I/O 系统调用(即数据输入、输出不会被阻塞)。下图展示了整个过程。当调用 select 时,应用进程被阻塞。同时,系统内核会“监视”所有 select 负责的 Socket。只要其中有 1 个 Socket 的数据准备好了,select 调用就返回。然后调用 recvfrom 将数据报复制到应用程序缓冲区,最后返回给用户进程。

乍一看,这种方式和 blocking I/O 相比似乎更差,因为整个过程产生了 2 次系统调用,select 和 recvfrom。但是使用 select 的好处是可以同时等待多个描述符准备好。换句话说可以同时“聆听”多个 Socket 通道,同时处理多个连接。select 的优势不是对于单个连接处理得更快,而是能同时处理更多的连接。这和多线程阻塞式 I/O 有点类似。只不过后者是使用多个线程(每个文件描述符对应一个线程)来处理 I/O,每个线程都可以自由地调用阻塞式系统调用,比如 recvfrom。我们知道线程多了会带来上下文切换的开销,因此未必优于 select 方式。在前面 Java NIO 的例子中,我们已经体会到了 selector 带来的性能提升。
在这里插入图片描述
Linux 内核将所有外部设备都当成一个个文件来操作。我们对文件的读写都通过调用内核提供的系统调用;内核给我们返回一个文件描述符(file descriptor)。而对一个 Socket 的读写也会有相应的描述符,称为 socketfd。应用进程对文件的读写通过对 fd 的读写完成。

4. Signal Driven I/O

信号驱动方式就是等数据准备好后,由内核发出 SIGIO 信号通知应用进程。示意图如下:
在这里插入图片描述
应用进程通过 sigaction 系统调用建立起 SIGIO 信号处理通道,然后此系统调用就返回,不阻塞。当数据准备好后,内核会产生一个 SIGIO 信号通知到应用进程。此时既可以使用 SIGIO 信号处理器通过 recvfrom 系统调用读取数据,然后通知应用进程数据准备好了,可以处理了;也可以直接通知应用进程读取数据。不管使用何种方式,好处都是应用进程不会阻塞,可以继续执行,只要等待信号通知数据准备好被处理了、数据准备好被读取了。

5. asynchronous I/O

异步 I/O 是由 POSIX 规范定义的。和信号驱动 I/O 模型的区别是前者内核告诉我们何时可以开始一个 I/O 操作,而后者内核会告诉我们一个 I/O 操作何时完成。示意图如下:
在这里插入图片描述

当用户进程发起系统调用后会立刻返回,并把所有的任务都交给内核去完成,不会被阻塞等待 I/O 完成。内核完成之后,只需返回一个信号告诉用户进程已经完成就可以了。

五种 I/O 模式可以从同步、异步,阻塞、非阻塞两个维度来划分:
在这里插入图片描述


引用:https://zhuanlan.zhihu.com/p/543661648

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2023-12-14 10:52:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-14 10:52:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-14 10:52:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-14 10:52:04       20 阅读

热门阅读

  1. 构造列表存储1000以内的素数

    2023-12-14 10:52:04       36 阅读
  2. 【Django-03】模型常用的增删改查

    2023-12-14 10:52:04       32 阅读
  3. 【Git使用总结】

    2023-12-14 10:52:04       39 阅读
  4. debian12 最小化安装桌面 i3wm

    2023-12-14 10:52:04       36 阅读
  5. 算法训练营Day15(二叉树)

    2023-12-14 10:52:04       42 阅读
  6. 209.长度最小的子数组

    2023-12-14 10:52:04       37 阅读
  7. 质数的求解方法

    2023-12-14 10:52:04       44 阅读
  8. Linux--绝对路径和相对路径

    2023-12-14 10:52:04       38 阅读
  9. linux设置环境变量

    2023-12-14 10:52:04       41 阅读
  10. Golang模板语法

    2023-12-14 10:52:04       39 阅读