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操作各阶段的流程可以参考下图:

View Mermaid diagram code
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

iomodel_comparison.webp

Blocking & Non Blocking I/O

阻塞 IO 是最通用的 IO 类型。使用这种模型进行数据接收的时候,在数据没有到之前程序会一直等待。
而当执行非阻塞 IO 时,如果数据已经准备好,操作会立即完成;如果未准备好(对于TCP套接字即⾄少有⼀个字节的数据可读,对于UDP套接字即有⼀个完整的数据报可读)
,不要阻塞等待,而是返回EWOULDBLOCK错误。应用程序可以基于这种错误进行处理,例如继续轮询(polling)等待数据就绪

iomodel_blocking_io.webp
iomodel_nio.webp
Warning

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

I/O Multiplexing

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

它们遵循相似的工作原理:应用程序开一个线程或进程,调用系统调用,例如 select,来监视多个文件描述符, 之后线程/进程会阻塞在 select 这个调用上,等待某个文件描述符状态改变(比如有数据可读、可写或发生异常),当文件描述符状态变为可读时,调用 recvfrom 把读到的数据复制到用户进程缓冲区

I/O Multiplexing vs Blocking I/O

项目阻塞 I/OI/O 复用(select)
系统调用次数1 次,直接调用recvfrom2 次先调用select 再调用 recvfrom

在处理单个文件描述符时,select 反而是“额外绕了一圈”, 但 select 的优势是可以等待多个描述符就绪,与之相似的是[[IO多路复用#多线程/进程 + Blocking I/O|多线程/进程 + Blocking I/O]]

20250717205411720.webp

三个函数在实现上有较大差异,此处暂不展开,执行下面命令查看Linux Programmer’s Manual

1
2
3
man select
man poll
man epoll

Signal Driven I/O

信号驱动的 IO 在进程开始的时候注册一个信号处理的回调函数,进程继续执行。当数据到来时,通知目标进程进行 IO 操作(signal handler)

Asynchronous I/O

Asynchronous: 两个层面的“异步”

“异步”这个词在技术讨论中常被用于描述两个不同层面的概念,这容易引起混淆:

  1. 操作系统层面的 I/O 模型:例如本文介绍的AIO。在此模型中,从数据准备数据复制的整个过程都由内核完成,应用只需发起请求,然后等待内核的完成信号即可。

  2. 应用程序层面的编程模型:框架提供的API是异步的,但框架所调用的操作系统接口是同步非阻塞。

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

iomodel_asyncio

Ref

怎样理解阻塞非阻塞与同步异步的区别? - 学刑法的程序员的回答 - 知乎

作者

GnixAij

发布于

2025-06-24

更新于

2025-07-18

许可协议

评论