4. 请求队列与 I/O 调度:blk_init_queue、blk_cleanup_queue、I/O 调度器类型(noop/deadline/cfq/mq-deadline)原理

块设备驱动的核心是管理 I/O 请求队列。Linux 内核通过 请求队列(Request Queue) 将文件系统下发的通用 I/O 请求进行排队、合并与排序,再交由底层驱动处理。本章深入剖析请求队列的初始化与销毁流程,并详细对比四种经典 I/O 调度器的设计原理与适用场景。

核心要点: 请求队列是块设备层的“交通枢纽”,I/O 调度器决定了请求的排队策略,直接影响磁盘的吞吐量与响应延迟。理解调度器原理是调试块设备性能问题的前提。

4.1 请求队列生命周期:blk_init_queue 与 blk_cleanup_queue

每个块设备在注册时都需要创建一个请求队列。内核提供 blk_init_queue 进行初始化,并在设备移除时通过 blk_cleanup_queue 进行销毁。

4.1.1 blk_init_queue 初始化流程

blk_init_queue 的原型如下:

request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);

参数说明:

  • rfn:驱动提供的请求处理函数(request_fn),当队列中有待处理请求时被调用。
  • lock:用于保护队列操作的自旋锁。

初始化过程中,内核会完成以下关键步骤:

  1. 分配 request_queue 结构体内存。
  2. 设置默认的 I/O 调度器(通常为 CFQ 或 mq-deadline)。
  3. 初始化队列的等待队列、完成队列、统计计数器。
  4. 绑定驱动提供的 request_fn 回调。
  5. 设置队列的软/硬限制(如最大扇区数、最大请求数)。
调试提示: 在驱动初始化阶段,务必检查 blk_init_queue 的返回值。若返回 NULL,通常表示内存不足或参数无效,此时应返回 -ENOMEM 并中止设备注册。

4.1.2 blk_cleanup_queue 销毁流程

当设备被移除或驱动卸载时,必须调用 blk_cleanup_queue 来释放队列资源:

void blk_cleanup_queue(request_queue_t *q);

销毁过程包括:

  • 标记队列为“死亡”状态,阻止新请求入队。
  • 等待所有未完成的请求完成或超时。
  • 释放调度器相关的内部数据结构。
  • 释放队列自身占用的内存。
常见错误: 在调用 blk_cleanup_queue 之前,必须确保没有线程正在访问该队列。通常需要在移除设备前先调用 del_gendisk 断开设备与队列的关联,否则可能导致内核崩溃。

4.1.3 请求队列生命周期 SVG 流程图

请求队列生命周期流程图 blk_init_queue 调用 分配 request_queue 结构体 绑定 request_fn 回调 设置默认 I/O 调度器 设备运行:请求入队/处理 blk_cleanup_queue 调用 释放队列资源

4.2 I/O 调度器类型与原理

I/O 调度器位于通用块层与驱动之间,负责对请求进行合并、排序和分发。Linux 内核支持多种调度器,每种调度器针对不同的工作负载进行了优化。

4.2.1 调度器类型对比表

调度器名称 核心算法 适用场景 主要特点
Noop FIFO + 简单合并 SSD、NVMe、RAM 盘 开销最低,不进行寻道优化
Deadline 按截止时间排序 数据库、实时应用 保证读写请求的最大延迟
CFQ 完全公平队列 传统 HDD、桌面系统 按进程分配时间片,公平性好
mq-deadline 多队列 Deadline 多队列块设备(NVMe) Deadline 的多队列版本,支持 blk-mq

4.2.2 Noop 调度器

Noop 调度器是最简单的调度器,它只做两件事:

  • 请求合并:将相邻扇区的请求合并为一个更大的请求。
  • FIFO 处理:按请求到达顺序提交给驱动。

Noop 不进行任何寻道优化,因此对于没有寻道开销的设备(如 SSD、NVMe)非常高效。对于 HDD,Noop 会导致严重的寻道延迟。

适用场景: 当底层设备自己管理 I/O 排序(如 NVMe 控制器内置调度)时,Noop 是最佳选择,因为它避免了内核与设备之间的双重调度。

4.2.3 Deadline 调度器

Deadline 调度器为每个请求设置一个截止时间(默认读 500ms,写 5s),并维护两个队列:

  • 排序队列(Sorted Queue):按扇区号排序,用于合并和顺序提交。
  • FIFO 队列:按截止时间排序,用于保证延迟。

调度策略:

  1. 优先从排序队列中提交请求,以最大化顺序吞吐。
  2. 如果 FIFO 队列头部的请求即将超时,则立即提交该请求(即使破坏顺序性)。
  3. 读请求的截止时间更短,因此读操作优先于写操作。
