《Linux IO 栈与存储性能调优:从块层到 io_uring》

深入 Linux IO 栈:从 VFS、页缓存、文件系统到块层与设备驱动,讲解缓冲 IO 与直接 IO 的区别、blk-mq 与 mq-deadline/none 调度器、io_uring 异步 IO 原理、RAID/LVM 布局、iostat/iowait 指标解读、fio 压测方法,以及一套可落地的存储性能调优清单。

引言

一次 read() 从应用出发,要穿过 VFS、页缓存、文件系统、块层调度器、设备驱动,最终到达磁盘——这条IO 栈上每一层都在决定延迟与吞吐。本文自上而下拆解每层职责,对比缓冲 IO 与直接 IO、认识多队列时代的 mq-deadline/none 调度器、介绍异步 IO 新贵 io_uring,再落到 RAID/LVM 布局与 iostat/fio 这两把「测量之尺」,最后给出一份存储性能调优清单。

前置:/linux-filesystem-disk/(文件系统与磁盘基础)。性能监控方法论见 /linux-performance-tuning/。


目录


1. IO 栈全景:从 VFS 到设备

IO 栈是分层的管道,每层只做一件事,层与层之间通过通用接口解耦:

应用层       open/read/write/fsync
  ↓         VFS(虚拟文件系统:抽象所有文件系统)
页缓存层     缓存热数据、异步写回
  ↓         具体文件系统(ext4/xfs/btrfs…)→ 块映射
块层        blk-mq 队列 + 调度器(mq-deadline/none)
  ↓         设备驱动 → DMA
设备         SSD/NVMe/HDD
# 看设备拓扑
lsblk
# 看挂载点用的文件系统
df -hT
# 看每个块设备当前使用的 IO 调度器
cat /sys/block/sda/queue/scheduler
层职责关键对象
VFS统一 open/read/write 接口文件、inode、dentry
页缓存读命中、写合并、异步落盘page cache、dirty pages
文件系统逻辑块 ↔ 物理块映射、元数据ext4/xfs、inode、日志
块层队列、合并、排序、调度blk-mq、request queue
设备层与硬件交换数据驱动、DMA、中断

心智:「慢」可能来自任何一层——先定位再优化。文件系统元数据慢、调度器合并差、设备本身慢,症状都是「IO 慢」,但解法完全不同。


2. VFS 与页缓存

页缓存(page cache)是 IO 栈的「内存加速器」:读过的页留在 RAM,写先落缓存再异步刷盘。

# 当前脏页与缓存量
grep -E 'Dirty|Writeback|PageTables|Cached' /proc/meminfo
# 手动刷盘
sync
# 文件 IO 是否命中缓存(cache hit 高说明工作集已热)
# 可用 cachestat(bcc 工具)或 strace 观测

写路径是典型的写回(write-back):write() 只把数据拷进页缓存就返回,内核按水位线异步写回磁盘。这也是「明明写了,突然断电丢数据」的原因——fsync/fdatasync 才保证落盘。

# 强制落盘
fsync 是系统调用,命令行里常用 sync
# 关闭某目录的「写缓存延迟」不现实,事务型应用自行 fsync
# 例如 SQLite/PostgreSQL 都有 fsync 相关参数

记忆:页缓存让「重复读」和「集中写」都变快,但它不是内存泄漏。free 里的 buff/cache 高是好事;真正要盯的是 Dirty——如果 Dirty 持续很大且磁盘写不出去,才是瓶颈。


3. 缓冲 IO 与直接 IO

缓冲 IO 走页缓存(默认),直接 IO(O_DIRECT)绕过页缓存直达磁盘——各有适用场景:

对比缓冲 IO直接 IO (O_DIRECT)
是否过页缓存是(可复用热数据)否(每次直达磁盘)
写返回时机写进缓存即返回提交给设备才返回
适合场景通用、缓存命中率高数据库日志/大文件流式读写
对齐要求无需按扇区/逻辑块对齐
# dd 直写(跳过页缓存,测真实磁盘)
dd if=/dev/zero of=/tmp/f bs=1M count=1024 oflag=direct
# 对比带缓存的写
dd if=/dev/zero of=/tmp/f bs=1M count=1024
# 程序里用 open() 传 O_DIRECT 即可

心法:数据库常用「直接 IO + 自己管理缓存」二选一:要么信 OS 页缓存,要么 O_DIRECT 全自己来,不要双倍缓存。日志型小写(WAL)尤其适合 O_DIRECT——少一层拷贝,fsync 语义更直接。

