Skip to content

I/O管理与设备驱动

课程概述

本教程深入讲解操作系统的I/O子系统,从I/O硬件原理到设备驱动开发,从I/O调度算法到零拷贝技术,帮助你全面掌握I/O管理的核心机制和优化技术。

学习目标:

  • 理解I/O硬件与中断机制
  • 掌握I/O调度算法的设计与权衡
  • 深入了解缓冲与缓存机制
  • 学习设备驱动程序的架构
  • 掌握异步I/O与零拷贝技术
  • 理解DMA与内存映射I/O

1. I/O硬件基础

1.1 I/O设备分类

┌─────────────────────────────────────────────────────────────┐
│              I/O设备分类与特性                                │
└─────────────────────────────────────────────────────────────┘

按数据传输速率分类:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  块设备 (Block Device)                                      │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ • 特点: 以数据块为单位传输                             │ │
│  │ • 支持随机访问                                         │ │
│  │ • 可寻址                                               │ │
│  │                                                        │ │
│  │ 示例:                                                  │ │
│  │ ┌──────────┐  ┌──────────┐  ┌──────────┐             │ │
│  │ │ 硬盘(HDD)│  │ 固态硬盘  │  │ U盘/SD卡 │             │ │
│  │ │ 100MB/s  │  │ (SSD)    │  │ 50MB/s   │             │ │
│  │ │          │  │ 500MB/s  │  │          │             │ │
│  │ └──────────┘  └──────────┘  └──────────┘             │ │
│  └───────────────────────────────────────────────────────┘ │
│                                                             │
│  字符设备 (Character Device)                                │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ • 特点: 以字符流为单位传输                             │ │
│  │ • 顺序访问                                             │ │
│  │ • 不可寻址                                             │ │
│  │                                                        │ │
│  │ 示例:                                                  │ │
│  │ ┌──────────┐  ┌──────────┐  ┌──────────┐             │ │
│  │ │ 键盘     │  │ 鼠标     │  │ 串口     │             │ │
│  │ │ 终端     │  │ 打印机   │  │ 调制解调器│             │ │
│  │ └──────────┘  └──────────┘  └──────────┘             │ │
│  └───────────────────────────────────────────────────────┘ │
│                                                             │
│  网络设备 (Network Device)                                  │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ • 特点: 数据包传输                                     │ │
│  │ • 异步通信                                             │ │
│  │ • 中断驱动                                             │ │
│  │                                                        │ │
│  │ 示例:                                                  │ │
│  │ ┌──────────┐  ┌──────────┐  ┌──────────┐             │ │
│  │ │ 网卡     │  │ WiFi     │  │ 蓝牙     │             │ │
│  │ │ (NIC)    │  │ 适配器   │  │ 适配器   │             │ │
│  │ │ 1Gbps    │  │ 867Mbps  │  │ 3Mbps    │             │ │
│  │ └──────────┘  └──────────┘  └──────────┘             │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

1.2 I/O控制方式

┌─────────────────────────────────────────────────────────────┐
│              四种I/O控制方式演进                              │
└─────────────────────────────────────────────────────────────┘

1. 程序控制I/O (Programmed I/O - PIO)
   ┌─────────────────────────────────────────────────────────┐
   │  CPU                    设备控制器                       │
   │  ┌────┐                 ┌────────┐                      │
   │  │ 1. │ 发起I/O请求 ──▶ │        │                      │
   │  ├────┤                 │ 状态   │                      │
   │  │ 2. │ 轮询状态寄存器  │ 寄存器 │                      │
   │  │    │ <────────────── │        │                      │
   │  ├────┤                 └────────┘                      │
   │  │ 3. │ 读取数据                                         │
   │  └────┘                                                  │
   │                                                          │
   │  缺点: CPU完全被占用,效率低                              │
   └─────────────────────────────────────────────────────────┘

2. 中断驱动I/O (Interrupt-driven I/O)
   ┌─────────────────────────────────────────────────────────┐
   │  CPU                    设备控制器                       │
   │  ┌────┐                 ┌────────┐                      │
   │  │ 1. │ 发起I/O请求 ──▶ │        │                      │
   │  ├────┤                 │        │                      │
   │  │ 2. │ 继续执行其他任务 │        │                      │
   │  │    │                 │ 数据   │                      │
   │  │    │                 │ 传输   │                      │
   │  ├────┤                 │        │                      │
   │  │ 3. │ <────中断────── │ 完成   │                      │
   │  ├────┤                 └────────┘                      │
   │  │ 4. │ 处理中断,读取数据                                │
   │  └────┘                                                  │
   │                                                          │
   │  优点: CPU可以执行其他任务                               │
   │  缺点: 每次I/O都需要中断,高速设备效率低                  │
   └─────────────────────────────────────────────────────────┘

3. DMA (Direct Memory Access)
   ┌─────────────────────────────────────────────────────────┐
   │  CPU        DMA控制器       内存        设备控制器       │
   │  ┌────┐    ┌──────────┐   ┌──────┐   ┌────────┐        │
   │  │ 1. │───▶│ 设置DMA  │   │      │   │        │        │
   │  │    │    │ (源/目的/│   │      │   │        │        │
   │  │    │    │  长度)   │   │      │   │        │        │
   │  ├────┤    └────┬─────┘   └──────┘   └────────┘        │
   │  │ 2. │ 继续执行 │                                       │
   │  │    │ 其他任务 │                                       │
   │  │    │         │ 2. 数据传输                           │
   │  │    │         │ <────────────────────▶               │
   │  │    │         │ (不经过CPU)                           │
   │  ├────┤         │                                       │
   │  │ 3. │ <───中断│                                       │
   │  │    │         │ (传输完成)                            │
   │  └────┘    └────┴─────┘                                 │
   │                                                          │
   │  优点: 大块数据传输时不占用CPU                           │
   │  应用: 硬盘、网卡、显卡等高速设备                        │
   └─────────────────────────────────────────────────────────┘

4. 通道I/O (Channel I/O)
   ┌─────────────────────────────────────────────────────────┐
   │  CPU        I/O通道        内存        设备              │
   │  ┌────┐    ┌──────────┐   ┌──────┐   ┌────────┐        │
   │  │    │───▶│ I/O处理器│   │      │   │ 设备1  │        │
   │  │    │    │          │   │      │   ├────────┤        │
   │  │    │    │ (独立的  │◀─▶│      │◀─▶│ 设备2  │        │
   │  │    │    │  指令集) │   │      │   ├────────┤        │
   │  │    │    │          │   │      │   │ 设备3  │        │
   │  └────┘    └──────────┘   └──────┘   └────────┘        │
   │                                                          │
   │  优点: 可执行复杂的I/O程序                               │
   │  应用: 大型主机、服务器                                  │
   └─────────────────────────────────────────────────────────┘

1.3 中断机制详解

