跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

Linux 五种 IO 模型

Linux IO 模型涵盖阻塞、非阻塞、信号驱动、多路转接及异步 IO 五种方式。其本质是等待与拷贝,优化核心在于减少等待耗时。阻塞 IO 为默认模式,数据未就绪时进程挂起;非阻塞 IO 返回错误码供用户轮询,避免阻塞但消耗 CPU;信号驱动 IO 利用 SIGIO 通知应用;多路转接接口如 epoll 可同时监控多个文件描述符;异步 IO 由内核完成拷贝并通知,无需进程参与。区分同步与异步的关键在于进程是否主动参与 IO 过程。

DevStack发布于 2026/3/15更新于 2026/10/25103 浏览
Linux 五种 IO 模型

五种 IO 模型

1. IO 的本质

IO 的本质就是等 + 拷贝。

  • 等:等待 IO 条件就绪,比如 recv 函数,当输入缓冲区中没有数据时阻塞等待。
  • 拷贝:当 IO 条件就绪后,将数据拷贝到内存或外设。

任何 IO 的过程,都包含'等'和'拷贝'这两个步骤,但在实际的应用场景中'等'消耗的时间往往比'拷贝'消耗的时间多,因此要让 IO 变得高效,最核心的办法就是尽量减少'等'的时间。

2. 五种 IO 模型

2.1 阻塞 IO

阻塞 IO 就是在数据准备好之前,系统调用会一直等待。

image-20240811103651386

阻塞 IO 是最常见的 IO 模型,所有的套接字,默认都是阻塞方式。

比如当调用 recv 函数从某个套接字上读取数据时,可能底层数据还没有准备好,此时就需要等待数据就绪,当数据就绪后再将数据从内核拷贝到用户空间,最后 recv 函数才会返回。

以阻塞方式进行 IO 操作的进程或线程,在'等'和'拷贝'期间都不会返回,在用户看来就像是阻塞住了,因此我们称之为阻塞 IO。

2.2 非阻塞 IO

非阻塞 IO 就是,如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回 EWOULDBLOCK 错误码。

image-20240811104148905

非阻塞 IO 往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询。这对 CPU 来说是较大的浪费,一般只有特定场景下才使用。

比如当调用 recvfrom 函数以非阻塞方式从某个套接字上读取数据时,如果底层数据还没有准备好,那么 recvfrom 函数会立马错误返回,而不会让该进程或线程进行阻塞等待。

阻塞 IO 和非阻塞 IO 的区别在于,阻塞 IO 当数据没有就绪时,后续检测数据是否就绪的工作是由操作系统发起的,而非阻塞 IO 当数据没有就绪时,后续检测数据是否就绪的工作是由用户发起的。

实现方式

实现非阻塞式 IO 的方式有很多,包括可以设置 send、recv 函数的 flags 参数为 MSG_DONTWAIT,但我们最推荐的还是一劳永逸的做法,比如以下两种方式。

(1)在打开文件时设置非阻塞读取

打开文件时默认都是以阻塞的方式打开的,如果要以非阻塞的方式打开某个文件,需要在使用 open 函数打开文件时携带 O_NONBLOCK 或 O_NDELAY 选项,此时就能够以非阻塞的方式打开文件。

(2)将已经打开的某个文件或套接字设置为非阻塞读取

需要用到 fcntl 函数:

int fcntl(int fd,  cmd, ... );
int
/* arg */

参数说明:

  • fd:已经打开的文件描述符。
  • cmd:需要进行的操作。
  • …:可变参数,传入的 cmd 值不同,后面追加的参数也不同,cmd 取值如下:
    • 复制一个现有的描述符(cmd=F_DUPFD)。
    • 获得/设置文件描述符标记(cmd=F_GETFD 或 F_SETFD)。
    • 获得/设置文件状态标记(cmd=F_GETFL 或 F_SETFL)。
    • 获得/设置异步 I/O 所有权(cmd=F_GETOWN 或 F_SETOWN)。
    • 获得/设置记录锁(cmd=F_GETLK, F_SETLK 或 F_SETLKW)。

返回值说明:

  • 如果函数调用成功,则返回值取决于具体进行的操作。
  • 如果函数调用失败,则返回 -1,同时错误码会被设置。

(3)封装 SetNonBlock 函数

我们可以封装一个函数,该函数就用于将指定的文件描述符设置为非阻塞状态。

  1. 先调用 fcntl 函数获取该文件描述符对应的文件状态标记(这是一个位图),此时调用 fcntl 函数时传入的 cmd 值为 F_GETFL。
  2. 在获取到的文件状态标记上添加非阻塞标记 O_NONBLOCK,再次调用 fcntl 函数对文件状态标记进行设置,此时调用 fcntl 函数时传入的 cmd 值为 F_SETFL。

代码如下:

bool SetNonBlock(int fd) {
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0){
        std::cerr << "fcntl error" << std::endl;
        return false;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
    return true;
}

需要注意的是,当 read 函数以非阻塞方式读取标准输入时,如果底层数据不就绪,那么 read 函数就会立即返回,但当底层数据不就绪时,read 函数是以出错的形式返回的,此时的错误码会被设置为 EAGAIN 或 EWOULDBLOCK。

