当前位置:首页>Linux>Linux存储栈全景-VFS到块设备层

Linux存储栈全景-VFS到块设备层

  • 2026-10-11 05:35:33
Linux存储栈全景-VFS到块设备层

Linux 存储栈全景:从 VFS 到块设备层的 IO 路径解剖

前言

一个ead() 系统调用,从敲下去到数据返回,中间穿越了多少层?如果你答不上来,那你永远无法真正理解"为啥我的 MySQL 写入这么慢"或"Nginx 大文件传输为何卡住"。

本文带你走一遍 Linux 存储 IO 的完整链路:从 VFS 虚拟文件系统 → 具体文件系统 → Page Cache → 块层(Block Layer)→ I/O 调度器 → 块设备驱动 → 硬件。每层做什么、能卡在哪、怎么观测,一次讲透。

01

PART 01:七层架构全景

每一层都是延迟放大器,也都是优化切入点。我们从最上面开始一层层往下拆。

02

PART 02:VFS —— 统一江湖的抽象层

VFS(Virtual File System)是 Linux 最伟大的设计之一。它定义了文件系统必须实现的通用接口。

四个核心对象

对象
作用
对应结构体
**super_block**
已挂载文件系统的超级块
struct super_block
**inode**
文件的元数据(权限、大小、位置)
struct inode
**dentry**
目录项,维护路径到 inode 映射
struct dentry
**file**
进程打开的文件描述符状态
struct file

当你执行 open("/data/log/app.log", O_RDWR):

  1. VFS 通过 dentry cache 查找 /data/log/app.log 的目录路径
  2. 逐步解析 dentry,找到 inode
  3. 调用底层文件系统的 open 回调
  4. 返回一个 struct file 挂到进程文件表

关键观测:

`

slabtop -o | grep dentry

cat /proc/sys/fs/inode-nr

cat /proc/sys/fs/file-max

`

卡点:dentry cache 过大导致 slab 内存吃紧、路径名查找锁竞争(特别是 dcache_lock 时代,RCU 已改善)。

03

PART 03:具体文件系统 —— 数据到底存哪了

VFS 把请求分发到实际的文件系统(ext4 / xfs / btrfs / zfs)。这里才是真正决定数据组织的地方。

ext4 的 IO 路径

write() → VFS → ext4_write_begin() → ext4_reserve_space (分配物理块) → ext4_journal_start (事务开始) → ext4_write_end (数据写入页缓存) → ext4_journal_stop (事务提交) → 等待日志写入 Journal (JBD2) → 数据最终回写到 inode 指向的数据块

延迟关键点

1. 日志(Journal)写入

ext4 data=ordered 模式下,元数据先写日志再写磁盘。日志本身是顺序 IO,看似快,但 fsync 时 必须等待日志落盘,这就是 PostgreSQL 常说"ext4 barrier 问题"的来源。

`

tune2fs -l /dev/sda1 | grep -i journal

mount -o data=writeback /dev/sda1 /data

`

2. 分配器碎片

ext4 使用 extents 树(B+树变体),大文件连续分配没问题,但小文件长期写入导致 extent 分裂,IO 路径变长。

不同文件系统的 IO 路径对比

环节
ext4
xfs
btrfs
zfs
元数据日志
JBD2 独立日志
日志区固定分配
CoW 无需日志
ZIL + intent log
空间分配
mballoc
延迟分配 + 块组
树状分配
虚拟设备层
延迟分配
支持
**原生优势**
支持
支持
CoW
否
否
是(默认)
是
适合场景
通用、成熟
大文件、高并发
快照、校验
数据安全、大规模

04

PART 04:Page Cache —— 被忽略的性能放大器

是什么

Linux 会把读到的磁盘数据缓存在内存中,这个区域叫 Page Cache,以 4KB 页为基本单位。

三个最关键的参数

`

/proc/sys/vm/dirty_ratio # 默认 20(%),脏页占内存比例达到此值阻塞写入进程

/proc/sys/vm/dirty_background_ratio # 默认 10(%),后台 pdflush 开始回写

/proc/sys/vm/dirty_expire_centisecs # 默认 3000(30s),脏页最大存活时间

`

血泪教训:Page Cache 导致的 MySQL 双写缓冲

很多 MySQL DBA 会发现:明明数据库有 Buffer Pool,磁盘写入还是慢。原因之一是 Page Cache 的写回机制造成了额外的 IO。

MySQL 通过 innodb_flush_method=O_DIRECT 跳过 Page Cache(直接写入磁盘),但这又把压力扔给了磁盘——需要数据库自身的 Buffer Pool 足够大。

更推荐的折中:innodb_flush_method=O_DIRECT_NO_FSYNC(MySQL 8.0+ 支持)