┌─────────────────────────────────────────────────────────────┐
│              中断处理全流程                                   │
└─────────────────────────────────────────────────────────────┘

                    正常执行流
    ┌────┐  ┌────┐  ┌────┐  ┌────┐
    │指令1│─▶│指令2│─▶│指令3│─▶│指令4│
    └────┘  └────┘  └────┘  └────┘

                      │ 中断发生

    ┌─────────────────────────────────────────────┐
    │        中断处理流程                          │
    │                                             │
    │  1. 硬件响应                                │
    │  ┌───────────────────────────────────────┐ │
    │  │ • 保存当前PC到栈                       │ │
    │  │ • 保存PSW(程序状态字)                  │ │
    │  │ • 切换到内核态                         │ │
    │  │ • 查找中断向量表                       │ │
    │  │ • 跳转到中断处理程序                   │ │
    │  └───────────────────────────────────────┘ │
    │                                             │
    │  2. 保存上下文                              │
    │  ┌───────────────────────────────────────┐ │
    │  │ 保存所有寄存器到内核栈                  │ │
    │  │ ┌─────────────┐                        │ │
    │  │ │ EAX: 0x1234 │                        │ │
    │  │ │ EBX: 0x5678 │                        │ │
    │  │ │ ECX: 0xABCD │                        │ │
    │  │ │ ...         │                        │ │
    │  │ └─────────────┘                        │ │
    │  └───────────────────────────────────────┘ │
    │                                             │
    │  3. 中断处理                                │
    │  ┌───────────────────────────────────────┐ │
    │  │ 执行中断服务例程(ISR)                  │ │
    │  │                                        │ │
    │  │ 上半部(Top Half):                      │ │
    │  │  • 快速响应                            │ │
    │  │  • 关键数据读取                        │ │
    │  │  • 设置标志位                          │ │
    │  │                                        │ │
    │  │ 下半部(Bottom Half):                   │ │
    │  │  • 软中断(SoftIRQ)                     │ │
    │  │  • 任务队列(Tasklet)                   │ │
    │  │  • 工作队列(Work Queue)                │ │
    │  └───────────────────────────────────────┘ │
    │                                             │
    │  4. 恢复上下文                              │
    │  ┌───────────────────────────────────────┐ │
    │  │ 从内核栈恢复寄存器                      │ │
    │  │ 恢复PC和PSW                            │ │
    │  │ 返回用户态                              │ │
    │  └───────────────────────────────────────┘ │
    └─────────────────────────────────────────────┘


    ┌────┐  ┌────┐  ┌────┐
    │指令4│─▶│指令5│─▶│指令6│ 继续执行
    └────┘  └────┘  └────┘

Linux中断向量表示例:
┌──────┬────────────────────────────────────────┐
│ IRQ  │ 中断源                                  │
├──────┼────────────────────────────────────────┤
│  0   │ 系统定时器 (PIT)                        │
│  1   │ 键盘控制器                              │
│  2   │ 级联到从PIC                             │
│  3   │ 串口2 (COM2)                            │
│  4   │ 串口1 (COM1)                            │
│  5   │ 并口2 (LPT2)                            │
│  6   │ 软盘控制器                              │
│  7   │ 并口1 (LPT1)                            │
│  8   │ 实时时钟 (RTC)                          │
│  9   │ ACPI                                    │
│  10  │ 可用                                    │
│  11  │ 可用                                    │
│  12  │ PS/2鼠标                                │
│  13  │ 数学协处理器                            │
│  14  │ 主IDE控制器                             │
│  15  │ 从IDE控制器                             │
└──────┴────────────────────────────────────────┘

2. I/O调度算法

2.1 磁盘调度算法

┌─────────────────────────────────────────────────────────────┐
│              磁盘I/O调度算法对比                              │
└─────────────────────────────────────────────────────────────┘

磁盘结构:
  柱面(Cylinder) ───┐

  ┌─────────────────▼──────────────────┐
  │                                    │
  │     ┌───┐                          │
  │     │   │ 磁头(Head)               │
  │     └─┬─┘                          │
  │       │                            │
  │  ═════▼═══════════════════════════ │ 磁道(Track)
  │       │                            │
  │       │                            │
  │  ═════════════════════════════════ │
  │                                    │
  │  ═════════════════════════════════ │
  │                                    │
  └────────────────────────────────────┘
       扇区(Sector)

访问时间 = 寻道时间 + 旋转延迟 + 传输时间
         (最慢,5-10ms) (中等,2-5ms) (最快,<1ms)

1. FCFS (先来先服务)
   ┌──────────────────────────────────────────────────────┐
   │ 请求队列: 98, 183, 37, 122, 14, 124, 65, 67         │
   │ 当前磁头位置: 53                                     │
   │                                                      │
   │  0   14  37  53  65 67  98     122124     183   200 │
   │  │    │   │   │   │  │   │      │ │        │     │  │
   │  └────┼───┼───┼───┼──┼───┼──────┼─┼────────┼─────┘  │
   │       │   │   └───┼──┼───┼──────┼─┼────────┘        │
   │       │   └───────┼──┼───┼──────┼─┘                 │
   │       └───────────┼──┼───┼──────┘                   │
   │                   └──┼───┘                           │
   │                      └───────────────────────────┐   │
   │                                                  │   │
   │  移动距离: 45+85+146+85+108+10+59+116 = 654      │   │
   │  优点: 公平,无饥饿                                │   │
   │  缺点: 效率低,寻道时间长                          │   │
   └──────────────────────────────────────────────────────┘

2. SSTF (最短寻道时间优先)
   ┌──────────────────────────────────────────────────────┐
   │ 选择离当前磁头最近的请求                              │
   │                                                      │
   │  0   14  37  53  65 67  98     122124     183   200 │
   │  │    │   │   │   │  │   │      │ │        │     │  │
   │  │    │   │   └───┼──┘   │      │ │        │     │  │
   │  │    │   └───────┘      └──────┼─┘        │     │  │
   │  │    │                         └──────────┘     │  │
   │  │    └──────────────────────────────────────────┘  │
   │  └───────────────────────────────────────────────┐   │
   │                                                  │   │
   │  顺序: 53→65→67→37→14→98→122→124→183            │   │
   │  移动距离: 12+2+30+23+84+24+2+59 = 236           │   │
   │  优点: 寻道时间短                                 │   │
   │  缺点: 可能导致饥饿                               │   │
   └──────────────────────────────────────────────────────┘

3. SCAN (电梯算法)
   ┌──────────────────────────────────────────────────────┐
   │ 磁头单向移动,到达一端后反向                           │
   │                                                      │
   │  0   14  37  53  65 67  98     122124     183   200 │
   │  │    │   │   │   │  │   │      │ │        │     │  │
   │  │    │   │   └───┼──┼───┼──────┼─┼────────┼─────┘  │
   │  │    │   │       │  │   │      │ │        │   反向 │
   │  │    │   └───────┼──┼───┼──────┼─┘        │   ◀────┘
   │  │    └───────────┼──┼───┼──────┘          │        │
   │  │                │  │   │                 │        │
   │  │                └──┼───┘                 │        │
   │  │                   └─────────────────────┘        │
   │                                                      │
   │  顺序: 53→65→67→98→122→124→183→200→37→14           │
   │  移动距离: 12+2+31+24+2+59+17+163+23 = 333          │
   │  优点: 避免饥饿,性能稳定                             │
   │  缺点: 两端等待时间较长                              │
   └──────────────────────────────────────────────────────┘

4. C-SCAN (循环扫描)
   ┌──────────────────────────────────────────────────────┐
   │ 只在一个方向服务,到达末端后快速返回起点                │
   │                                                      │
   │  0   14  37  53  65 67  98     122124     183   200 │
   │  │    │   │   │   │  │   │      │ │        │     │  │
   │  │    │   │   └───┼──┼───┼──────┼─┼────────┼─────┘  │
   │  │    │   │       │  │   │      │ │        │   返回 │
   │  │    │   └───────┼──┼───┼──────┼─┘        └────────┤
   │  │    └───────────┼──┼───┼──────┘                  0│
   │  │                │  │   │                           │
   │  └────────────────┼──┘   │                           │
   │                   └──────┘                           │
   │                                                      │
   │  顺序: 53→65→67→98→122→124→183→200→0→14→37         │
   │  移动距离: 12+2+31+24+2+59+17+200+14+23 = 384       │
   │  优点: 等待时间更均匀                                 │
   │  缺点: 移动距离可能较长                               │
   └──────────────────────────────────────────────────────┘

2.2 Linux I/O调度器

┌─────────────────────────────────────────────────────────────┐
│              Linux I/O调度器演进                             │
└─────────────────────────────────────────────────────────────┘

1. Noop (空操作调度器)
   ┌─────────────────────────────────────────────────────┐
   │  请求队列 (FIFO)                                     │
   │  ┌──────┬──────┬──────┬──────┬──────┐              │
   │  │ Req1 │ Req2 │ Req3 │ Req4 │ Req5 │──▶ 磁盘      │
   │  └──────┴──────┴──────┴──────┴──────┘              │
   │                                                     │
   │  特点: 不排序,不合并                                 │
   │  适用: SSD、NVMe等随机访问无性能差异的设备           │
   └─────────────────────────────────────────────────────┘

