IO Models

IO Models

I/O Overview

I/O即输入输出(Input/Output),核心目标是实现数据的交换与控制。从本质上来说,IO操作主要涉及两个阶段:

  1. 数据准备阶段:等待数据准备好(如等待数据从网络到达)
  2. 数据复制阶段:将数据从kernel buffer复制到user buffer
Kernel/User Space

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

IO模式

Blocking & Non Blocking I/O

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

Warning

当一个应用程序使用了非阻塞模式的套接字,应用程序需要不停的 polling(轮询) 内核来检查是否 I/O 操作已经就绪。这将是一个极浪费 CPU 资源的操作。因此这种I/O模型在使用中不是很普遍。

I/O Multiplexing

操作系统提供了一系列系统调用(syscall),例如 select()poll()epoll()

它们遵循相似的工作原理:应用程序开一个线程或进程,选用syscall来监视多个文件描述符, 调用syscall后线程/进程会阻塞在这个调用上,直到某个文件描述符状态改变(比如有数据可读、可写或发生异常)

io multiplexing

三个函数在实现上有较大差异,此处暂不展开,执行下面命令查看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 则在数据复制完成的时候才发送信号通知注册的信号处理函数。

Asynchronous

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

作者

GnixAij

发布于

2025-06-24

更新于

2025-06-27

许可协议

评论