观测命令:

`

grep -E "^Cached|^Buffers" /proc/meminfo

cat /proc/vmstat | grep dirty

sync

`

05

PART 05:Block Layer —— 通用块层

Page Cache 下层就是 通用块层(Generic Block Layer)。它负责:

  1. 将文件系统发来的 bio(Block IO 请求)合并与排序
  2. 抽象出统一的块设备接口(不关心底下是 SSD 还是 HDD)
  3. 传递给 I/O 调度器

Plugging —— 内核态小批量合并

write() → 页缓存 → 生成 bio → 加入当前 CPU 的 plug list → schedule() 或特意 unplug 时 → bio 合并、排序 → 提交给调度器

这个机制减少了调度器的调用次数,是 Linux 内核 IO 高性能的关键设计之一。

06

PART 06:I/O 调度器 —— 把乱序变成有序

I/O 调度器决定 BIO 的下发顺序。历史上 Linux 有多个调度器:

六种调度器对比

调度器
内核版本
核心算法
适合场景
特点
**Noop**
2.6+
FIFO + 简单合并
纯 NVMe SSD
延迟最低,依赖硬件做排序
**Deadline**
2.6+
按截止时间排序
混合负载
保证读请求不饿死,分读写队列
**CFQ**
2.6+
时间片分配
HDD、桌面
公平但吞吐低,已废弃
**mq-deadline**
4.x+
多队列版 Deadline
NVMe 多队列
现代标配
**BFQ**
5.x+
权重预算分配
交互式、桌面
延迟极小,但 CPU 开销高
**Kyber**
4.x+
基于延迟的闭环控制
云环境
自适应,无需调参

NVMe 时代的选择

从内核 5.0 起,多队列块层(blk-mq)已成为默认:

`

cat /sys/block/nvme0n1/queue/scheduler

echo none > /sys/block/nvme0n1/queue/scheduler

`

核心结论:NVMe SSD → none;SATA SSD → mq-deadline;HDD → BFQ 或 mq-deadline

07

PART 07:块设备驱动与硬件

NVMe 协议 vs 传统 SATA

传统 SATA(AHCI)是单队列模型,锁竞争严重。NVMe 支持 64K 队列对,每个 CPU 可独占队列,无锁设计、中断聚合。NVMe 把 IOPS 从 SATA SSD 的 ~10 万推到百万级,瓶颈从硬件转移到内核栈本身——这也是 io_uring、SPDK 应运而生的原因。

08

PART 08:完整 IO 路径延迟拆解

以 NVMe SSD 写 4KB 数据为例:

阶段
延迟(约)
累计
可优化空间
系统调用上下文切换
~100ns
0.1us
syscall 减少
VFS 权限检查
~200ns
0.3us
无(安全必须)
dentry/inode 查找
~500ns
0.8us
dentry cache 命中率
ext4 分配 + 日志
~3us
3.8us
延迟分配、日志模式
Page Cache 写入
~200ns
4.0us
无(内存操作)
调度器排队
~2us
6.0us
调度器选择
NVMe 驱动 PCIe DMA
~7us
13us
硬件升级
NAND 闪存写入
~30us
**43us**
硬件升级

总共约 43us,其中真正的物理写入占 70%。优化空间主要在前三段软件栈。

09

PART 09:全链路排查工具箱

分层
工具
观测内容
VFS / 文件系统
strace -e trace=read,write,open,fsync
系统调用耗时
文件系统
iostat -x 1
await、svctm、%util
Page Cache
/proc/meminfo
Cached、Dirty、Writeback
I/O 调度器
/sys/block/*/queue/scheduler
调度器类型与参数
Block 层
lktrace -d /dev/sda -o -
blkparse
每阶段延迟拆分
驱动 / 硬件

vme-cli list / smartctl

 设备健康、温度、队列数 

最常用的三板斧

  1. 定位 IO 是否在等盘:iostat -x 1,r_await > 10ms 说明盘在忙
  2. 看 Page Cache 刷盘压力:cat /proc/meminfo | grep -E "Dirty|Writeback"
  3. 看调度器队列深度:cat /sys/block/nvme0n1/queue/nr_requests

`

总结

APP → VFS → 具体 FS → Page Cache → Block Layer → I/O Scheduler → 驱动 → 磁盘

每一层都有观测工具,每一层都有调优空间。不懂全景,调优就是瞎蒙。

下一篇我们进入 文件系统选型对决,用实测数据对比 xfs/ext4/btrfs/zfs 在不同场景下的真实表现。

预告:《文件系统选型对决:XFS vs ext4 vs Btrfs vs ZFS 性能与场景深度对比》

END

最新文章

随机文章