2. Deadline (截止时间调度器)
   ┌─────────────────────────────────────────────────────┐
   │  排序队列 (按扇区号)                                 │
   │  ┌──────┬──────┬──────┬──────┬──────┐              │
   │  │ S10  │ S25  │ S40  │ S78  │ S90  │              │
   │  └──────┴──────┴──────┴──────┴──────┘              │
   │                                                     │
   │  读FIFO队列 (超时500ms)                             │
   │  ┌──────┬──────┬──────┐                            │
   │  │ R:S78│ R:S10│ R:S25│                            │
   │  └──────┴──────┴──────┘                            │
   │                                                     │
   │  写FIFO队列 (超时5s)                                │
   │  ┌──────┬──────┐                                   │
   │  │ W:S40│ W:S90│                                   │
   │  └──────┴──────┘                                   │
   │                                                     │
   │  调度策略:                                           │
   │  • 优先从排序队列选择                                │
   │  • 如果FIFO队列有超时请求,先处理                     │
   │  • 读优先于写                                        │
   │                                                     │
   │  适用: 数据库、Web服务器等延迟敏感应用               │
   └─────────────────────────────────────────────────────┘

3. CFQ (完全公平队列)
   ┌─────────────────────────────────────────────────────┐
   │  Per-Process队列 (每个进程一个队列)                  │
   │                                                     │
   │  进程A队列  进程B队列  进程C队列                     │
   │  ┌──────┐  ┌──────┐  ┌──────┐                      │
   │  │ Req1 │  │ Req3 │  │ Req5 │                      │
   │  │ Req2 │  │ Req4 │  │ Req6 │                      │
   │  └───┬──┘  └───┬──┘  └───┬──┘                      │
   │      │         │         │                          │
   │      └─────────┼─────────┘                          │
   │                │                                     │
   │            时间片轮转                                │
   │                │                                     │
   │                ▼                                     │
   │           ┌─────────┐                               │
   │           │  磁盘   │                               │
   │           └─────────┘                               │
   │                                                     │
   │  特点:                                               │
   │  • 每个进程分配时间片                                │
   │  • 按优先级调整时间片长度                            │
   │  • 空闲队列检测,提高交互性                           │
   │                                                     │
   │  适用: 桌面系统,多进程I/O场景                        │
   └─────────────────────────────────────────────────────┘

4. BFQ (Budget Fair Queueing)
   ┌─────────────────────────────────────────────────────┐
   │  基于带宽预算的公平调度                               │
   │                                                     │
   │  进程A (预算: 1MB)                                   │
   │  ┌────────────────┐                                 │
   │  │ ████████░░░░░░ │ 已用: 0.5MB                     │
   │  └────────────────┘                                 │
   │                                                     │
   │  进程B (预算: 2MB)                                   │
   │  ┌────────────────┐                                 │
   │  │ ██████████░░░░ │ 已用: 1.3MB                     │
   │  └────────────────┘                                 │
   │                                                     │
   │  特点:                                               │
   │  • 基于I/O带宽而非时间片                             │
   │  • 更精确的QoS保证                                   │
   │  • 低延迟模式支持                                    │
   │                                                     │
   │  适用: 现代桌面系统,SSD混合负载                      │
   └─────────────────────────────────────────────────────┘

5. mq-deadline / Kyber (多队列调度器)
   ┌─────────────────────────────────────────────────────┐
   │  为NVMe等多队列设备设计                              │
   │                                                     │
   │  CPU0 队列    CPU1 队列    CPU2 队列    CPU3 队列   │
   │  ┌──────┐    ┌──────┐    ┌──────┐    ┌──────┐     │
   │  │ Req1 │    │ Req3 │    │ Req5 │    │ Req7 │     │
   │  │ Req2 │    │ Req4 │    │ Req6 │    │ Req8 │     │
   │  └───┬──┘    └───┬──┘    └───┬──┘    └───┬──┘     │
   │      │           │           │           │         │
   │      └───────────┴───────────┴───────────┘         │
   │                      │                              │
   │                  硬件队列                            │
   │                  ┌───▼────┐                         │
   │                  │ NVMe   │                         │
   │                  │ 32队列 │                         │
   │                  └────────┘                         │
   │                                                     │
   │  特点:                                               │
   │  • 无锁设计,减少CPU竞争                              │
   │  • 充分利用设备多队列能力                            │
   │  • 支持百万级IOPS                                    │
   │                                                     │
   │  适用: NVMe SSD, 高性能存储                          │
   └─────────────────────────────────────────────────────┘

3. 缓冲与缓存

3.1 缓冲区管理

┌─────────────────────────────────────────────────────────────┐
│              缓冲区(Buffer)架构                               │
└─────────────────────────────────────────────────────────────┘

缓冲区的作用:
1. 平滑速度差异 (CPU快,I/O慢)
2. 减少I/O次数 (批量读写)
3. 提供共享访问

单缓冲 vs 双缓冲:
┌─────────────────────────────────────────────────────────────┐
│ 单缓冲                                                       │
│                                                             │
│  用户进程          缓冲区            设备                    │
│  ┌──────┐        ┌──────┐         ┌──────┐                 │
│  │      │ ───▶   │ Data │  ───▶   │      │                 │
│  │ 等待 │        └──────┘         │ I/O  │                 │
│  └──────┘                          └──────┘                 │
│                                                             │
│  时间线:                                                     │
│  ├──计算──┼──等待I/O──┼──计算──┼──等待I/O──┼                │
│                                                             │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 双缓冲 (Ping-Pong Buffer)                                   │
│                                                             │
│  用户进程      缓冲区A       缓冲区B         设备            │
│  ┌──────┐    ┌──────┐     ┌──────┐       ┌──────┐         │
│  │      │───▶│ Data │     │      │       │      │         │
│  │ 计算 │    └──┬───┘     └──────┘       │ I/O  │         │
│  │      │       │          ▲              │      │         │
│  └──────┘       └──────────┘              └──────┘         │
│                  交替使用                                    │
│                                                             │
│  时间线:                                                     │
│  ├──计算+I/O──┼──计算+I/O──┼──计算+I/O──┼                   │
│                                                             │
│  效率提升: 近2倍                                             │
└─────────────────────────────────────────────────────────────┘

Linux Buffer Cache架构:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  进程空间                                                    │
│  ┌────────────────────────────────────────────────────┐    │
│  │ read() / write()                                   │    │
│  └───────────────────┬────────────────────────────────┘    │
│                      │                                      │
│  ════════════════════▼═══════════════════════════════════  │
│                                                             │
│  内核空间                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │           VFS (虚拟文件系统)                         │   │
│  └──────────────────┬──────────────────────────────────┘   │
│                     │                                       │
│  ┌──────────────────▼──────────────────────────────────┐   │
│  │         Page Cache (页缓存)                          │   │
│  │  ┌────────┬────────┬────────┬────────┬────────┐     │   │
│  │  │ Page 1 │ Page 2 │ Page 3 │ Page 4 │ Page 5 │     │   │
│  │  │ Clean  │ Dirty  │ Clean  │ Dirty  │ Clean  │     │   │
│  │  └────────┴────────┴────────┴────────┴────────┘     │   │
│  └──────────────────┬──────────────────────────────────┘   │
│                     │                                       │
│  ┌──────────────────▼──────────────────────────────────┐   │
│  │         Buffer Cache (缓冲区缓存)                    │   │
│  │  ┌─────────────────────────────────────────────┐    │   │
│  │  │ Block Buffer Head                           │    │   │
│  │  │ ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐     │    │   │
│  │  │ │ Blk0 │─▶│ Blk1 │─▶│ Blk2 │─▶│ Blk3 │     │    │   │
│  │  │ └──────┘  └──────┘  └──────┘  └──────┘     │    │   │
│  │  └─────────────────────────────────────────────┘    │   │
│  └──────────────────┬──────────────────────────────────┘   │
│                     │                                       │
│  ┌──────────────────▼──────────────────────────────────┐   │
│  │         Block Layer (块层)                           │   │
│  │  • I/O调度器                                         │   │
│  │  • 请求合并                                          │   │
│  │  • 插件化架构                                        │   │
│  └──────────────────┬──────────────────────────────────┘   │
│                     │                                       │
│  ┌──────────────────▼──────────────────────────────────┐   │
│  │         设备驱动层                                   │   │
│  │  SCSI驱动  NVMe驱动  SATA驱动                        │   │
│  └──────────────────┬──────────────────────────────────┘   │
│                     │                                       │
│  ════════════════════▼═══════════════════════════════════  │
│                                                             │
│  硬件层                                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  HDD / SSD / NVMe                                   │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

