3.1.2 Linux存储层次结构与I/O路径
本节从自上而下梳理 Linux 存储层次结构与 I/O 路径,帮助理解一次读写从应用到磁盘(或网络存储)的完整链路,便于定位性能瓶颈与故障点。
存储层次结构概览#
- 应用层:系统调用读写接口(read/write/append、mmap 等),决定同步/异步、顺序/随机等访问模式。
- VFS 层:统一文件系统接口,屏蔽 ext4/xfs/btrfs 等差异,完成路径解析与权限校验。
- 文件系统层:负责元数据管理、日志(journaling)、空间分配与缓存一致性。
- 页缓存(Page Cache):内存缓存热数据与预读,影响 I/O 命中率与写回行为。
- 块层(Block Layer):I/O 合并、调度与队列管理,决定 IOPS 与延迟表现。
- 设备映射层(DM):LVM/RAID/加密等逻辑抽象与映射。
- 驱动与控制器:SATA/SAS/NVMe/FC/iSCSI 驱动与 HBA。
- 物理介质:SSD/HDD/NVMe、存储阵列或网络存储后端。
I/O 路径(读写流程)#
sequenceDiagram
participant App as 应用
participant VFS as VFS
participant FS as 文件系统
participant PC as 页缓存
participant BLK as 块层
participant DM as 设备映射
participant DRV as 驱动
participant DEV as 介质
App->>VFS: read/write
VFS->>FS: 路径与权限
FS->>PC: 查询/更新缓存
PC->>BLK: 未命中/写回
BLK->>DM: 调度与合并
DM->>DRV: 逻辑映射
DRV->>DEV: 访问介质
DEV-->>App: 返回数据/完成
-
读路径(Read)
1) 应用发起读 → 2) VFS 找到 inode → 3) 文件系统判断是否命中页缓存
4) 未命中则构造块 I/O → 5) 块层合并/调度 → 6) 设备映射 → 7) 驱动 → 8) 介质读取 → 9) 回填缓存 → 10) 返回用户态 -
写路径(Write)
1) 应用写入 → 2) 进入页缓存(脏页)
3) 文件系统记录元数据/日志 → 4) 写回线程/刷盘触发
5) 块层调度 → 6) 设备映射 → 7) 驱动 → 8) 介质持久化关键点:写入返回时是否落盘取决于同步语义(sync、fsync、O_DIRECT 等)
示例:观察 I/O 路径与缓存行为#
1) 安装/准备工具#
# Debian/Ubuntu
apt-get update
apt-get install -y sysstat iotop blktrace fio
# RHEL/CentOS
yum install -y sysstat iotop blktrace fio
2) 创建测试文件与读写对比#
# 创建 1GiB 测试文件(顺序写)
dd if=/dev/zero of=/var/tmp/io_test.img bs=1M count=1024 oflag=direct status=progress
# 顺序读(绕过页缓存)
dd if=/var/tmp/io_test.img of=/dev/null bs=1M iflag=direct status=progress
# 普通读(可能命中页缓存)
dd if=/var/tmp/io_test.img of=/dev/null bs=1M status=progress
命令解释
- oflag=direct:直接 I/O,绕过页缓存,观察真实介质性能。
- iflag=direct:同上,读时绕过缓存。
- status=progress:显示吞吐进度。
预期效果
- 首次普通读吞吐较低,第二次普通读明显提升(页缓存命中)。
- direct 读写更接近设备真实能力,但 CPU 负担更高。
3) 观察块层调度器与队列深度#
# 查看当前设备调度器(以 sda 为例)
cat /sys/block/sda/queue/scheduler
# 临时切换调度器(none/mq-deadline/deadline/bfq)
echo mq-deadline > /sys/block/sda/queue/scheduler
# 查看队列深度
cat /sys/block/sda/queue/nr_requests
命令解释
- scheduler:显示并切换 I/O 调度器。
- nr_requests:块层队列深度,影响吞吐与延迟。
缓存与一致性影响#
- 页缓存:提高读性能,但可能引入“写入已完成但未落盘”的风险。
- 写回策略:由内核参数决定(如 dirty_ratio、dirty_background_ratio)。
- 文件系统日志:保证元数据一致性,部分文件系统支持数据日志。
示例:查看与调整写回参数(临时)#
# 查看当前写回策略
sysctl vm.dirty_ratio vm.dirty_background_ratio vm.dirty_expire_centisecs
# 临时调整(立即生效,重启失效)
sysctl -w vm.dirty_ratio=20
sysctl -w vm.dirty_background_ratio=10
sysctl -w vm.dirty_expire_centisecs=3000
命令解释
- dirty_ratio:脏页占比阈值,超过将触发强制回写。
- dirty_background_ratio:后台回写阈值。
- dirty_expire_centisecs:脏页最大停留时间。
块层调度与队列#
- I/O 调度器:deadline、mq-deadline、none、bfq 等。
- 多队列(blk-mq):提升多核并发能力,避免单队列锁争用。
- 队列深度:影响吞吐与延迟,需与硬件特性匹配。
示例:fio 模拟不同访问模式#
# 随机读 4K,观察 IOPS 与延迟
fio --name=randread --filename=/var/tmp/io_test.img \
--rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=30 --time_based --direct=1
# 顺序写 1M,观察吞吐
fio --name=seqwrite --filename=/var/tmp/io_test.img \
--rw=write --bs=1m --iodepth=16 --numjobs=1 --runtime=30 --time_based --direct=1
命令解释
- --direct=1:绕过页缓存,更接近设备真实性能。
- --iodepth:队列深度,影响吞吐与延迟。
典型瓶颈位置#
- 页缓存不足:缓存命中率下降,随机读增多。
- 块层调度不当:导致延迟抖动或吞吐受限。
- 设备映射层:LVM/RAID 复杂度提升开销。
- 驱动/控制器:队列深度、固件限制或中断过高。
- 介质特性:HDD 随机性能弱、SSD 耐久与写放大问题。
故障排查示例#
# 1) 定位高 I/O 进程
iotop -oPa
# 2) 查看设备 I/O 延迟与队列
iostat -x 1 5
# 3) 追踪块层事件(短时间)
blktrace -d /dev/sda -o - | blkparse -i -
# 4) 观察页缓存与脏页
grep -E 'Dirty|Writeback|Cached' /proc/meminfo
命令解释
- iotop -oPa:只显示有 I/O 的进程,聚合线程。
- iostat -x:关注 await、svctm、%util。
- blktrace:查看 I/O 进入/完成链路时间。
运维实践要点#
- 结合业务访问模式选择 I/O 语义(sync/async、direct I/O)。
- 对高并发 SSD/NVMe 使用合适调度器(如 none/mq-deadline)。
- 监控缓存与写回行为,避免“脏页堆积”造成抖动。
- 评估 LVM/RAID 对延迟与吞吐的影响,必要时简化链路。
- 结合 iostat、iotop、blktrace 等工具定位具体层级问题。
练习#
- 使用
dd对比 direct I/O 与普通 I/O 的读写速度,记录 3 次结果并分析缓存命中影响。 - 切换
scheduler为none与mq-deadline,用fio测试 4K 随机读,比较 IOPS 与平均延迟。 - 人为制造脏页(大文件写入),观察
/proc/meminfo的 Dirty/Writeback 变化,并解释回写触发时机。 - 使用
iostat -x监控await、%util,判断是设备瓶颈还是队列堆积导致的延迟。