什么是I/O

I/O = Input/Output,输入/输出。本质上就是:数据的流动

  • 输入:数据从外部设备 → 进入内存(比如读文件、收网络包、键盘输入)
  • 输出:数据从内存 → 到外部设备(比如写文件、发网络包、屏幕显示)

常见的I/O设备

设备 输入 输出
磁盘 读文件 写文件
网卡 接收数据包 发送数据包
键盘 按键输入 -
显示器 - 显示内容
鼠标 点击/移动 -

I/O在计算机中的位置

┌─────────────────────────────────────┐
│            应用程序                  │
│         (用户空间)                   │
└─────────────┬───────────────────────┘
              │ 系统调用
              ↓
┌─────────────────────────────────────┐
│            操作系统内核               │
│         (内核空间)                   │
│    ┌─────────────────────────┐      │
│    │      I/O子系统          │      │
│    │  (文件系统、网络协议栈)  │      │
│    └──────────┬──────────────┘      │
└───────────────┼─────────────────────┘
                │ 设备驱动
                ↓
┌─────────────────────────────────────┐
│         硬件设备                      │
│      (磁盘、网卡、显卡...)            │
└─────────────────────────────────────┘

为什么I/O慢?

I/O是计算机中最慢的操作,原因:

操作 速度量级 对比
CPU指令 纳秒级 🚀🚀🚀
内存访问 ~100ns 🚀🚀
SSD读取 ~100μs 🚀
机械硬盘 ~10ms 🐢
网络传输 1-100ms+ 🐌🐌🐌

CPU太快,I/O太慢,差距在10⁵-10⁸倍

所以I/O模型的核心问题就是:怎么让CPU不等I/O?

I/O的两个核心动作

对于一次读操作:

1. 等待数据准备好

磁盘/网卡 → 内核缓冲区

2. 拷贝数据

内核缓冲区 → 用户缓冲区(应用程序内存)

所有I/O模型的差异,都在于如何处理这两个阶段

内核态 vs 用户态

这是理解I/O的关键:

  • 用户态:应用程序运行的权限级别,不能直接访问硬件
  • 内核态:操作系统运行的权限级别,可以访问一切

一次I/O必定涉及用户态↔内核态切换

切换开销:

  • 保存/恢复寄存器
  • 切换页表
  • 刷新缓存

频繁的系统调用是性能杀手

缓冲区的作用

内核缓冲区:操作系统维护的缓冲区

用户缓冲区:应用程序自己的缓冲区

写入流程:

用户缓冲区 → 内核缓冲区 → 磁盘/网卡

读取流程:

磁盘/网卡 → 内核缓冲区 → 用户缓冲区

为什么有缓冲区?

  • 减少实际I/O次数(合并小请求)
  • 解耦速度差异(生产者-消费者模式)

零拷贝技术

传统I/O需要多次拷贝:

磁盘 → 内核缓冲区 → 用户缓冲区 → 内核缓冲区 → 网卡
        (DMA拷贝)    (CPU拷贝)   (CPU拷贝)  (DMA拷贝)

零拷贝技术(sendfile、mmap)可以减少拷贝次数:

磁盘 → 内核缓冲区 → 网卡
       (DMA拷贝)   (DMA拷贝)

应用场景: Kafka、Nginx等高性能服务器

概念 核心理解
I/O本质 数据在内存与外部设备间流动
I/O之痛 速度慢,CPU要等
I/O模型 解决"怎么等"的问题
核心矛盾 CPU太快,I/O太慢
优化方向 减少等待、减少拷贝、减少切换

Linux的五种I/O模型是理解网络编程和高并发服务器的核心。

对于一次I/O操作(比如read),会经历两个阶段:

  1. 等待数据准备:数据从网卡/磁盘读取到内核缓冲区
  2. 将数据从内核拷贝到用户空间:从内核缓冲区拷贝到应用程序缓冲区

五种I/O模型的区别主要在这两个阶段如何处理。

1. 阻塞I/O(Blocking I/O)

最简单的模型,默认行为。

应用进程调用recvfrom
    ↓
    阻塞等待...
    ↓
    数据准备好
    ↓
    内核拷贝数据到用户空间
    ↓
    返回成功

特点:

  • 调用后进程挂起,直到数据准备好并拷贝完成
  • 简单直观,代码容易理解
  • 缺点: 一个线程只能处理一个连接,效率极低