3.2 页缓存机制

┌─────────────────────────────────────────────────────────────┐
│              Page Cache工作原理                              │
└─────────────────────────────────────────────────────────────┘

读操作流程:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  1. 应用调用read()                                           │
│     │                                                       │
│     ▼                                                       │
│  2. 检查Page Cache                                          │
│     ├─ 命中(Cache Hit) ──▶ 直接返回数据 ──▶ 完成           │
│     │                      (快速路径)                        │
│     │                                                       │
│     └─ 未命中(Cache Miss)                                   │
│         │                                                   │
│         ▼                                                   │
│  3. 分配新页面                                               │
│     ┌──────────────────┐                                   │
│     │ Page 分配         │                                   │
│     │ (alloc_pages)    │                                   │
│     └────────┬─────────┘                                   │
│              │                                              │
│              ▼                                              │
│  4. 发起磁盘I/O                                              │
│     ┌──────────────────┐                                   │
│     │ submit_bio()     │                                   │
│     └────────┬─────────┘                                   │
│              │                                              │
│              ▼                                              │
│  5. 等待I/O完成                                              │
│     ┌──────────────────┐                                   │
│     │ wait_on_page()   │                                   │
│     └────────┬─────────┘                                   │
│              │                                              │
│              ▼                                              │
│  6. 返回数据                                                 │
│     (慢速路径)                                               │
└─────────────────────────────────────────────────────────────┘

写操作策略:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  1. Write-Through (写穿)                                    │
│  ┌────────────────────────────────────────────────────┐    │
│  │  应用write() ──▶ Page Cache ──▶ 立即写入磁盘       │    │
│  │                     │                   │          │    │
│  │                     │                   ▼          │    │
│  │                     │              确认写入        │    │
│  │                     ▼                   │          │    │
│  │                  返回成功 ◀──────────────┘          │    │
│  │                                                    │    │
│  │  优点: 数据安全                                     │    │
│  │  缺点: 性能低                                       │    │
│  └────────────────────────────────────────────────────┘    │
│                                                             │
│  2. Write-Back (写回)                                       │
│  ┌────────────────────────────────────────────────────┐    │
│  │  应用write() ──▶ Page Cache (标记Dirty)            │    │
│  │                     │                              │    │
│  │                     ▼                              │    │
│  │                  返回成功                           │    │
│  │                                                    │    │
│  │                  (异步刷新)                         │    │
│  │                     │                              │    │
│  │                     ▼                              │    │
│  │             定时/条件触发 ──▶ 批量写入磁盘          │    │
│  │                                                    │    │
│  │  优点: 性能高                                       │    │
│  │  缺点: 断电可能丢数据                               │    │
│  │                                                    │    │
│  │  刷新时机:                                          │    │
│  │  • 定时刷新 (默认30秒)                              │    │
│  │  • 内存压力 (脏页过多)                              │    │
│  │  • 手动sync/fsync                                  │    │
│  └────────────────────────────────────────────────────┘    │
│                                                             │
│  3. Write-Combine (写合并)                                  │
│  ┌────────────────────────────────────────────────────┐    │
│  │  write(块1) ──┐                                    │    │
│  │  write(块2) ──┤ 合并                               │    │
│  │  write(块3) ──┤ ──▶ Page Cache ──▶ 一次写入磁盘    │    │
│  │  write(块4) ──┘                                    │    │
│  │                                                    │    │
│  │  优点: 减少I/O次数                                  │    │
│  └────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

页面回收策略(LRU):
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  LRU列表 (Least Recently Used)                              │
│                                                             │
│  活跃列表 (Active List)                                      │
│  ┌──────┬──────┬──────┬──────┬──────┐                      │
│  │ P100 │ P95  │ P92  │ P88  │ P80  │ 最近访问过多次        │
│  └──────┴──────┴──────┴──────┴──────┘                      │
│     │                              │                        │
│     │ 降级                    晋升 │                        │
│     ▼                              ▼                        │
│  非活跃列表 (Inactive List)                                  │
│  ┌──────┬──────┬──────┬──────┬──────┐                      │
│  │ P75  │ P60  │ P45  │ P30  │ P10  │ 候选淘汰              │
│  └──────┴──────┴──────┴──────┴──────┘                      │
│                                  │                          │
│                                  │ 回收                     │
│                                  ▼                          │
│                           ┌─────────────┐                   │
│                           │ 释放/写回    │                   │
│                           └─────────────┘                   │
│                                                             │
│  Two-Queue算法优化:                                          │
│  • 新页面先进入非活跃列表                                     │
│  • 第二次访问才进入活跃列表                                   │
│  • 避免一次性大文件污染缓存                                   │
└─────────────────────────────────────────────────────────────┘

4. 设备驱动程序

4.1 驱动架构

┌─────────────────────────────────────────────────────────────┐
│              Linux设备驱动架构                                │
└─────────────────────────────────────────────────────────────┘

分层架构:
┌─────────────────────────────────────────────────────────────┐
│  用户空间                                                    │
│  ┌────────────────────────────────────────────────────┐    │
│  │  应用程序                                           │    │
│  │  open() / read() / write() / ioctl() / close()     │    │
│  └──────────────────────┬─────────────────────────────┘    │
│                         │                                   │
│  ═══════════════════════▼════════════════════════════════  │
│                                                             │
│  内核空间                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  VFS (虚拟文件系统层)                                │   │
│  │  /dev/sda1  /dev/input/mouse0  /dev/net/tun         │   │
│  └──────────────────────┬──────────────────────────────┘   │
│                         │                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  设备类层 (Device Class)                             │   │
│  │  ┌─────────┬─────────┬─────────┬─────────────────┐  │   │
│  │  │ 字符设备 │ 块设备   │ 网络设备 │ 其他设备类       │  │   │
│  │  │ (char)  │ (block) │ (net)   │ (input/usb...)  │  │   │
│  │  └─────────┴─────────┴─────────┴─────────────────┘  │   │
│  └──────────────────────┬──────────────────────────────┘   │
│                         │                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  设备驱动层 (Device Driver)                          │   │
│  │  ┌──────────────────────────────────────────────┐   │   │
│  │  │ struct file_operations {                     │   │   │
│  │  │   .open    = xxx_open,                       │   │   │
│  │  │   .read    = xxx_read,                       │   │   │
│  │  │   .write   = xxx_write,                      │   │   │
│  │  │   .ioctl   = xxx_ioctl,                      │   │   │
│  │  │   .release = xxx_release,                    │   │   │
│  │  │ }                                            │   │   │
│  │  └──────────────────────────────────────────────┘   │   │
│  └──────────────────────┬──────────────────────────────┘   │
│                         │                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  总线层 (Bus Layer)                                  │   │
│  │  PCI总线  USB总线  I2C总线  SPI总线                  │   │
│  └──────────────────────┬──────────────────────────────┘   │
│                         │                                   │
│  ═══════════════════════▼════════════════════════════════  │
│                                                             │
│  硬件层                                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  物理设备                                            │   │
│  │  硬盘  网卡  键盘  鼠标  显卡  ...                   │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

