IO Models
I/O Overview
I/O即输入输出(Input/Output),核心目标是实现数据的交换与控制。从本质上来说,IO操作主要涉及两个阶段:
- 数据准备阶段:等待数据准备好(如等待数据从网络到达)
- 数据复制阶段:将数据从kernel buffer复制到user buffer
Kernel Space(内核空间)和User Space(用户空间)是操作系统中两个重要的内存区域。
内核空间是操作系统核心代码运行的区域,具有更高的权限,可以直接访问硬件资源;
而用户空间则是应用程序运行的区域,权限较低,不能直接访问硬件资源。通常需要通过系统调用执行特权操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────────────────────────┐
│ User Space 0x0000000000000000 - 0x00007FFFFFFFFFFF │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Stack Buffer│ │ Heap Buffer │ │ mmap Buffer │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Kernel Space 0xFFFF800000000000 - 0xFFFFFFFFFFFFFFFF │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Socket Buffer│ │ Page Cache │ │ DMA Buffer │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
基于两个阶段的处理方式差异,可以将I/O操作分为以下几种模型:
IO模型 | 数据准备阶段 | 数据复制阶段 |
---|---|---|
阻塞IO | 阻塞等待 | 同步复制 |
非阻塞IO | 非阻塞轮询 | 同步复制 |
IO多路复用 | 阻塞等待多个源 | 同步复制 |
信号驱动IO | 异步通知 | 同步复制 |
异步IO | 异步等待 | 异步复制 |
一次I/O操作各阶段的流程可以参考下图:
sequenceDiagram box User Space participant APP as Web Application participant FWK as Web Framework/System Libraries end participant SYS as 🔶 System Call Interface box Kernel Space participant KER as Kernel end participant HW as Physical Hardware APP->>FWK: 使用Web框架/库函提供的接口,进行IO操作 FWK->>SYS: 发起系统调用 Note over SYS: Context Switch (用户态→内核态) SYS->>KER: 内核处理逻辑 KER->>HW: 硬件操作 Note over KER: 内核等待I/O设备数据就绪 HW-->>KER: 数据就绪, 存储到内核缓冲区(Kernel Buffer) Note over KER: 数据由内核缓冲区复制到用户缓冲区(User Buffer) KER-->>SYS: 系统调用返回 Note over SYS: Context Switch (内核态→用户态) SYS-->>FWK: 返回I/O结果 FWK-->>APP: 返回业务结果
I/O Models

Blocking & Non Blocking I/O
阻塞 IO 是最通用的 IO 类型。使用这种模型进行数据接收的时候,在数据没有到之前程序会一直等待。
而当执行非阻塞 IO 时,如果数据已经准备好,操作会立即完成;否则会返回错误,而不阻塞程序。应用程序可以基于这种错误进行处理,或继续轮询等待数据就绪


当一个应用程序使用了非阻塞模式的套接字,应用程序需要不停的 polling(轮询) 内核来检查是否 I/O 操作已经就绪。这将是一个极浪费 CPU 资源的操作。因此这种I/O模型在使用中不是很普遍。
I/O Multiplexing
操作系统提供了一系列系统调用(syscall
),例如 select()
、poll()
和 epoll()
。
它们遵循相似的工作原理:应用程序开一个线程或进程,选用syscall来监视多个文件描述符, 调用syscall
后线程/进程会阻塞在这个调用上,直到某个文件描述符状态改变(比如有数据可读、可写或发生异常)

三个函数在实现上有较大差异,此处暂不展开,执行下面命令查看Linux Programmer’s Manual
1
2
3
man select
man poll
man epoll
Signal Driven I/O
信号驱动的 IO 在进程开始的时候注册一个信号处理的回调函数,进程继续执行。当数据到来时,通知目标进程进行 IO 操作(signal handler)

Asynchronous I/O
异步 IO 与前面的信号驱动 IO 相似,其区别在于信号驱动 IO 当数据到来的时候,使用信号通知注册的信号处理函数,而异步 IO 则在数据复制完成的时候才发送信号通知注册的信号处理函数。

通常一些 Web Framework 或 Libraries 会提供"异步"的接口,当我们说某个Web框架/Lib 提供了"异步API"时,通常指的是它提供了非阻塞的编程接口,让开发者可以编写异步风格的代码。考虑跨平台兼容性以及开发效率等问题,其底层实现往往是基于I/O多路复用的同步I/O模型。
IO Models