同步阻塞IO

上下文切换过程:

  1. 用户态 → 内核态:调用 read() 系统调用
  2. 内核态:等待数据(进程挂起,不占用 CPU)
  3. 内核态:数据从网卡复制到内核缓冲区(DMA 直接内存访问)
  4. 内核态:数据从内核缓冲区复制到用户缓冲区
  5. 内核态 → 用户态:返回调用结果

特点:2 次上下文切换 + 2 次数据复制

用户进程              内核
   |                   |
   | 调用read()        |
   |----------------->|
   |                   |
   | 等待数据准备      |
   | (进程阻塞)        |--- 等待数据包到达 ---
   |                   |      ↓
   |                   | 数据到达网卡
   |                   |      ↓
   |                   | 数据复制到内核缓冲区
   |                   |      ↓
   | 数据复制完成      |
   |<-----------------|
   |                   |
   | 复制数据到用户空间|
   | (内核到用户复制)  |
   |                   |
   | 返回结果          |
   |<-----------------|

同步非阻塞IO

上下文切换过程:

  1. 用户态 → 内核态:发起系统调用
  2. 内核态 → 用户态:立即返回(无数据)
  3. 重复步骤 1-2 进行轮询(多次上下文切换)
  4. 用户态 → 内核态:数据就绪时调用
  5. 内核态:数据从内核复制到用户空间
  6. 内核态 → 用户态:返回结果

特点:多次上下文切换 + 2 次数据复制,CPU 消耗高

用户进程              内核
   |                   |
   | 调用recv()        |
   |----------------->|
   |                   |
   | 立即返回EWOULDBLOCK|
   |<-----------------|
   |                   |
   | 不断轮询recv()    |
   |----------------->|
   | 返回EWOULDBLOCK   |
   |<-----------------|
   |      ...          |
   | 第N次调用recv()   |
   |----------------->|
   |                   |
   | 数据已准备好      |
   | 开始复制到用户空间 |
   |<-----------------|
   | 复制完成          |
   |<-----------------|

多路复用

NIO+Selector对路复用器

用户进程              内核
   |                   |
   | 调用select()      |
   |----------------->|
   |                   |
   | 监视多个fd        |
   | (进程阻塞)        |--- 等待任意fd就绪 ---
   |                   |      ↓
   |                   | 有fd就绪(如socket1)
   |<-----------------|
   |                   |
   | 遍历就绪fd        |
   | 调用recv(socket1) |
   |----------------->|
   |                   |
   | 复制数据到用户空间 |
   |<-----------------|

上下文切换过程(以 select 为例):

应用程序               内核
   |                    |
   | select() 系统调用  | 1. 用户态→内核态
   |------------------>|
   |                    |
   | 遍历所有fd         | 2. 在内核遍历所有fd
   |                    |
   | 有fd就绪时返回     | 3. 内核态→用户态
   |<------------------|
   |                    |
   | 遍历就绪fd         | 4. 在用户空间遍历
   |                    |
   | 对每个就绪fd:      |
   |   recv()系统调用   | 5. 用户态→内核态
   |------------------>|
   |   数据复制         | 6. 内核到用户复制
   |<------------------| 7. 内核态→用户态

epoll 的改进:

应用程序               内核
   |                    |
   | epoll_create()     |
   |------------------>|
   |                    |
   | epoll_ctl(ADD)     | 添加fd到红黑树
   |------------------>|
   |                    |
   | epoll_wait()       | 1. 用户态→内核态
   |------------------>|
   |                    |
   | 等待就绪事件        | 2. 进程阻塞
   |                    |    ↓
   |                    | 事件就绪队列
   |<------------------| 3. 返回就绪事件
   |                    |    (只返回就绪的fd)
   |                    |
   | 处理就绪事件        |
   | recv(就绪fd)       | 4. 用户态→内核态
   |------------------>|
   |                    | 5. 数据复制
   |<------------------| 6. 内核态→用户态

特点

  • select/poll:每次调用都要传递所有 fd,内核遍历所有 fd
  • epoll:内核维护事件表,只返回就绪的 fd

异步阻塞IO(少用)

AIO,linux中对的异步实际上是假的AIO,底层实现还是多路复用模拟的AIO

在windows中实现了真正的异步AIO(IOCP)。

上下文切换:

类似同步阻塞,但操作发起是非阻塞的,等待完成是阻塞的。

用户进程              内核
   |                   |
   | 发起异步操作      |
   |----------------->|
   |                   |
   | 立即返回          |--- 异步处理 ---
   |<-----------------|      ↓
   |                   | 等待操作完成
   |                   |      ↓
   | 调用阻塞等待      | 完成时设置状态
   |----------------->|
   |                   |
   | 等待完成          | 阻塞等待信号
   |<-----------------|

异步非阻塞IO

数据流程(Linux AIO):

用户进程              内核
   |                   |
   | io_submit()       | 1. 用户态→内核态
   |----------------->|
   |                   |
   | 立即返回          | 2. 内核态→用户态
   |<-----------------|
   |                   |
   | 继续执行其他任务   |--- 内核异步处理 ---
   |                   |      ↓
   |                   | 等待数据到达
   |                   |      ↓
   |                   | 数据复制到用户缓冲区
   |                   |      ↓
   | 收到信号/回调     | 3. 内核发送信号
   |<-----------------|
   |                   |
   | 处理完成事件       |

Windows IOCP 流程:

应用程序              内核
   |                   |
   | WSARecv()         | 1. 提交异步请求
   |----------------->|
   |                   |
   | 立即返回          | 2. 返回正在处理
   |<-----------------|
   |                   |
   | 继续执行          |--- 内核处理 ---
   |                   |      ↓
   | GetQueuedCompletionStatus()|
   |----------------->| 3. 等待完成端口
   |                   |      ↓
   |                   | 操作完成时
   |                   | 数据已复制到用户缓冲区
   |<-----------------| 4. 返回完成状态

数据复制差异:

  • 同步 I/O:用户进程调用时,数据才从内核复制到用户空间
  • 异步 I/O:内核自动完成数据复制,完成后通知用户进程

信号驱动

不完善,基本不用

对比总结

模型用户态→内核态切换次数数据复制时机等待方式
同步阻塞1次(等待+复制)调用时复制进程阻塞
同步非阻塞多次轮询数据就绪时复制轮询
多路复用2次(select+recv)就绪后复制单线程阻塞等待多个
异步阻塞2次(提交+等待)完成时已复制阻塞等待完成
异步非阻塞1次(提交)内核自动复制回调/信号

数据复制过程

  1. DMA 复制:网卡数据 → 内核缓冲区(由 DMA 控制器完成,不占用 CPU)
  2. CPU 复制:内核缓冲区 → 用户缓冲区(需要 CPU 参与)