# 应用主动落盘语义对比
fsync(fd);        # 数据+元数据都落盘
fdatasync(fd);    # 只落数据(更快,元数据另算)

4. IO 调度器:mq-deadline 与 none

内核 IO 调度器决定「请求以什么顺序发给设备」——NVMe 时代很多调度已从「排序」退化为「尽量直通」。

# 查看/切换调度器
cat /sys/block/nvme0n1/queue/scheduler
echo none > /sys/block/nvme0n1/queue/scheduler
echo mq-deadline > /sys/block/sda/queue/scheduler
# 持久化:加内核参数 elevator=deadline 或 udev 规则
调度器机制适用
none直接进设备队列,几乎不排序NVMe/SSD(随机 IO 本身快,排序收益低)
mq-deadline按截止时间保证最老请求不被饿死HDD/SATA SSD(需要合并与公平)
kyber按延迟目标动态限流云盘/SSD 上追求延迟稳定
bfq(旧)按权重公平分配带宽桌面多任务/共享场景
# 调整队列深度(决定并发在途请求数)
cat /sys/block/nvme0n1/queue/nr_requests
echo 1024 > /sys/block/nvme0n1/queue/nr_requests

记忆:「SSD 用 none,HDD 用 mq-deadline」是最常用的经验起点。调度器排序本身有 CPU 开销,对百万级 IOPS 的 NVMe 来说收益不抵开销,直通反而更好。


5. io_uring:异步 IO 新时代

io_uring 用「内核与应用共享的环形队列」实现高性能异步 IO——应用提交请求到 SQ、内核完成写入 CQ,全程避免一次系统调用的拷贝与上下文切换开销。

应用提交 SQ 条目 → 内核异步执行 → 完成写入 CQ  → 应用收割结果
(共享内存 ring,而非逐次 syscall)
# 安装 liburing 开发库(Debian 系)
sudo apt install liburing-dev
# 最小例子:io_uring 提交一个 read
gcc -O2 -o uread uread.c -luring
对比传统同步 readlibaioio_uring
系统调用次数每次 IO 一次提交/收割各一次极少量(可批量)
是否支持缓冲 IO是基本仅 O_DIRECT两者都支持
复杂度低中中高
典型场景普通应用数据库数据库、存储引擎、网络服务
# 生态里已用上 io_uring 的例子
# RocksDB、PostgreSQL、nginx 都提供 io_uring 后端开关

心法:io_uring 是「一次设计换吞吐」——IOPS 越高收益越大,小请求大批量场景最划算。业务侧先确认真瓶颈(iostat 高 util + 应用线程卡在 read),再引入 io_uring 才值得。


6. 文件系统与 RAID/LVM 层

文件系统管「怎么组织数据」,RAID/LVM 管「底层盘怎么组合」——两层叠加决定吞吐与冗余:

# 软 RAID1(两块盘镜像)
mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sdb /dev/sdc
# LVM:PV → VG → LV
pvcreate /dev/sdb && vgcreate vg0 /dev/sdb
lvcreate -L 100G -n data vg0
mkfs.xfs /dev/vg0/data
级别冗余读写适用
RAID0无读写均摊(条带)性能优先、可承受丢盘
RAID1镜像读可并行系统盘/数据库日志
RAID5单盘校验读好、写有校验开销大容量通用存储
RAID10镜像+条带读写好数据库高性能首选

文件系统挂载选项也直接改变行为:

# 禁用 atime 更新,减少元数据写
mount -o noatime /dev/vg0/data /data
# xfs 针对大文件 IO 的常用挂载项
mount -o noatime,largeio /data
# SSD 开启 TRIM 保持长期性能
systemctl enable --now fstrim.timer

记忆:数据库盘别省 RAID10;顺序大文件用 RAID0/5 即可。条带大小要跟 IO 大小匹配(数据库 8-64KB 随机块对齐条带),否则一次逻辑读变多次物理读。


7. iostat 与 iowait 分析

iostat 是看「设备到底多忙」的第一工具——比 top 的 iowait 更精确:

iostat -x 1 5        # 扩展指标,每 1 秒采样 5 次
iostat -d 1          # 只看设备
指标含义判读
await请求从进队到完成平均耗时高 = 设备排队或本身慢
r_await / w_await读写分别的 await读写分开看更准
svctm设备服务时间(近似)接近 await 则无排队
%util设备忙的百分比接近 100% 且 await 高 = 饱和
r/s, w/s每秒读写请求数与期望 IOPS 对比
# iowait 是什么:CPU 等待 IO 完成的时间占比
top -1
# 真正排障时组合:iostat 看设备 + pidstat -d 看哪个进程在 IO
pidstat -d 1 5

