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)"
donepython
#!/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管理机制:
核心知识点:
- I/O硬件基础: 设备分类、I/O控制方式、中断机制
- I/O调度: 磁盘调度算法、Linux调度器演进
- 缓冲缓存: Buffer Cache、Page Cache、LRU算法
- 设备驱动: 驱动架构、字符设备、中断处理
- 异步I/O: I/O模型对比、Linux AIO
- 零拷贝: sendfile、splice、性能优化
实战技能:
- 编写字符设备驱动
- 使用异步I/O提升性能
- 应用零拷贝技术
- I/O性能监控与调优
最佳实践:
- 高并发场景使用I/O多路复用
- 大文件传输使用零拷贝
- SSD使用noop调度器
- 数据库使用deadline调度器
- 合理配置vm参数
- 定期监控I/O性能
掌握I/O管理是理解操作系统和性能优化的关键!
💬 讨论
使用 GitHub 账号登录后即可参与讨论