设备模型:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  Kobject (内核对象)                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  • 引用计数                                          │   │
│  │  • sysfs文件系统表示                                 │   │
│  │  • 热插拔事件                                        │   │
│  └─────────────┬───────────────────────────────────────┘   │
│                │                                            │
│                ▼                                            │
│  Device (设备)   Driver (驱动)   Bus (总线)                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                 │
│  │ name     │  │ name     │  │ name     │                 │
│  │ bus      │  │ bus      │  │ match()  │                 │
│  │ driver ──┼─▶│ probe()  │  │ devices  │                 │
│  │ parent   │  │ remove() │  │ drivers  │                 │
│  └──────────┘  └──────────┘  └──────────┘                 │
│       │            │              │                        │
│       └────────────┴──────────────┘                        │
│                    │                                        │
│                    ▼                                        │
│            设备驱动匹配                                      │
│            ┌──────────────────┐                            │
│            │ 1. 检查bus类型   │                            │
│            │ 2. 调用match()   │                            │
│            │ 3. 调用probe()   │                            │
│            │ 4. 初始化设备    │                            │
│            └──────────────────┘                            │
└─────────────────────────────────────────────────────────────┘

4.2 字符设备驱动示例

c
/*
 * 简单字符设备驱动示例
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "mychar"
#define BUF_SIZE 1024

static dev_t dev_num;
static struct cdev my_cdev;
static struct class *my_class;
static char kernel_buffer[BUF_SIZE];

/* 打开设备 */
static int mychar_open(struct inode *inode, struct file *filp)
{
    printk(KERN_INFO "mychar: Device opened\n");
    return 0;
}

/* 读设备 */
static ssize_t mychar_read(struct file *filp, char __user *buf,
                           size_t count, loff_t *f_pos)
{
    size_t len = min(count, (size_t)BUF_SIZE);

    if (copy_to_user(buf, kernel_buffer, len)) {
        return -EFAULT;
    }

    printk(KERN_INFO "mychar: Read %zu bytes\n", len);
    return len;
}

/* 写设备 */
static ssize_t mychar_write(struct file *filp, const char __user *buf,
                            size_t count, loff_t *f_pos)
{
    size_t len = min(count, (size_t)BUF_SIZE);

    if (copy_from_user(kernel_buffer, buf, len)) {
        return -EFAULT;
    }

    kernel_buffer[len] = '\0';
    printk(KERN_INFO "mychar: Written %zu bytes: %s\n", len, kernel_buffer);
    return len;
}

/* ioctl控制 */
static long mychar_ioctl(struct file *filp, unsigned int cmd,
                        unsigned long arg)
{
    switch (cmd) {
    case 0: /* 清空缓冲区 */
        memset(kernel_buffer, 0, BUF_SIZE);
        printk(KERN_INFO "mychar: Buffer cleared\n");
        break;
    default:
        return -EINVAL;
    }
    return 0;
}

/* 关闭设备 */
static int mychar_release(struct inode *inode, struct file *filp)
{
    printk(KERN_INFO "mychar: Device closed\n");
    return 0;
}

/* 文件操作结构 */
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = mychar_open,
    .read = mychar_read,
    .write = mychar_write,
    .unlocked_ioctl = mychar_ioctl,
    .release = mychar_release,
};

/* 模块初始化 */
static int __init mychar_init(void)
{
    int ret;
    struct device *dev_ret;

    /* 1. 分配设备号 */
    ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    if (ret < 0) {
        printk(KERN_ERR "mychar: Failed to allocate device number\n");
        return ret;
    }
    printk(KERN_INFO "mychar: Device number allocated (Major: %d)\n",
           MAJOR(dev_num));

    /* 2. 初始化cdev */
    cdev_init(&my_cdev, &fops);
    my_cdev.owner = THIS_MODULE;

    /* 3. 添加cdev到系统 */
    ret = cdev_add(&my_cdev, dev_num, 1);
    if (ret < 0) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "mychar: Failed to add cdev\n");
        return ret;
    }

    /* 4. 创建设备类 */
    my_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(my_class)) {
        cdev_del(&my_cdev);
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "mychar: Failed to create class\n");
        return PTR_ERR(my_class);
    }

    /* 5. 创建设备节点 /dev/mychar */
    dev_ret = device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);
    if (IS_ERR(dev_ret)) {
        class_destroy(my_class);
        cdev_del(&my_cdev);
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "mychar: Failed to create device\n");
        return PTR_ERR(dev_ret);
    }

    printk(KERN_INFO "mychar: Device driver initialized successfully\n");
    return 0;
}

/* 模块卸载 */
static void __exit mychar_exit(void)
{
    device_destroy(my_class, dev_num);
    class_destroy(my_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "mychar: Device driver removed\n");
}

module_init(mychar_init);
module_exit(mychar_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");

4.3 中断处理

c
/*
 * 中断处理示例 (网卡驱动简化版)
 */
#include <linux/interrupt.h>
#include <linux/pci.h>

#define IRQ_NUM 11

/* 中断上半部 (Top Half) - 快速执行 */
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
    struct my_device *dev = (struct my_device *)dev_id;
    uint32_t status;

    /* 1. 读取中断状态寄存器 */
    status = readl(dev->base_addr + INT_STATUS_REG);

    if (!(status & MY_DEVICE_INT_MASK)) {
        /* 不是我们的中断 */
        return IRQ_NONE;
    }

    /* 2. 清除中断标志 */
    writel(status, dev->base_addr + INT_STATUS_REG);

    /* 3. 禁用设备中断 (防止中断风暴) */
    writel(0, dev->base_addr + INT_ENABLE_REG);

    /* 4. 调度下半部处理 */
    tasklet_schedule(&dev->tasklet);

    return IRQ_HANDLED;
}

/* 中断下半部 (Bottom Half) - Tasklet */
static void my_tasklet_handler(unsigned long data)
{
    struct my_device *dev = (struct my_device *)data;
    struct sk_buff *skb;

    /* 处理接收到的数据包 */
    while ((skb = receive_packet(dev)) != NULL) {
        /* 将数据包送入协议栈 */
        netif_rx(skb);
    }

    /* 重新使能中断 */
    writel(MY_DEVICE_INT_MASK, dev->base_addr + INT_ENABLE_REG);
}

/* 注册中断 */
static int my_device_probe(struct pci_dev *pdev,
                          const struct pci_device_id *id)
{
    struct my_device *dev;
    int ret;

    /* 分配设备结构 */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    /* 初始化tasklet */
    tasklet_init(&dev->tasklet, my_tasklet_handler,
                 (unsigned long)dev);

    /* 请求中断 */
    ret = request_irq(pdev->irq, my_interrupt_handler,
                     IRQF_SHARED, "mydevice", dev);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d\n", pdev->irq);
        kfree(dev);
        return ret;
    }

    printk(KERN_INFO "IRQ %d registered\n", pdev->irq);
    return 0;
}

/* 卸载时释放中断 */
static void my_device_remove(struct pci_dev *pdev)
{
    struct my_device *dev = pci_get_drvdata(pdev);

    /* 禁用中断 */
    writel(0, dev->base_addr + INT_ENABLE_REG);

    /* 释放IRQ */
    free_irq(pdev->irq, dev);

    /* 停止tasklet */
    tasklet_kill(&dev->tasklet);

    kfree(dev);
}

5. 异步I/O

5.1 I/O模型对比

┌─────────────────────────────────────────────────────────────┐
│              五种I/O模型对比                                  │
└─────────────────────────────────────────────────────────────┘

1. 阻塞I/O (Blocking I/O)
   ┌─────────────────────────────────────────────────────┐
   │  应用进程           内核                             │
   │  ┌──────┐          ┌──────┐                         │
   │  │      │─recvfrom─▶       │                         │
   │  │      │          │ 等待  │                         │
   │  │ 阻塞 │          │ 数据  │                         │
   │  │ 等待 │          │      │                         │
   │  │      │◀─────────│ 复制  │                         │
   │  │      │  返回数据 │ 数据  │                         │
   │  └──────┘          └──────┘                         │
   │                                                     │
   │  特点: 调用时阻塞,直到数据准备好并复制完成           │
   └─────────────────────────────────────────────────────┘

