Skip to content

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 为例:

  1. write() 返回 ≠ 落盘。数据先进入 Page Cache,标记为脏页(dirty),系统调用立即返回 —— 这就是为什么"写完就断电"会丢数据。
  2. Writeback:内核按阈值(vm.dirty_ratio 等)或 fsync() 触发回写,脏页变成 bio 交给块层。
  3. 块层:多个 bio 可能被合并(merge),经过 IO 调度器排队 —— 这里是"IO 排队"的第一现场,iostataqu-sz 列就是在这层观测。
  4. 驱动与设备:NVMe 有很深的提交队列(多队列),设备内部还有 FTL 的映射与 GC —— 顺序写变随机写的经典问题就出在这。
  5. 完成与中断:设备完成 IO,中断/轮询收回,iostatawait 在此闭合。

Direct I/O(数据库、Ceph OSD 的默认数据路径)跳过 Page Cache,write() 直接构造 bio 下发 —— 用 CPU 开销换可预测的延迟。

1.3 每层都可能排队

排队点观测手段(预告)
VFS/文件系统inode 锁、日志提交perfcat /proc/locks
Page Cache脏页回写堆积sar -B/proc/vmstat
块层调度队列iostat -xaqu-sz/await
设备盘内部队列、GCiostatutil、fio 压测
网络(远程存储)重传、拥塞ping RTT、ethtool、BCC 工具

1.4 分布式视角的三个伏笔

这三件事在第二篇会反复出现,现在先留下印象:

  1. PG 与对象:Ceph 把文件切成 4MiB 对象、对象归属 PG —— 概念上对应本篇的"文件→块"切分,只是切分后要跨网络写多副本。
  2. fsync 语义:RBD 的镜像、RGW 的数据安全都依赖 fsync 语义正确传递到 OSD 的落盘 —— 本篇的 writeback 知识是理解它的地基。
  3. 网络就是新的总线:单机栈里 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 · 亲手摸一次存储栈入门
⏱ 20 分钟🖥 任意一台 Linux(虚拟机/WSL2 均可)
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 分别在栈的哪一层被观测到"。

下一章:2. 观测工具箱 →