铁律:%util=100% 不等于「盘坏了」,只说明设备是瓶颈。SSD 在高并发下 util 可能虚高(忙等待也算 util),要结合 await 与延迟分位数判断,而不是只看 %util 一个数。

# 看延迟分布比均值更有意义
# fio 或 bcc 的 biolatency 工具给出直方图

8. fio 压测实战

fio 是工业标准的 IO 压测工具——用 job 文件描述「读/写/随机/队列深度」:

sudo apt install fio
# 一条命令随机读 64KB、队列深度 32、测 60 秒
fio --name=randread --rw=randread --bs=64k --iodepth=32 \
    --size=4G --numjobs=4 --direct=1 --time_based --runtime=60 \
    --filename=/tmp/test --group_reporting
# 完整 job 文件:顺序写 + 随机读两段
[global]
direct=1
ioengine=libaio   # 或 io_uring
size=4G
[seqwrite]
rw=write
bs=128k
iodepth=16
[randread]
rw=randread
bs=4k
iodepth=64
rw 模式含义关注指标
read / write顺序读写带宽(MiB/s)
randread / randwrite随机读写IOPS、延迟分位数
rw=rw混合读写比例 rwmixread

记忆:压测要贴着真实负载配参数——数据库在线业务测 randread 8k iodepth=32,日志系统测 write 4k fsync=1。direct=1 绕过页缓存才能测设备真性能,否则测的是缓存。


9. 性能调优清单

一份按「设备 → 队列 → 文件系统 → 应用」顺序执行的调优清单:

# 1. 调度器:SSD 用 none,HDD 用 mq-deadline
echo none > /sys/block/nvme0n1/queue/scheduler
# 2. 队列深度与预读
echo 1024 > /sys/block/nvme0n1/queue/nr_requests
blockdev --setra 4096 /dev/sda        # 顺序读预读(KB)
# 3. 文件系统挂载
mount -o noatime /dev/vg0/data /data
# 4. SSD 定期 TRIM
systemctl enable --now fstrim.timer
# 5. 对齐校验:分区/条带对齐到 4K
fdisk -l /dev/sda      # 看 Start 扇区是否 8 的倍数
# 6. 脏页参数配合写吞吐(见内存专题)
sysctl -w vm.dirty_background_ratio=5
sysctl -w vm.dirty_ratio=30
# 7. 应用侧:日志与数据分离盘、调大连接池/队列并发

心法:调优顺序 = 先保证设备健康(TRIM/固件)→ 再选对调度器与队列 → 然后文件系统挂载项 → 最后才调应用并发。每步用 fio 前后对比,数字说话,别凭感觉。


10. 速查表

需求命令/参数
看设备忙度iostat -x 1
看哪个进程在 IOpidstat -d 1
切换调度器echo none > /sys/block/*/queue/scheduler
改队列深度echo 1024 > /sys/block/*/queue/nr_requests
压测随机读fio --rw=randread --bs=4k --iodepth=64
压测顺序写fio --rw=write --bs=128k --iodepth=16
绕过缓存测盘dd ... oflag=direct
禁用 atimemount -o noatime
SSD 保养systemctl enable --now fstrim.timer
软 RAID 建盘mdadm --create /dev/md0 --level=10 ...
LVM 建卷pvcreate → vgcreate → lvcreate
强制落盘应用内 fsync/fdatasync

一句话记忆:IO 栈自上而下是 VFS→页缓存→文件系统→块层→设备;SSD 配 none、HDD 配 mq-deadline,追求低延迟可上 io_uring;压测用 fio、看盘用 iostat,调优每步前后对比数字。


延伸阅读

  • /linux-filesystem-disk/ — 文件系统与磁盘基础
  • /linux-performance-tuning/ — 性能监控与调优方法论
  • /linux-kernel-tuning/ — sysctl 内核参数调优
  • /linux-backup-disaster-recovery/ — 存储冗余与备份策略
  • /linux-containers-isolation/ — 容器 IO 限制与隔离

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「linux」更多文章

  1. 《进程调度与 CPU:CFS、优先级与 cgroup CPU 控制》
  2. 《Linux 网络虚拟化:namespace、veth、网桥与虚拟交换机》
  3. 《ELF 二进制与动态链接:从符号到加载》