2. 非阻塞I/O (Non-blocking I/O)
   ┌─────────────────────────────────────────────────────┐
   │  应用进程           内核                             │
   │  ┌──────┐          ┌──────┐                         │
   │  │      │─recvfrom─▶       │                         │
   │  │      │◀EWOULDBLOCK─     │ 数据未就绪               │
   │  │      │          │      │                         │
   │  │ 轮询 │─recvfrom─▶       │                         │
   │  │      │◀EWOULDBLOCK─     │                         │
   │  │      │          │      │                         │
   │  │      │─recvfrom─▶       │ 数据就绪                │
   │  │ 阻塞 │          │ 复制  │                         │
   │  │      │◀─────────│ 数据  │                         │
   │  └──────┘  返回数据 └──────┘                         │
   │                                                     │
   │  特点: 需要不断轮询,浪费CPU                          │
   └─────────────────────────────────────────────────────┘

3. I/O多路复用 (I/O Multiplexing)
   ┌─────────────────────────────────────────────────────┐
   │  应用进程           内核                             │
   │  ┌──────┐          ┌──────┐                         │
   │  │      │─select/──▶       │ 监听多个fd              │
   │  │      │  epoll   │      │                         │
   │  │ 阻塞 │          │ 等待  │                         │
   │  │ 等待 │          │ 某个  │                         │
   │  │      │◀─────────│ 就绪  │                         │
   │  │      │  fd可读  │      │                         │
   │  │      │─recvfrom─▶       │                         │
   │  │ 阻塞 │          │ 复制  │                         │
   │  │      │◀─────────│ 数据  │                         │
   │  └──────┘  返回数据 └──────┘                         │
   │                                                     │
   │  特点: 单线程处理多个连接,高并发场景                 │
   └─────────────────────────────────────────────────────┘

4. 信号驱动I/O (Signal-driven I/O)
   ┌─────────────────────────────────────────────────────┐
   │  应用进程           内核                             │
   │  ┌──────┐          ┌──────┐                         │
   │  │      │─sigaction─▶      │ 注册SIGIO               │
   │  │      │◀─────────│      │                         │
   │  │ 继续 │          │ 等待  │                         │
   │  │ 执行 │          │ 数据  │                         │
   │  │      │◀──SIGIO──│      │ 数据就绪                │
   │  │      │─recvfrom─▶       │                         │
   │  │ 阻塞 │          │ 复制  │                         │
   │  │      │◀─────────│ 数据  │                         │
   │  └──────┘  返回数据 └──────┘                         │
   │                                                     │
   │  特点: 异步通知,但读取数据时仍然阻塞                 │
   └─────────────────────────────────────────────────────┘

5. 异步I/O (Asynchronous I/O)
   ┌─────────────────────────────────────────────────────┐
   │  应用进程           内核                             │
   │  ┌──────┐          ┌──────┐                         │
   │  │      │─aio_read─▶       │                         │
   │  │      │◀─────────│      │ 立即返回                │
   │  │ 继续 │          │ 等待  │                         │
   │  │ 执行 │          │ 数据  │                         │
   │  │      │          │ 复制  │                         │
   │  │      │          │ 数据  │                         │
   │  │      │◀──信号───│      │ 复制完成                │
   │  └──────┘  通知完成 └──────┘                         │
   │                                                     │
   │  特点: 完全异步,效率最高                             │
   └─────────────────────────────────────────────────────┘

性能对比:
┌──────────────┬──────────┬────────┬──────────────┐
│ I/O模型      │ 阻塞次数  │ CPU占用│ 适用场景      │
├──────────────┼──────────┼────────┼──────────────┤
│ 阻塞I/O      │ 2次       │ 低     │ 连接数少     │
│ 非阻塞I/O    │ 1次+轮询  │ 高     │ 不推荐       │
│ I/O多路复用  │ 2次       │ 中     │ 高并发服务器 │
│ 信号驱动I/O  │ 1次       │ 中     │ UDP应用      │
│ 异步I/O      │ 0次       │ 低     │ 高性能应用   │
└──────────────┴──────────┴────────┴──────────────┘

5.2 Linux AIO实战

c
/*
 * Linux原生AIO示例
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <libaio.h>

#define QUEUE_DEPTH 32
#define BLOCK_SIZE 4096

int main(int argc, char *argv[])
{
    io_context_t ctx;
    struct iocb iocb;
    struct iocb *iocbs[1];
    struct io_event events[QUEUE_DEPTH];
    struct timespec timeout;
    char *buffer;
    int fd, ret;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        return 1;
    }

    /* 1. 打开文件 (必须使用O_DIRECT) */
    fd = open(argv[1], O_RDONLY | O_DIRECT);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    /* 2. 分配对齐的缓冲区 */
    posix_memalign((void **)&buffer, 512, BLOCK_SIZE);

    /* 3. 初始化AIO上下文 */
    memset(&ctx, 0, sizeof(ctx));
    ret = io_setup(QUEUE_DEPTH, &ctx);
    if (ret < 0) {
        perror("io_setup");
        close(fd);
        return 1;
    }

    /* 4. 准备异步读请求 */
    memset(&iocb, 0, sizeof(iocb));
    iocb.aio_fildes = fd;
    iocb.aio_lio_opcode = IOCB_CMD_PREAD;
    iocb.aio_buf = (uint64_t)buffer;
    iocb.aio_nbytes = BLOCK_SIZE;
    iocb.aio_offset = 0;

    iocbs[0] = &iocb;

    /* 5. 提交I/O请求 */
    ret = io_submit(ctx, 1, iocbs);
    if (ret != 1) {
        perror("io_submit");
        io_destroy(ctx);
        close(fd);
        return 1;
    }

    printf("Async I/O submitted, doing other work...\n");

    /* 这里可以执行其他任务 */
    sleep(1);

    /* 6. 等待I/O完成 */
    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;

    ret = io_getevents(ctx, 1, 1, events, &timeout);
    if (ret != 1) {
        perror("io_getevents");
        io_destroy(ctx);
        close(fd);
        return 1;
    }

    /* 7. 处理结果 */
    if (events[0].res == BLOCK_SIZE) {
        printf("Read %ld bytes successfully\n", events[0].res);
        printf("First 64 bytes: %.64s\n", buffer);
    } else {
        fprintf(stderr, "Read failed: %ld\n", events[0].res);
    }

    /* 8. 清理资源 */
    io_destroy(ctx);
    free(buffer);
    close(fd);

    return 0;
}
python
#!/usr/bin/env python3
"""
Python异步I/O示例 (使用asyncio)
"""
import asyncio
import aiofiles

async def read_file_async(filename):
    """异步读取文件"""
    try:
        async with aiofiles.open(filename, mode='r') as f:
            content = await f.read()
            print(f"[{filename}] Read {len(content)} bytes")
            return content
    except Exception as e:
        print(f"[{filename}] Error: {e}")
        return None

async def write_file_async(filename, data):
    """异步写入文件"""
    try:
        async with aiofiles.open(filename, mode='w') as f:
            await f.write(data)
            print(f"[{filename}] Written {len(data)} bytes")
    except Exception as e:
        print(f"[{filename}] Error: {e}")

async def main():
    """并发执行多个I/O操作"""
    print("Starting async I/O operations...")

    # 并发读取多个文件
    tasks = [
        read_file_async('/etc/hostname'),
        read_file_async('/etc/os-release'),
        read_file_async('/proc/cpuinfo'),
    ]

    results = await asyncio.gather(*tasks)

    # 异步写入
    await write_file_async('/tmp/test_async.txt',
                          'Async I/O test\n' * 1000)

    print("All operations completed!")