调试技巧: 在数据库场景下,如果发现写延迟抖动,可以尝试调整 /sys/block/<dev>/queue/iosched/writes_starved 参数,控制读请求连续提交的次数上限。

4.2.4 CFQ 调度器

CFQ(Completely Fair Queuing)为每个进程分配一个独立的 I/O 队列,并按时间片轮转调度。核心机制:

  • 进程隔离:每个进程的 I/O 请求放入独立队列。
  • 时间片分配:每个队列获得一个时间片,时间片内可以提交一定数量的请求。
  • 优先级支持:通过 I/O 优先级(ionice)调整进程的权重。

CFQ 在传统 HDD 上表现优异,但在 SSD 上由于时间片切换开销较大,已被 mq-deadline 或 none 取代。

注意: 从 Linux 5.0 开始,CFQ 已被移除,推荐使用 mq-deadline 或 none(即 Noop 的多队列版本)。

4.2.5 mq-deadline 调度器

mq-deadline 是 Deadline 调度器针对多队列块设备(blk-mq)的适配版本。它与传统 Deadline 的核心区别:

  • 多队列支持:每个硬件提交队列(hardware dispatch queue)独立维护排序队列和 FIFO 队列。
  • 无全局锁:使用每队列锁,减少锁竞争。
  • 批量提交:支持一次从多个队列中批量提取请求。

mq-deadline 是目前 NVMe 和高端 SSD 的默认调度器,兼顾了延迟保证与多核扩展性。

4.2.6 I/O 调度器架构对比 SVG 图

四种 I/O 调度器架构对比 Noop 调度器 FIFO 队列 合并相邻请求 Deadline 调度器 排序队列 FIFO 队列 截止时间检查 CFQ 调度器 进程 A 队列 进程 B 队列 进程 C 队列 时间片轮转调度 mq-deadline HW Queue 0 HW Queue 1 HW Queue 2 每队列 Deadline 调度器选择建议: • Noop:NVMe、RAM 盘、虚拟化设备(后端自行管理 I/O 排序) • Deadline:数据库、实时系统(需要保证最大延迟) • CFQ:传统 HDD、桌面系统(已废弃,仅用于旧内核) • mq-deadline:现代多队列设备(NVMe、高端 SSD),默认推荐

4.3 调度器切换与调试

在运行时可以通过 sysfs 接口动态切换 I/O 调度器,无需重启系统:

# 查看当前调度器
cat /sys/block/sda/queue/scheduler

# 切换调度器(例如切换到 deadline)
echo deadline > /sys/block/sda/queue/scheduler

输出示例:

[mq-deadline] none

方括号内的调度器为当前激活的调度器。对于 blk-mq 设备,可选的调度器通常为 none(即 Noop)和 mq-deadline。

性能调试技巧: 在测试块设备驱动时,建议分别测试 none 和 mq-deadline 两种调度器下的吞吐量和延迟分布。使用 fio 工具配合不同的调度器,可以快速定位调度器引入的性能瓶颈。

4.4 请求队列与调度器交互流程

当文件系统下发一个 I/O 请求时,完整的交互流程如下:

  1. 请求生成:文件系统调用 submit_bio 提交 bio 结构体。
  2. 请求合并:通用块层尝试将 bio 合并到现有请求中(合并条件:相邻扇区、相同方向)。
  3. 请求入队:如果无法合并,则创建新请求并插入调度器的排序队列。
  4. 调度决策:调度器根据自身策略(FIFO、截止时间、时间片等)选择下一个要提交的请求。
  5. 请求分发:调度器将选中的请求放入驱动层的分发队列(dispatch queue)。
  6. 驱动处理:驱动的 request_fn 从分发队列中取出请求,提交给硬件。
关键观察点: 在调试 I/O 性能时,可以通过 /sys/block/<dev>/queue/ 下的统计文件观察调度器的行为,例如 nr_requests(队列深度)、iosched/ 目录下的调度器特定参数。

4.5 本章小结

  • 请求队列是块设备驱动的核心数据结构,blk_init_queue 和 blk_cleanup_queue 管理其生命周期。
  • Noop 适用于无寻道开销的设备,Deadline 保证延迟上限,CFQ 提供进程公平性(已废弃),mq-deadline 是现代多队列设备的默认选择。
  • 通过 sysfs 可以在运行时切换调度器,便于性能调优和问题定位。
  • 理解调度器原理是分析块设备 I/O 延迟和吞吐量问题的前提。