3. I/O 调度器原理:CFQ、Deadline、NOOP、Kyber、BFQ 调度器的工作机制与适用场景
I/O 调度器位于通用块层与块设备驱动之间,其核心职责是对下发的 BIO(块 I/O 请求)进行排队、合并与排序,以优化磁盘寻道时间、提高吞吐量或降低延迟。Linux 内核支持多种调度器,每种调度器在设计哲学与适用场景上存在显著差异。本章将深入剖析 CFQ、Deadline、NOOP、Kyber 与 BFQ 五种主流调度器的工作机制与选型依据。
3.1 I/O 调度器在存储栈中的位置
下图展示了 I/O 调度器在 Linux 块 I/O 栈中的层级关系:
3.2 CFQ (Completely Fair Queuing)
CFQ 是 Linux 内核长期以来的默认 I/O 调度器,其设计理念源自 CPU 调度中的完全公平队列算法。CFQ 为每个发起 I/O 的进程分配独立的时间片与请求队列,通过轮转调度保证所有进程获得近似公平的 I/O 带宽。
3.2.1 工作机制
- 进程队列隔离: 每个进程拥有独立的请求队列,进程间的 I/O 请求不会相互干扰。
- 时间片轮转: 调度器按时间片轮转方式依次服务每个进程队列,每个时间片内尽量连续处理同一进程的请求。
- 请求合并与排序: 在队列内部,CFQ 对相邻扇区请求进行合并,并按扇区号进行排序,减少磁盘寻道。
- 优先级支持: 通过
ionice工具可设置进程的 I/O 优先级(实时、尽力、空闲),CFQ 据此分配不同的时间片长度。
3.2.2 适用场景
| 场景 | 评价 |
|---|---|
| 传统 HDD 多进程负载 | 公平性好,吞吐量稳定 |
| 桌面系统(混合负载) | 交互式进程响应较好 |
| NVMe SSD 高并发场景 | 调度开销过大,不推荐 |
| 数据库等延迟敏感应用 | 公平性导致写延迟抖动 |
3.3 Deadline 调度器
Deadline 调度器以请求的截止时间(deadline)为核心调度依据,旨在保证每个 I/O 请求在可接受的延迟范围内得到服务,避免请求“饿死”。
3.3.1 工作机制
- 双队列结构: 维护一个按扇区排序的排序队列(红黑树)和一个按 deadline 排序的分发队列(FIFO)。
- 截止时间机制: 每个请求被赋予一个 deadline(读请求通常为 500ms,写请求为 5s),超时请求被优先处理。
- 读写分离: 读请求具有更短的 deadline,确保读操作的低延迟,避免写操作阻塞读操作。
- 批量处理: 在未超时的情况下,按扇区顺序批量处理请求,兼顾吞吐量。
3.3.2 适用场景
| 场景 | 评价 |
|---|---|
| 数据库服务器 | 读延迟低,写操作不阻塞读 |
| 虚拟化宿主机 | 多虚拟机 I/O 延迟可控 |
| 实时音视频处理 | 保证关键请求的及时响应 |
| 纯写负载(日志) | 写 deadline 较长,效果一般 |
3.4 NOOP 调度器
NOOP(No Operation)调度器是所有调度器中最简单的一种,它几乎不做任何调度决策,仅执行最基本的请求合并操作,然后将请求直接插入到驱动层的请求队列中。
3.4.1 工作机制
- FIFO 队列: 请求按到达顺序放入一个 FIFO 队列,不做排序。
- 相邻合并: 仅对相邻扇区的请求进行合并,减少中断次数。
- 零调度开销: 不进行任何寻道优化或优先级调度,CPU 开销极低。
3.4.2 适用场景
| 场景 | 评价 |
|---|---|
| NVMe SSD | 无机械寻道,调度器反而增加延迟 |
| 硬件 RAID 卡 | RAID 卡自身有调度逻辑,上层无需重复调度 |
| UIO 或 DPDK 场景 | 用户态驱动直接管理 I/O,内核调度器越简单越好 |
| 虚拟化 Guest 内部 | 宿主机已做调度,Guest 内部使用 NOOP 减少嵌套 |
3.5 Kyber 调度器
Kyber 是专为快速固态存储设备设计的一种轻量级调度器,它通过动态调整令牌分配来控制 I/O 延迟,无需复杂的队列管理。
3.5.1 工作机制
- 读写分离令牌桶: 为读和写分别维护独立的令牌桶,读令牌桶的目标延迟更短。
- 动态令牌调整: 根据设备实际 I/O 延迟与目标延迟的偏差,动态调整令牌发放速率。
- 无排序开销: 不维护排序队列,请求按到达顺序处理,适合随机访问性能优异的 SSD。
- 低 CPU 占用: 调度算法简单,CPU 开销远低于 CFQ 和 BFQ。
3.5.2 适用场景
| 场景 | 评价 |
|---|---|
| NVMe SSD 云主机 | 延迟可控,CPU 开销低 |
| 延迟敏感型应用 | 通过目标延迟保证 QoS |
| 多租户存储系统 | 读写隔离,避免相互干扰 |
| 传统 HDD | 无寻道优化,不推荐 |
3.6 BFQ (Budget Fair Queuing)
BFQ 是 CFQ 的现代替代品,基于“预算”(budget)概念实现更精细的 I/O 带宽分配。BFQ 不仅考虑时间片,还考虑每次 I/O 操作传输的数据量。
3.6.1 工作机制
- 预算分配: 每个进程队列被分配一个预算(以扇区数计量),预算用完后切换到下一个队列。
- 低延迟模式: 对交互式进程(如文本编辑器)的 I/O 请求进行快速响应,减少感知延迟。
- 权重控制: 支持精细的权重设置,权重高的进程获得更多的预算和更频繁的调度机会。
- 请求预取: 根据进程的 I/O 模式进行预取优化,提升顺序读性能。
3.6.2 适用场景
| 场景 | 评价 |
|---|---|
| 桌面系统(多媒体、办公) | 交互体验优秀,系统不卡顿 |
| Android 设备 | Google 推荐,低延迟模式适合移动端 |
| 多进程混合负载 | 公平性优于 CFQ,延迟更低 |
| 高吞吐服务器 | 调度开销较大,可能成为瓶颈 |
3.7 五种调度器对比总结
| 调度器 | 核心机制 | 延迟控制 | 公平性 | CPU 开销 | 推荐设备 |
|---|---|---|---|---|---|
| CFQ | 进程时间片轮转 | 中等 | 高 | 高 | HDD(已废弃) |
| Deadline | 截止时间 + 双队列 | 高(读优先) | 中等 | 中 | HDD / SATA SSD |
| NOOP | FIFO + 合并 | 低 | 低 | 极低 | NVMe / RAID |
| Kyber | 令牌桶 + 延迟反馈 | 高(可配置) | 中等 | 低 | NVMe SSD |
| BFQ | 预算分配 + 权重 | 高(低延迟模式) | 极高 | 高 | 桌面 / Android |
3.8 调度器切换与运行时调整
在 Linux 系统中,可以通过以下方式查看和切换 I/O 调度器:
# 查看当前系统支持的调度器及当前生效的调度器
cat /sys/block/sda/queue/scheduler
# 输出示例:[mq-deadline] kyber bfq none
# 临时切换调度器(重启后失效)
echo kyber > /sys/block/sda/queue/scheduler
# 永久切换(通过内核启动参数)
# 在 GRUB 配置中添加: elevator=kyber
# 查看当前调度器参数
cat /sys/block/sda/queue/iosched/
mq-deadline、none(对应 NOOP)。CFQ 已被移除,BFQ 和 Kyber 作为替代方案。
3.9 选型决策树
在实际工程中,建议通过 fio 等工具对目标负载进行基准测试,结合延迟分布(P99/P999)和吞吐量指标,选择最适合当前硬件与业务模型的调度器。对于大多数现代 NVMe SSD 场景,Kyber 是兼顾延迟与吞吐量的最佳选择。