if __name__ == '__main__':
    asyncio.run(main())

6. 零拷贝技术

6.1 传统I/O vs 零拷贝

┌─────────────────────────────────────────────────────────────┐
│              传统I/O的四次拷贝                                │
└─────────────────────────────────────────────────────────────┘

场景: 将文件通过网络发送

用户空间                  内核空间                  硬件
┌──────────┐            ┌──────────┐            ┌──────────┐
│          │            │          │            │          │
│ 应用缓冲区│            │ Page     │            │ 磁盘     │
│          │            │ Cache    │            │          │
│          │            │          │            │          │
└────┬─────┘            └────┬─────┘            └────┬─────┘
     │                       │                       │
     │  ① read()             │                       │
     │  ─────────────────▶   │                       │
     │                       │  ② DMA读              │
     │                       │  ◀─────────────────   │
     │                       │  (拷贝1: 磁盘→内核)    │
     │  ③ CPU拷贝            │                       │
     │  ◀─────────────────   │                       │
     │  (拷贝2: 内核→用户)    │                       │
     │                       │                       │
     │  ④ write()            │                       │
     │  ─────────────────▶   │                       │
     │  (拷贝3: 用户→内核)    │                       │
     │                       │                       │
┌──────────┐            ┌────▼─────┐            ┌──────────┐
│          │            │ Socket   │            │          │
│          │            │ Buffer   │            │ 网卡     │
│          │            │          │            │          │
└──────────┘            └────┬─────┘            └────┬─────┘
                             │  ⑤ DMA写              │
                             │  ─────────────────▶   │
                             │  (拷贝4: 内核→网卡)    │

总结:
• 4次拷贝: 2次DMA + 2次CPU
• 4次上下文切换: read()进入/返回, write()进入/返回
• 占用CPU资源,效率低

┌─────────────────────────────────────────────────────────────┐
│              零拷贝技术                                       │
└─────────────────────────────────────────────────────────────┘

方案1: mmap() + write()
┌──────────────────────────────────────────────────────────┐
│ 用户空间              内核空间              硬件          │
│ ┌─────────┐         ┌─────────┐         ┌─────────┐     │
│ │ 内存映射 │◀────────│ Page    │         │ 磁盘    │     │
│ │ 区域    │  共享   │ Cache   │◀─ DMA ─ │         │     │
│ └─────────┘         └────┬────┘         └─────────┘     │
│                          │                               │
│                          │                               │
│                     ┌────▼────┐         ┌─────────┐     │
│                     │ Socket  │─ DMA ─▶ │ 网卡    │     │
│                     │ Buffer  │         │         │     │
│                     └─────────┘         └─────────┘     │
│                                                          │
│ 优化: 3次拷贝 (DMA读 + CPU拷贝 + DMA写)                  │
│ 缺点: 数据仍需CPU拷贝到Socket Buffer                     │
└──────────────────────────────────────────────────────────┘

方案2: sendfile()
┌──────────────────────────────────────────────────────────┐
│ 用户空间              内核空间              硬件          │
│ ┌─────────┐         ┌─────────┐         ┌─────────┐     │
│ │ 应用    │         │ Page    │◀─ DMA ─ │ 磁盘    │     │
│ │ 进程    │         │ Cache   │         │         │     │
│ └─────────┘         └────┬────┘         └─────────┘     │
│     │                    │ CPU拷贝                       │
│     │ sendfile()         ▼                               │
│     │               ┌─────────┐         ┌─────────┐     │
│     └──────────────▶│ Socket  │─ DMA ─▶ │ 网卡    │     │
│                     │ Buffer  │         │         │     │
│                     └─────────┘         └─────────┘     │
│                                                          │
│ 优化: 3次拷贝 (DMA读 + CPU拷贝 + DMA写)                  │
│ 上下文切换: 2次                                          │
└──────────────────────────────────────────────────────────┘

方案3: sendfile() + DMA gather copy
┌──────────────────────────────────────────────────────────┐
│ 用户空间              内核空间              硬件          │
│ ┌─────────┐         ┌─────────┐         ┌─────────┐     │
│ │ 应用    │         │ Page    │◀─ DMA ─ │ 磁盘    │     │
│ │ 进程    │         │ Cache   │         │         │     │
│ └─────────┘         └────┬────┘         └─────────┘     │
│     │                    │ 传递文件描述符                 │
│     │ sendfile()         │ (不拷贝数据)                  │
│     │                    ▼                               │
│     │               ┌─────────┐                          │
│     └──────────────▶│ Socket  │                          │
│                     │ Buffer  │         ┌─────────┐     │
│                     │ (只含   │         │ 网卡    │     │
│                     │  描述符)│─ DMA ─▶ │ (gather)│     │
│                     └─────────┘  scatter└─────────┘     │
│                          │             ▲                 │
│                          └─────────────┘                 │
│                        DMA直接从Page Cache读取            │
│                                                          │
│ 优化: 2次拷贝 (DMA读 + DMA写), 真正的零拷贝!              │
│ 要求: 网卡支持scatter-gather DMA                         │
└──────────────────────────────────────────────────────────┘

方案4: splice()
┌──────────────────────────────────────────────────────────┐
│ 内核空间                                                 │
│ ┌─────────┐         ┌─────────┐         ┌─────────┐     │
│ │ 文件    │         │ Pipe    │         │ Socket  │     │
│ │ Page    │─────────│ Buffer  │─────────│ Buffer  │     │
│ │ Cache   │  splice │ (内核)  │  splice │         │     │
│ └─────────┘         └─────────┘         └─────────┘     │
│                                                          │
│ 特点: 在内核管道间传输数据,不经过用户空间                 │
│ 适用: 任意文件描述符间的数据传输                         │
└──────────────────────────────────────────────────────────┘

6.2 零拷贝代码示例

c
/*
 * sendfile()零拷贝示例 - 文件服务器
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define BACKLOG 10

int main(int argc, char *argv[])
{
    int server_fd, client_fd, file_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    struct stat file_stat;
    off_t offset;
    ssize_t sent;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        return 1;
    }

    /* 创建socket */
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket");
        return 1;
    }

    /* 设置socket选项 */
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    /* 绑定地址 */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&server_addr,
             sizeof(server_addr)) < 0) {
        perror("bind");
        close(server_fd);
        return 1;
    }

    /* 监听 */
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen");
        close(server_fd);
        return 1;
    }

    printf("Server listening on port %d\n", PORT);

    /* 打开要发送的文件 */
    file_fd = open(argv[1], O_RDONLY);
    if (file_fd < 0) {
        perror("open");
        close(server_fd);
        return 1;
    }

    /* 获取文件大小 */
    if (fstat(file_fd, &file_stat) < 0) {
        perror("fstat");
        close(file_fd);
        close(server_fd);
        return 1;
    }

    printf("File size: %ld bytes\n", file_stat.st_size);

    /* 接受连接 */
    client_fd = accept(server_fd, (struct sockaddr *)&client_addr,
                      &client_len);
    if (client_fd < 0) {
        perror("accept");
        close(file_fd);
        close(server_fd);
        return 1;
    }

    printf("Client connected: %s:%d\n",
           inet_ntoa(client_addr.sin_addr),
           ntohs(client_addr.sin_port));

    /* 使用sendfile()零拷贝发送文件 */
    offset = 0;
    sent = sendfile(client_fd, file_fd, &offset, file_stat.st_size);

    if (sent == file_stat.st_size) {
        printf("File sent successfully using zero-copy: %ld bytes\n", sent);
    } else {
        perror("sendfile");
    }

    /* 清理资源 */
    close(client_fd);
    close(file_fd);
    close(server_fd);

    return 0;
}

/*
 * splice()零拷贝示例
 */
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

#define SPLICE_SIZE (64*1024)

