1. Linux 存储栈全景
本章目标
- 画出一次文件写入从系统调用到磁盘的完整路径
- 说出每一层"做了什么、可能在哪里排队"
- 理解 Direct I/O 与 Buffered I/O 的分叉点
1.1 一图看懂存储栈
text
┌─────────────────────────────────────────────┐
│ 应用程序 write(fd, buf, 4096) │ ← 第二篇的 RBD/QEMU、RGW 都在这层
├─────────────────────────────────────────────┤
│ 系统调用层 VFS │ ← 虚拟文件系统:统一接口
├─────────────────────────────────────────────┤
│ 文件系统 XFS / ext4 / CephFS client ... │ ← 语义:日志、inode、目录项
├─────────────────────────────────────────────┤
│ 页缓存 Page Cache │ ← 写入通常到这里就返回了!
├─────────────────────────────────────────────┤
│ 块层 Block Layer (bio/request) │ ← 合并、排队、调度 (mq-deadline...)
├─────────────────────────────────────────────┤
│ 设备驱动 NVMe / SCSI / virtio-blk │
├─────────────────────────────────────────────┤
│ 硬件 本地盘 / iSCSI 目标 / NVMe-oF / 网络HBA │ ← 分布式存储从这里"变成网络"
└─────────────────────────────────────────────┘关键认知:分布式存储 = 把这张图的下半部分换成了网络对端。Ceph 客户端在本地实现文件/块语义,真实数据落在远端 OSD。所以本地栈的每一层知识都直接迁移。
1.2 一次 write() 的旅程
以默认的 Buffered I/O 写 4KiB 为例:
write()返回 ≠ 落盘。数据先进入 Page Cache,标记为脏页(dirty),系统调用立即返回 —— 这就是为什么"写完就断电"会丢数据。- Writeback:内核按阈值(
vm.dirty_ratio等)或fsync()触发回写,脏页变成 bio 交给块层。 - 块层:多个 bio 可能被合并(merge),经过 IO 调度器排队 —— 这里是"IO 排队"的第一现场,
iostat的aqu-sz列就是在这层观测。 - 驱动与设备:NVMe 有很深的提交队列(多队列),设备内部还有 FTL 的映射与 GC —— 顺序写变随机写的经典问题就出在这。
- 完成与中断:设备完成 IO,中断/轮询收回,
iostat的await在此闭合。
而 Direct I/O(数据库、Ceph OSD 的默认数据路径)跳过 Page Cache,write() 直接构造 bio 下发 —— 用 CPU 开销换可预测的延迟。
1.3 每层都可能排队
| 层 | 排队点 | 观测手段(预告) |
|---|---|---|
| VFS/文件系统 | inode 锁、日志提交 | perf、cat /proc/locks |
| Page Cache | 脏页回写堆积 | sar -B、/proc/vmstat |
| 块层 | 调度队列 | iostat -x 的 aqu-sz/await |
| 设备 | 盘内部队列、GC | iostat 的 util、fio 压测 |
| 网络(远程存储) | 重传、拥塞 | ping RTT、ethtool、BCC 工具 |
1.4 分布式视角的三个伏笔
这三件事在第二篇会反复出现,现在先留下印象:
- PG 与对象:Ceph 把文件切成 4MiB 对象、对象归属 PG —— 概念上对应本篇的"文件→块"切分,只是切分后要跨网络写多副本。
- fsync 语义:RBD 的镜像、RGW 的数据安全都依赖 fsync 语义正确传递到 OSD 的落盘 —— 本篇的 writeback 知识是理解它的地基。
- 网络就是新的总线:单机栈里 NVMe 延迟 ~20µs,而一次跨机房 RTT 可能 500µs —— 这就是为什么 Ceph 的写延迟永远做不到本地 NVMe,也是 GPFS/Weka 拼命优化网络路径的原因。
本章小结
- 存储栈是分层的,每层有职责也有排队点
- Buffered I/O 与 Direct I/O 的分叉在 Page Cache
- 分布式存储把栈的下半部分换成网络,其余知识全部复用
自测
Quiz默认 Buffered I/O 下,write() 系统调用返回时,数据最可能位于哪里?
多选以下哪些环节可能造成「应用感知到 IO 变慢」?(多选)
Lab · 亲手摸一次存储栈入门
1. 用 `dd if=/dev/zero of=/tmp/t bs=1M count=512 oflag=direct` 和不带 `oflag=direct` 各写一次,用 `time` 对比耗时,解释差异(提示:Page Cache)。 2. 写入过程中另开终端跑 `iostat -x 1`(没有则 `apt install sysstat`),盯住 `w/s`、`wkB/s`、`aqu-sz`、`await` 四列。 3. 执行 `cat /proc/meminfo | grep -i dirty`,观察脏页数量在写入前后的变化。 4. **验收标准**:用自己的话说出"direct 模式快/慢在哪里、await 和 aqu-sz 分别在栈的哪一层被观测到"。