☰
【Linux笔记】IO模型
2026/10/8 2:15:53 网站建设 项目流程

一、IO理解

1.1 什么是 IO

IO(Input/Output)指的是:计算机系统中数据的输入与输出操作。

在 Linux 中 "一切皆文件",网络套接字、磁盘文件、管道、终端设备等,都对应一个文件描述符(fd)

内核通过fd来统一管理所有 IO 资源。

1.2 IO 操作的两个阶段

一次 IO 读操作分为两个阶段:

阶段1:等待数据就绪 - 数据从网卡/磁盘到达内核缓冲区(kernel buffer) - 此时进程可能需要等待 阶段2:数据拷贝 - 内核将数据从内核缓冲区拷贝到用户进程缓冲区(user buffer) - 拷贝完成后返回

二、五种 IO 模型

2.1 阻塞 IO

用户进程 内核 | | |-- read(fd, buf) ----------->| | (进程阻塞) |-- 等待数据到达 | |-- 数据到达内核buffer | |-- 内核拷贝数据到用户buf |<------- 返回 --------------- | | |
  • 所有的 socket 默认都是阻塞模式

  • 在数据未就绪时,进程被挂起,无法做其他事

  • 两个阶段都会阻塞

2.2 非阻塞 IO ★

用户进程 内核 | | |-- read(fd, buf) ----------->| |<------- EAGAIN ------------ | (数据未就绪) | | | (做其他事或继续轮询) | | | |-- read(fd, buf) ----------->| |<------- 返回数据 ------------| (数据已就绪)
  • 通过 fcntl 设置 O_NONBLOCK 标志

  • 数据未就绪时立即返回 EAGAIN/EWOULDBLOCK

  • 需要不断轮询,浪费 CPU

2.3 信号驱动 IO

用户进程 内核 | | |-- 注册SIGIO信号处理函数 ----> | |-- fcntl设置属主和信号 ------->| | (做其他事) | | |-- 数据到达 |<------ SIGIO信号 ----------- | |-- read(fd, buf) ----------->| |<------- 返回数据 ----------- |
  • 内核在数据就绪时发送 SIGIO 信号通知进程

  • 进程收到信号后再调用 read 读取

  • 实际使用较少

2.4 IO 多路复用 ★

用户进程 内核 | | |-- select/poll/epoll_wait -->| | (阻塞在select上) |-- 监控多个fd | |-- 某个fd就绪 |<------- 返回就绪fd列表 ----- | | | |-- read(就绪的fd) ----------> | |<------- 返回数据 ----------- |
  • 用一个系统调用同时监控多个 fd

  • 当某个 fd 就绪时,再进行对应的读写

  • select / poll / epoll 三种实现

2.5 异步 IO

用户进程 内核 | | |-- aio_read / io_uring ----->| | (做其他事) |-- 等待数据到达 | |-- 数据拷贝到用户buf(内核完成) |<------ 完成通知/回调 ------- | | |
  • 真正的异步:发起请求后,内核完成全部工作(包括数据拷贝)后通知进程。

  • Linux 的实现:POSIX AIO、Linux AIO、io_uring

三、IO重要概念

3.1 阻塞与非阻塞

阻塞IO : 因IO条件不满足(数据从网卡/磁盘还未到达内核缓冲区),而阻塞卡住,直到IO条件就绪。 非阻塞IO : 采用轮询模式判断IO条件是否满足,若检测到IO条件不具备,就出错返回。

3.2 同步与异步

同步IO : 参与了IO的一个阶段或多个阶段过程 (等待数据 + 拷贝数据) 异步IO : 仅"发起IO" 或 "IO工作流"与当前工作流无关

四、非阻塞IO

4.1 fcntl 系统调用

#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );
  • 复制一个现有的描述符(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)。

4.2代码示例

设置非阻塞模式

#include <fcntl.h> #include <unistd.h> ​ // 获取当前文件状态标志 int flags = fcntl(fd, F_GETFL, 0); ​ // 设置非阻塞标志 fcntl(fd, F_SETFL, flags | O_NONBLOCK);

代码示例:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> ​ int main() { char buf[1024]; ssize_t n; ​ // 将标准输入设置为非阻塞 int flags = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); ​ printf("尝试读取(非阻塞模式)...\n"); ​ while (1) { n = read(STDIN_FILENO, buf, sizeof(buf) - 1); if (n > 0) { buf[n] = '\0'; printf("读到 %zd 字节: %s", n, buf); } else if (n == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { printf("数据未就绪,稍后再试...\n"); usleep(500000); // 500ms 后重试 } else { perror("read error"); break; } } else if (n == 0) { printf("EOF\n"); break; } } return 0; }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询