本页目录
块设备层
覆盖: bio → request → plug/unplug → blk-mq (multi-queue) → IO scheduler (mq-deadline/kyber/bfq) → blktrace/blkparse 调试 内核版本: 2.6 ~ 6.x,重点标注 blk-mq 重构 (3.13~5.x)
概述
块层是文件系统和设备驱动之间的转换层。它的输入是文件系统提交的 bio("读第 N 个扇区,放到这些页面,完成后通知我"),输出是发给设备驱动的 request。在这之间,块层做合并、排序、调度,目标是最小化 IO 延迟的同时最大化吞吐。
从 2.6 的单请求队列到 5.x 的多队列 (blk-mq),Linux 块层经历了一次完整重写——驱动模型从"一个设备一个队列一把锁"变成了"per-CPU 提交队列 + 多个硬件队列"。
bio: 块 IO 的原子单元
// include/linux/blk_types.h
;
bio_vec: 描述一个内存页上的 IO
// include/linux/bvec.h
;
一个 bio 可以包含多个 bio_vec——这就是 scatter-gather (散列-聚集) IO。文件系统可以将非连续的内存页面打包成一次 IO,块层和驱动再拆分为设备可接受的大小。
bio 提交路径
// block/blk-core.c
void
blk-mq: 多队列架构
为什么从 blk-sq 迁移到 blk-mq
blk-sq (单队列, 2.6 ~ 3.x):
struct request_queue *q; ← 一把大锁保护所有提交
所有 CPU 竞争 q->queue_lock → 多核扩展瓶颈
IO scheduler 的单线程模型 → 高 IOPS 设备 (NVMe) 触顶
blk-mq (3.13+, 5.x 完全替代 blk-sq):
两层队列:
Software Queues (ctx): per-CPU 或 per-cgroup → 零锁竞争
Hardware Queues (hctx): 映射到设备硬件队列 (NVMe SQ, SCSI tag)
Tags: 用于管理硬件队列的并发请求数
→ 每个硬件队列有固定的 tag pool → 自然限流
数据结构
// include/linux/blk-mq.h
;
;
提交路径: plug → scheduler → dispatch → driver
Plug: 批量提交
// block/blk-mq.c + block/blk-plug.c
// plug 是 per-task 的 bio 积累机制:
// blk_start_plug(current) // 开启 plug
// submit_bio() × N // 积累 N 个 bio, 不立即 dispatch
// blk_finish_plug(current) // 一次性 flush 所有 bio
// 设计原理:
// 1. 减少锁竞争 (一次拿锁, 插入 N 个请求)
// 2. 增加合并机会 (插 plug 期间可以 front/back merge)
// 3. 文件系统通常在写路径中利用 plug:
// ext4_writepages() → blk_start_plug() → 多次 submit_bio → blk_finish_plug()
IO 调度器
多队列 IO 调度器与旧单队列调度器的根本区别:调度器不是全局的,是 per-hctx 的。每个硬件队列独立调度,因为不同队列对应不同 CPU 的提交。
mq-deadline
// block/mq-deadline.c
// 读写分别维护红黑树 (按 sector 排序) + FIFO (按时钟排序)
//
// 核心参数:
// read_expire: 读请求最长等待时间 (默认 500ms)
// write_expire: 写请求最长等待时间 (默认 5000ms)
// writes_starved: 写请求配额 (优先处理多少批读后才处理一批写)
//
// 调度逻辑:
// 1. 先查 FIFO: 有超时的请求 → 从红黑树取该 sector 附近的请求 (批量下发)
// 2. 否则: 按 direction batch 下发 (优先读, 但 writs_starved 保证写不被饿死)
// 3. 每次 dispatch 不超过 16 个请求 (避免 starving 其他队列)
//
// 适合: NVMe, 通用 SSD
kyber
// block/kyber-iosched.c
// 基于 token bucket 的自适应调度
// 目标: 控制读/写队列深度,使延迟保持在目标以内
//
// 工作原理:
// 维护读/写的延迟直方图
// 根据延迟动态调整该方向的 token bucket rate
// 延迟低 → 增大 bucket → 更多 IO 并发
// 延迟高 → 减小 bucket → 减少 IO 并发 (降低设备队列深度)
//
// 适合: 对延迟敏感的 SSD 负载 (如数据库)
bfq (Budget Fair Queuing)
// block/bfq-iosched.c
// 按 cgroup/进程分配 IO 带宽
// 每个进程有一个 "budget" (可发出的扇区数)
// 基于 B-WF2Q+ 算法 (类似 CFS 的公平调度)
//
// 特点:
// - 交互式进程自动获得更高权重 (检测 think time)
// - 支持 cgroup blkio controller
// - 顺序读被检测到 → 给予更多 budget → 高吞吐
//
// 适合: 桌面系统 (确保后台备份不影响前台应用响应)
调度器切换
# [mq-deadline] kyber bfq none
队列参数调优
# 硬件队列数 (通常等于 CPU 数)
# max_sectors_kb: 单次 IO 最大大小 (默认 512KB = 1024 扇区)
# nr_requests: 每个硬件队列的最大请求数
# scheduler 参数
调试与观测
# blktrace: 追踪每个 IO 的完整生命周期
# 输出: time, CPU, action (Q=queued, G=get, I=inserted, D=dispatch, C=complete)
# iostat: 设备级统计
# r/s, w/s, r_await, w_await, aqu-sz (平均队列深度), %util
# 单设备原始计数
# 字段: read_ios, read_merges, read_sectors, read_ticks, write_ios, ...
# blk-mq 调试
# 每个硬件队列的 tag 使用情况
# ftrace 追踪块层事件
关键配置与编译
# 内核启动参数
scsi_mod.use_blk_mq=1 # 强制 SCSI 使用 blk-mq (5.0+ 默认)
elevator=mq-deadline # 默认 IO scheduler
elevator=bfq # 桌面推荐
# 内核编译选项
CONFIG_BLK_MQ=y # 启用 blk-mq
CONFIG_IOSCHED_BFQ=y # BFQ 支持
CONFIG_BLK_DEV_THROTTLING=y # cgroup blkio
参考与延伸
- 源码:
block/blk-mq.c(~4000行, blk-mq 核心),block/blk-merge.c,block/mq-deadline.c,block/bfq-iosched.c,block/kyber-iosched.c - 内核文档:
Documentation/block/,Documentation/block/bfq-iosched.rst - LWN:
- "The multi-queue block layer" (lwn.net/Articles/552904/)
- "blk-mq and the I/O schedulers" (lwn.net/Articles/738449/)
关键词: bio, bio_vec, blk-mq, software queue, hardware queue, plug, mq-deadline, kyber, bfq, blktrace, iostat, IO scheduler