int main(int argc, char *argv[])
{
    int input_fd, output_fd;
    int pipefd[2];
    ssize_t bytes;

    if (argc < 3) {
        fprintf(stderr, "Usage: %s <input> <output>\n", argv[0]);
        return 1;
    }

    /* 打开输入文件 */
    input_fd = open(argv[1], O_RDONLY);
    if (input_fd < 0) {
        perror("open input");
        return 1;
    }

    /* 打开输出文件 */
    output_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (output_fd < 0) {
        perror("open output");
        close(input_fd);
        return 1;
    }

    /* 创建管道 */
    if (pipe(pipefd) < 0) {
        perror("pipe");
        close(input_fd);
        close(output_fd);
        return 1;
    }

    /* 使用splice()在文件间传输数据 */
    while (1) {
        /* 从文件splice到管道 */
        bytes = splice(input_fd, NULL, pipefd[1], NULL,
                      SPLICE_SIZE, SPLICE_F_MORE | SPLICE_F_MOVE);
        if (bytes < 0) {
            perror("splice input");
            break;
        }
        if (bytes == 0) {
            /* EOF */
            break;
        }

        /* 从管道splice到文件 */
        bytes = splice(pipefd[0], NULL, output_fd, NULL,
                      bytes, SPLICE_F_MORE | SPLICE_F_MOVE);
        if (bytes < 0) {
            perror("splice output");
            break;
        }
    }

    /* 清理资源 */
    close(pipefd[0]);
    close(pipefd[1]);
    close(input_fd);
    close(output_fd);

    printf("File copied using splice (zero-copy)\n");

    return 0;
}

7. I/O性能优化实战

7.1 查看I/O统计

bash
#!/bin/bash
# I/O性能监控脚本

echo "=== 磁盘I/O统计 ==="
iostat -x 1 3

echo -e "\n=== 每个进程的I/O ==="
iotop -b -n 1

echo -e "\n=== 磁盘调度器 ==="
for disk in /sys/block/sd*/queue/scheduler; do
    echo "$disk: $(cat $disk)"
done

echo -e "\n=== 页缓存统计 ==="
cat /proc/meminfo | grep -E "Cached|Dirty|Writeback"

echo -e "\n=== 块设备队列深度 ==="
for disk in /sys/block/sd*/queue/nr_requests; do
    echo "$disk: $(cat $disk)"
done
python
#!/usr/bin/env python3
"""
I/O性能分析工具
"""
import os
import time

def get_disk_stats():
    """读取/proc/diskstats"""
    stats = {}
    with open('/proc/diskstats', 'r') as f:
        for line in f:
            fields = line.split()
            if len(fields) < 14:
                continue

            device = fields[2]
            if not device.startswith('sd') or len(device) > 3:
                continue

            stats[device] = {
                'reads': int(fields[3]),
                'reads_merged': int(fields[4]),
                'sectors_read': int(fields[5]),
                'read_time': int(fields[6]),
                'writes': int(fields[7]),
                'writes_merged': int(fields[8]),
                'sectors_written': int(fields[9]),
                'write_time': int(fields[10]),
                'io_in_progress': int(fields[11]),
                'io_time': int(fields[12]),
            }

    return stats

def calculate_iops(stats1, stats2, interval):
    """计算IOPS"""
    result = {}

    for device in stats1:
        if device not in stats2:
            continue

        reads = (stats2[device]['reads'] - stats1[device]['reads']) / interval
        writes = (stats2[device]['writes'] - stats1[device]['writes']) / interval

        # 计算吞吐量 (扇区 * 512字节)
        read_mb = (stats2[device]['sectors_read'] -
                  stats1[device]['sectors_read']) * 512 / 1024 / 1024 / interval
        write_mb = (stats2[device]['sectors_written'] -
                   stats1[device]['sectors_written']) * 512 / 1024 / 1024 / interval

        result[device] = {
            'read_iops': reads,
            'write_iops': writes,
            'total_iops': reads + writes,
            'read_mb_s': read_mb,
            'write_mb_s': write_mb,
            'total_mb_s': read_mb + write_mb,
        }

    return result

def main():
    print("Monitoring I/O performance (Ctrl+C to stop)...\n")

    try:
        while True:
            stats1 = get_disk_stats()
            time.sleep(1)
            stats2 = get_disk_stats()

            iops = calculate_iops(stats1, stats2, 1)

            print("\033[H\033[J")  # 清屏
            print(f"{'Device':<10} {'R IOPS':<10} {'W IOPS':<10} "
                  f"{'Total':<10} {'R MB/s':<10} {'W MB/s':<10} {'Total MB/s':<10}")
            print("-" * 70)

            for device, stats in sorted(iops.items()):
                print(f"{device:<10} "
                      f"{stats['read_iops']:<10.2f} "
                      f"{stats['write_iops']:<10.2f} "
                      f"{stats['total_iops']:<10.2f} "
                      f"{stats['read_mb_s']:<10.2f} "
                      f"{stats['write_mb_s']:<10.2f} "
                      f"{stats['total_mb_s']:<10.2f}")

    except KeyboardInterrupt:
        print("\nStopped.")

if __name__ == '__main__':
    main()

7.2 I/O调优建议

bash
#!/bin/bash
# I/O调优脚本

DEVICE="sda"

echo "=== I/O调优配置 ==="

# 1. 切换到deadline调度器(数据库场景)
echo "1. 设置I/O调度器为deadline"
echo deadline > /sys/block/$DEVICE/queue/scheduler

# 2. 增加读提前量
echo "2. 设置read_ahead_kb"
echo 512 > /sys/block/$DEVICE/queue/read_ahead_kb

# 3. 增加队列深度
echo "3. 设置nr_requests"
echo 256 > /sys/block/$DEVICE/queue/nr_requests

# 4. 调整vm参数
echo "4. 调整虚拟内存参数"
sysctl -w vm.dirty_ratio=10
sysctl -w vm.dirty_background_ratio=5
sysctl -w vm.dirty_writeback_centisecs=100
sysctl -w vm.dirty_expire_centisecs=200

# 5. 文件系统挂载选项
echo "5. 优化挂载选项"
echo "建议: mount -o noatime,nodiratime,data=writeback /dev/$DEVICE /mnt"

# 6. SSD优化
if [ -f /sys/block/$DEVICE/queue/rotational ]; then
    if [ "$(cat /sys/block/$DEVICE/queue/rotational)" = "0" ]; then
        echo "6. 检测到SSD,应用SSD优化"
        echo noop > /sys/block/$DEVICE/queue/scheduler
        echo 0 > /sys/block/$DEVICE/queue/add_random
        echo "建议启用TRIM: fstrim -v /"
    fi
fi

echo ""
echo "=== 当前配置 ==="
echo "Scheduler: $(cat /sys/block/$DEVICE/queue/scheduler)"
echo "Read ahead: $(cat /sys/block/$DEVICE/queue/read_ahead_kb) KB"
echo "Queue depth: $(cat /sys/block/$DEVICE/queue/nr_requests)"
echo "Dirty ratio: $(sysctl -n vm.dirty_ratio)"
echo "Dirty background: $(sysctl -n vm.dirty_background_ratio)"

8. 总结

本教程深入讲解了操作系统的I/O管理机制:

核心知识点:

  1. I/O硬件基础: 设备分类、I/O控制方式、中断机制
  2. I/O调度: 磁盘调度算法、Linux调度器演进
  3. 缓冲缓存: Buffer Cache、Page Cache、LRU算法
  4. 设备驱动: 驱动架构、字符设备、中断处理
  5. 异步I/O: I/O模型对比、Linux AIO
  6. 零拷贝: sendfile、splice、性能优化

实战技能:

  • 编写字符设备驱动
  • 使用异步I/O提升性能
  • 应用零拷贝技术
  • I/O性能监控与调优

最佳实践:

  1. 高并发场景使用I/O多路复用
  2. 大文件传输使用零拷贝
  3. SSD使用noop调度器
  4. 数据库使用deadline调度器
  5. 合理配置vm参数
  6. 定期监控I/O性能

掌握I/O管理是理解操作系统和性能优化的关键!

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布