典型场景: 简单的客户端程序

2. 非阻塞I/O(Non-blocking I/O)

不断轮询,不等待。

应用进程调用recvfrom
    ↓
    数据未准备好 → 立即返回EAGAIN错误
    ↓
    再次调用recvfrom
    ↓
    再次返回EAGAIN...
    ↓
    (循环轮询)
    ↓
    数据准备好了 → 拷贝数据 → 返回成功

特点:

  • 设置socket为NONBLOCK,调用立即返回
  • 数据未准备好返回错误(EAGAIN/EWOULDBLOCK)
  • 缺点: CPU空转严重,轮询消耗大量资源

典型场景: 很少单独使用,通常配合I/O多路复用

3. I/O多路复用

用select/poll/epoll同时监控多个连接

多个的进程的IO可以注册到一个复用器(select)上,然后用一个进程调用该select,,select会监听所有注册进来的IO。

如果select监听的IO在内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任一IO在内核缓冲区中有可读数据时,select调用就会返回;而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。
Linux中IO复用的实现方式主要有Select,Poll和Epoll:

Select:注册IO、阻塞扫描,监听的IO最大连接数不能多于FD_ SIZE(1024)。
Poll:原理和Select相似,没有数量限制,但IO数量大,扫描线性性能下降。
Epoll :事件驱动不阻塞,mmap实现内核与用户空间的消息传递,数量很大,Linux2.6后内核支持。

应用进程调用select/epoll_wait
    ↓
    阻塞等待任意一个socket可读
    ↓
    有socket可读了
    ↓
    返回就绪的socket列表
    ↓
    对就绪socket调用recvfrom
    ↓
    内核拷贝数据到用户空间
    ↓
    返回成功

特点:

  • 一个线程可以监控多个文件描述符
  • select/poll/epoll是系统调用,本身也会阻塞
  • 优点: 单线程处理大量连接,性能好
  • 缺点: 需要两次系统调用(select + recvfrom)

三种实现对比:

  • select:有fd数量限制(1024),O(n)遍历
  • poll:无数量限制,仍需O(n)遍历
  • epoll:事件驱动,只返回就绪的fd,O(1)

典型场景: Nginx、Redis、Netty等高性能服务器

4. 信号驱动I/O(Signal-driven I/O)

数据准备好时发信号通知。

应用进程建立SIGIO信号处理函数
    ↓
    调用sigaction注册信号
    ↓
    进程继续执行其他任务...
    ↓
    数据准备好 → 内核发送SIGIO信号
    ↓
    信号处理函数中调用recvfrom
    ↓
    内核拷贝数据到用户空间

特点:

  • 等待数据阶段不阻塞
  • 数据准备好后通过信号通知
  • 缺点: 信号处理复杂,TCP场景信号过多,实际很少使用

典型场景: UDP应用较多,TCP很少用

5. 异步I/O(Asynchronous I/O)

真正的异步,两个阶段都不阻塞。

应用进程调用aio_read
    ↓
    立即返回,进程继续执行
    ↓
    (同时)内核等待数据 + 拷贝数据
    ↓
    全部完成后,通知应用进程

特点:

  • 两个阶段都不阻塞
  • 应用进程只需发起请求,完成后会被通知
  • 优点: 真正的异步,性能最优
  • 缺点: Linux的aio支持长期不完善,编程模型复杂

典型场景: Windows的IOCP用得比较多,Linux上用较少

五种模型对比

模型 第一阶段(等待数据) 第二阶段(拷贝数据) 特点
阻塞I/O 阻塞 阻塞 简单但低效
非阻塞I/O 非阻塞(轮询) 阻塞 CPU空转
I/O多路复用 阻塞(在select上) 阻塞 高并发首选
信号驱动I/O 非阻塞(信号通知) 阻塞 TCP下较少用
异步I/O 非阻塞 非阻塞 真正异步

同步 vs 异步

  • 同步I/O:导致请求进程阻塞,直到I/O操作完成(前四种都是同步)
  • 异步I/O:不导致请求进程阻塞(只有AIO是真正的异步)

实际开发中,I/O多路复用(epoll) 是Linux高性能服务器的标准选择,Redis、Nginx都用这个。阻塞I/O适合简单场景,异步I/O理论上最优但Linux支持不够成熟。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