从磁盘到内存:一次文件读取的 CPU-DMA 协作之旅
当你在 Linux 上执行
cat /etc/passwd时,数据从磁盘到屏幕看似一瞬间,背后却是一场 CPU、DMA 控制器、内存控制器、Cache 控制器协同演出的精密舞蹈。本文用一个具体实例,把这个过程彻底讲清楚。
动机:为什么需要理解这个?
很多人知道"读取文件会经过 PageCache",但对DMA 是什么、CPU 如何配置它、数据如何在硬件层级流动缺乏直觉。
理解这个过程,你会:
- 搞清楚为什么 SSD 比机械硬盘快那么多— 关键不在于顺序读写,而在于寻道时间和 IOPS,本质是 DMA 中断次数和总线等待的差异
- 理解
mmap和direct I/O的本质差异— mmap 绕过 PageCache,direct I/O 绕过 PageCache + Cache,两者各有权衡 - 理解内核中断处理和上下文切换的开销来源— 一次磁盘读可能触发数十次中断,每次中断都是 CPU 的强制停机
- 对性能优化有更底层的感觉— 知道为什么"预读"有效、"合并写"省力、"零拷贝"值钱
场景:一次完整的cat /etc/passwd调用链
先给出从用户敲命令到数据出现在屏幕上的完整路径,后续章节会逐段拆解。
cat/etc/passwdcat进程执行read(fd, buf, 4096),背后触发的完整流程如下:
┌──────────────────────────────────────────────────────────────────────┐ │ 用户态(User Space) │ │ │ │ cat 进程 │ │ app 发起 read(fd, buf, 4096) ← libc 封装 │ │ ↓ │ │ int 0x80(x86 软中断) ← 触发系统调用 │ │ ↓ │ │ CPU 从用户态切换到内核态 │ └──────────────┬─────────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────────────────┐ │ 内核态(Kernel Space) │ │ │ │ 1. 查中断描述符表 IDT,128 号条目 = 系统调用入口(sys_read) │ │ ↓ │ │ 2. sys_read() 执行:检查 PageCache 中是否有该文件的缓存页 │ │ ↓ │ │ ┌──────────────────┬────────────────────────────────┐ │ │ │ PageCache 命中 ✓ │ PageCache 未命中 ✗ │ │ │ │ ↓ │ ↓ │ │ │ │ 直接用 copy_to_user() │ 向磁盘驱动发起 I/O 请求 │ │ │ │ 把数据从 PageCache │ ↓ │ │ │ │ 拷贝到 app 的 buf │ 分配一个空 PageCache 页 │ │ │ │ ← 返回给 cat 进程 │ ↓ │ │ │ │ │ 配置 DMA 控制器(MMIO) │ │ │ │ │ ↓ │ │ │ │ │ DMA 从磁盘读取 → 写入 │ │ │ │ │ PageCache 页中 │ │ │ │ │ ↓ │ │ │ │ │ copy_to_user() 拷贝到 │ │ │ │ │ app 的 buf │ │ │ │ │ ← 返回给 cat 进程 │ │ │ └──────────────────────┴────────────────────────────────┘ │ └──────────────┬─────────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────────────────┐ │ 返回用户态(Return to User) │ │ │ │ cat 进程收到 read() 返回值,打印到 stdout → 终端屏幕 │ └──────────────────────────────────────────────────────────────────────┘注意关键路径区分:
| 路径 | 触发 DMA? | 触发中断? | 速度 |
|---|---|---|---|
| PageCache命中 | ❌ 不需要 |