因此在以非阻塞方式读取数据时,如果调用 read 函数时得到的返回值是 -1,此时还需要通过错误码进一步进行判断,如果错误码的值是 EAGAIN 或 EWOULDBLOCK,说明本次调用 read 函数出错是因为底层数据还没有就绪,因此后续还应该继续调用 read 函数进行轮询检测数据是否就绪,当数据继续时再进行数据的读取。

此外,调用 read 函数在读取到数据之前可能会被其他信号中断,此时 read 函数也会以出错的形式返回,此时的错误码会被设置为 EINTR,此时应该重新执行 read 函数进行数据的读取。

因此在以非阻塞的方式读取数据时,如果调用 read 函数读取到的返回值为 -1,此时并不应该直接认为 read 函数在底层读取数据时出错了,而应该继续判断错误码,如果错误码的值为 EAGAIN、EWOULDBLOCK 或 EINTR 则应该继续调用 read 函数再次进行读取。

2.3 信号驱动 IO

信号驱动 IO 就是当内核将数据准备好的时候,使用 SIGIO 信号通知应用程序进行 IO 操作。

image-20240811104229202

当底层数据就绪的时候会向当前进程或线程递交 SIGIO 信号,因此可以通过 signal 或 sigaction 函数将 SIGIO 的信号处理程序自定义为需要进行的 IO 操作,当底层数据就绪时就会自动执行对应的 IO 操作。

信号的产生是异步的,但信号驱动 IO 是同步 IO 的一种。我们说信号的产生异步的,因为信号在任何时刻都可能产生。但信号驱动 IO 是同步 IO 的一种,因为当底层数据就绪时,当前进程或线程需要停下正在做的事情,转而进行数据的拷贝操作,因此当前进程或线程仍然需要参与 IO 过程。判断一个 IO 过程是同步的还是异步的,本质就是看当前进程或线程是否需要参与 IO 过程,如果要参与那就是同步 IO,否则就是异步 IO。

2.4 多路转接

IO 多路转接也叫做 IO 多路复用,能够同时等待多个文件描述符的就绪状态。

image-20240811104453647

IO 多路转接与阻塞 IO 的区别在于:IO 多路转接能够同时等待多个文件描述符的就绪状态。

  • 阻塞 IO 一次只能'等'一个文件描述符上的数据或空间就绪,这样 IO 效率太低了。
  • 因此系统为我们提供了三组接口,分别叫做 select、poll 和 epoll,这些接口的核心工作就是'等',我们可以将所有'等'的工作都交给这些多路转接接口。
  • 因为这些多路转接接口是一次'等'多个文件描述符的,因此能够将**'等'的时间进行重叠**,当数据就绪后再调用对应的 recv 等函数进行数据的拷贝,此时这些函数就能够直接进行拷贝,而不需要进行'等'操作了。

2.5 异步 IO

异步 IO 就是由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。

image-20240811105028740

异步 IO 不需要你进行'等'和'拷贝'的操作,这两个动作都由操作系统来完成,你要做的只是发起 IO,当 IO 完成后操作系统会通知应用程序。

使用异步 IO 可能会造成操作系统压力大、编程难度、程序调试难度大、资源竞争和数据一致性问题,所以一般不适用异步 IO。

3. 进程同步与 IO 同步

在多进程和多线程当中有同步与互斥的概念,但是这里的 IO 同步和进程或线程之间的同步是完全不相干的概念。

  • 进程/线程同步指的是,在保证数据安全的前提下,让进程/线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,谈论的是进程/线程间的一种工作关系。
  • 而同步 IO 指的是进程/线程与操作系统之间的关系,谈论的是进程/线程是否需要主动参与 IO 过程。

目录

  1. 五种 IO 模型
  2. 1. IO 的本质
  3. 2. 五种 IO 模型
  4. 2.1 阻塞 IO
  5. 2.2 非阻塞 IO
  6. 实现方式
  7. 2.3 信号驱动 IO
  8. 2.4 多路转接
  9. 2.5 异步 IO
  10. 3. 进程同步与 IO 同步

更多推荐文章

查看全部
  • VS Code 禁用 GitHub Copilot 实用指南
  • C++ 继承机制详解
  • C++ 异常处理机制深度解析
  • 认识 Java 中的锁升级机制
  • AI 绘画工具背后的视觉技术:Stable Diffusion 解析
  • OpenClaw 智能体框架入门:环境部署与模型配置指南
  • Linux 信号量详解与基于环形队列的生产消费模型实现
  • OpenClaw 清理 Skill 实战:基于 Rust+Tauri 构建安全沙箱
  • RoboMME:机器人通用策略的记忆基准测试与理解
  • ImageNet 图像识别技术原理与应用
  • Pi0 机器人大模型在昇腾 A2 上的部署与性能测评
  • DashDot 结合 cpolar 实现服务器轻量级远程监控指南
  • Flutter 三方库 huggingface_client 的鸿蒙化适配指南
  • 基于强化学习的无人机端到端飞行控制算法开发
  • OpenNana 提示词图库:300+ AI 灵感资源与使用指南
  • 基于 Docker 的 Web 应用一键打包为 Android APK 方案
  • Web 前端核心 API 入门:变量声明、DOM 操作与定时器
  • Toonflow AI 短剧工厂:一站式 AI 短剧创作平台
  • Python 实现 GitHub 热门项目自动挖掘与分析 AI Agent
  • URDF 与 RViz2:机器人建模基础与可视化实践

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online