title: bfq-iosched
categories:
- linux
- block
tags: - linux
- block
abbrlink: 5e919c43
date: 2025-10-15 11:21:18
文章目录
- [block/bfq-iosched.c BFQ I/O调度器(Budget Fair Queueing) 追求极致公平与交互响应的重量级调度器](#block/bfq-iosched.c BFQ I/O调度器(Budget Fair Queueing) 追求极致公平与交互响应的重量级调度器)

block/bfq-iosched.c BFQ I/O调度器(Budget Fair Queueing) 追求极致公平与交互响应的重量级调度器
历史与背景
这项技术是为了解决什么特定问题而诞生的?
BFQ(Budget Fair Queueing)I/O调度器的诞生是为了解决一个长期困扰Linux用户,尤其是桌面用户的核心问题:I/O密集型任务对系统交互响应能力的严重破坏。
在BFQ出现之前,当一个后台进程(如大型文件复制、软件更新、文件索引)开始疯狂读写磁盘时,用户会明显感觉到整个系统变得卡顿:启动应用程序变慢、窗口响应迟钝、甚至鼠标指针都会跳动。这是因为后台任务产生的海量I/O请求"淹没"了存储设备,导致前台交互式应用(如浏览器、文本编辑器)的关键读写请求必须排很长的队。
BFQ的核心目标就是实现进程间的I/O公平性,并优先保障交互式应用的低延迟。它不仅仅是调度I/O请求,更是在管理不同进程对I/O带宽的访问权,确保没有任何一个进程可以霸占整个存储设备。
它的发展经历了哪些重要的里程碑或版本迭代?
- CFQ的继承者:BFQ的思想源于并极大地改进了其前身------单队列时代的CFQ(Completely Fair Queuing)调度器。CFQ是第一个为每个进程创建I/O队列的调度器,但其延迟控制和公平性模型仍有不足。
- Paolo Valente的持续努力:BFQ主要由开发者Paolo Valente领导开发。在很长一段时间里,它作为一个高质量的"树外"内核补丁存在,在社区中积累了良好的声誉。
- 为
blk-mq重构(关键里程碑) :BFQ面临的最大挑战是适配全新的多队列块层(blk-mq)。这需要对其核心逻辑进行重大重构,使其能够从管理单个全局队列,转变为在多个硬件队列上协同工作。block/bfq-iosched.c正是这个现代化、mq-aware版本的实现。 - 合入主线并成为默认 :在为
blk-mq重构成功后,BFQ最终被合入Linux内核主线。由于其出色的交互性能,许多面向桌面的主流发行版(如Fedora, Ubuntu)和Android都已将其作为默认的I/O调度器。
目前该技术的社区活跃度和主流应用情况如何?
BFQ是目前Linux内核中最复杂、功能最强大的I/O调度器。它是一个非常活跃的项目,仍在不断地进行优化和调整。
- 应用情况 :它是Linux桌面、Android移动设备 以及任何需要保证多任务环境下交互响应性的场景的事实标准。
- 社区地位:被公认为解决I/O公平性和交互性问题的最佳通用方案。
核心原理与设计
它的核心工作原理是什么?
BFQ的核心是一个复杂的、基于**预算(Budget)和时间片(Time Slice)的加权调度算法。它为每个进程(或cgroup)**创建一个专属的I/O队列。
- Per-Process Queues:这是BFQ实现公平性的基础。不同进程的I/O请求在逻辑上是隔离的。
- 预算分配 (Budget Assignment):调度器会轮流服务这些进程队列。当轮到一个进程的队列时,BFQ会赋予它一个"预算"------通常是允许其读写的扇区数量。
- 服务与抢占:被选中的进程可以持续派发I/O请求,直到其预算耗尽。一旦预算用完,或者它的时间片到期,BFQ就会抢占它,并选择下一个进程的队列进行服务。
- 复杂的启发式算法(Heuristics - BFQ的"魔法") :BFQ的强大之处在于其智能的启发式规则,用于动态调整调度行为:
- 交互性检测:BFQ会试图"猜测"一个进程是交互式的还是批处理的。例如,一个发出同步读请求(意味着进程会等待I/O完成)后就去睡眠的进程,很可能是交互式的。
- 低延迟优先:对于被识别为交互式的进程,BFQ会给予极高的优先级,几乎可以立即抢占正在服务的后台任务,以处理这个交互式请求。
- 权重与cgroups :BFQ与cgroups v2的
io.bfq.weight控制器紧密集成,允许管理员为不同的服务(cgroups)设置不同的I/O权重,实现精细化的服务质量(QoS)控制。 - 队列合并 :它能检测到多个协作进程(如
tar | gzip)并可能合并它们的队列,以提高HDD上的寻道效率。
它的主要优势体现在哪些方面?
- 卓越的交互响应性:核心优势。即使在最重的I/O负载下,也能保证系统UI和前台应用的流畅。
- 强大的公平性:确保没有进程可以饿死其他进程,所有进程都能获得公平的I/O带宽份额。
- 精细化的QoS控制 :是唯一与cgroups深度集成的
mq调度器,提供了强大的I/O资源控制能力。 - 高吞吐量:虽然以公平性著称,但其内部的多种优化(如请求合并、空闲等待等)也使其在许多场景下能提供非常高的吞吐量,尤其是在HDD上。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 高CPU开销 :BFQ的复杂算法和启发式规则使其成为CPU开销最高的I/O调度器。在CPU成为瓶颈的系统中,这可能会带来负面影响。
- 对超高速设备可能过度调度:对于延迟极低的顶级NVMe SSD,BFQ的软件排队和调度开销本身可能大于其带来的好处。在这种纯粹追求IOPS和最低延迟的场景下,更简单的调度器可能更优。
- 复杂性:其内部有大量的可调参数和复杂的行为模式,使得性能分析和问题诊断变得非常困难。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
- 桌面Linux系统:这是BFQ的"主场",能提供最佳的用户体验。
- Android和其他移动设备:后台同步、应用安装等操作不应影响前台应用的流畅度。
- 多租户服务器:在共享存储的虚拟化或容器环境中,需要使用cgroups为不同租户或服务提供公平的I/O份额并防止相互干扰。
- 通用服务器:在运行混合工作负载(例如,同时有Web服务、数据库和批处理任务)的服务器上,BFQ可以防止批处理任务影响在线服务的响应时间。
是否有不推荐使用该技术的场景?为什么?
- 单任务、高性能计算/数据库 :在一个只运行单一数据库实例且该实例自己管理I/O的服务器上,BFQ的公平性机制和CPU开销是不必要的。在这种场景下,**
none或kyber**调度器通常是更好的选择,以追求极致的IOPS或可预测的延迟。 - CPU资源极其受限的系统:如果系统的CPU已经满载,BFQ的额外开销可能会加剧CPU瓶颈,反而降低整体性能。
对比分析
请将其 与 其他相似技术 进行详细对比。
| 特性 | bfq (Budget Fair Queueing) | kyber | mq-deadline | none |
|---|---|---|---|---|
| 核心原理 | 复杂的预算和权重,为每个进程创建队列 | 基于延迟目标的反馈控制 | 截止时间 + LBA排序 | 简单的FIFO,无重排 |
| CPU开销 | 高 | 中等 | 低 | 最低 |
| 主要目标 | 公平性 和交互性 | 可预测的I/O延迟 | 平衡吞吐量与延迟 | 最大化IOPS/吞吐量,最低延迟 |
| 最佳适用硬件 | HDD, SATA SSD, 桌面环境 | 高速NVMe/SATA SSD | SATA SSD, HDD, 企业级SSD | 超高速NVMe SSD |
| cgroups I/O控制 | 是 | 否 | 否 | 否 |
| 关键可调参数 | 众多启发式参数 | read_lat_nsec, write_lat_nsec |
read_expire, write_expire |
无 |
BFQ I/O 调度器模块:定义、注册与初始化
本代码片段展示了 BFQ (Budget Fair Queueing) I/O 调度器在 Linux 内核中的定义、多阶段初始化和注册 过程。其核心功能是:通过填充一个 elevator_type 结构体(iosched_bfq_mq),将 BFQ 调度算法的所有核心操作封装起来;在模块加载时 (bfq_init),它不仅向内核的 I/O 调度框架注册自己,还会执行一系列 BFQ 特有的初始化步骤,包括注册 cgroup I/O 控制策略 和建立专用的 SLAB 缓存,从而为系统提供一个复杂的、注重公平性和吞吐量的 I/O 调度策略。
实现原理分析
此代码展示了一个比 mq-deadline 或 kyber 更复杂的调度器模块的初始化模式,其原理体现在分层初始化和与 cgroup 子系统的深度集成。
-
elevator_type结构:BFQ 调度器的蓝图:- 与所有调度器一样,
iosched_bfq_mq结构是其核心定义,通过.ops成员将抽象的调度操作映射到具体的bfq_*实现函数。 dispatch_request: 这是 BFQ 算法的核心,它会根据每个进程队列的"预算"(budget)和内部的服务树(service tree)来选择下一个要分发的请求,以实现公平性。icq_size和icq_align: BFQ 明确请求 了icq(I/O context queue) 缓存。icq是一个 per-process/per-cgroup 的数据结构,BFQ 使用它来为每个 I/O 发起实体(进程或 cgroup)创建独立的队列,这是实现其"公平队列"思想的物理基础。elv_register函数会根据这两个字段为 BFQ 创建一个名为bfq_io_cq的专用 SLAB 缓存。
- 与所有调度器一样,
-
Sysfs 可调参数 (
BFQ_ATTR宏):BFQ_ATTR是一个便利的宏,用于快速定义一个 sysfs 属性。它将属性名name自动扩展为对应的bfq_name_show和bfq_name_store函数。bfq_attrs数组利用这个宏,声明了一系列可以在运行时通过 sysfs 调整的 BFQ 算法参数,如fifo_expire_sync(同步请求超时),max_budget(最大预算) 等,为系统管理员提供了高度的灵活性。
-
多阶段初始化 (
bfq_init):bfq_init的逻辑比简单的elv_register调用要复杂,体现了 BFQ 的高级特性:
a. cgroup 策略注册 :blkcg_policy_register(&blkcg_policy_bfq)是第一步。BFQ 是一个 cgroup-aware 的调度器,能够为不同的 cgroup 提供 I/O 带宽的隔离和权重分配。此调用将 BFQ 的 I/O 控制策略注册到内核的块设备 cgroup (blkcg) 子系统中,使得用户可以通过 cgroupfs 来配置 BFQ 的行为。
b. SLAB 缓存设置 :bfq_slab_setup()调用用于创建 BFQ 内部所需的其他专用 SLAB 缓存(除了icq之外),例如用于分配bfq_queue(per-process 队列)或bfq_group(cgroup 对应的实体)的缓存。
c. 调度器注册 :elv_register(&iosched_bfq_mq)是最后一步,将 BFQ 自身注册到 I/O 调度框架。- 健壮的错误处理 :
bfq_init同样使用了goto链来实现严谨的错误处理。如果在任何一步失败,它会按严格的逆序撤销所有已经成功的初始化步骤,确保没有资源泄漏。
代码分析
c
// 定义一个便利宏,用于快速创建 BFQ 的 sysfs 属性项。
#define BFQ_ATTR(name) \
__ATTR(name, 0644, bfq_##name##_show, bfq_##name##_store)
// 定义一个 sysfs 属性数组,列出了所有 BFQ 的可调参数。
static const struct elv_fs_entry bfq_attrs[] = {
BFQ_ATTR(fifo_expire_sync), // 同步请求FIFO超时
BFQ_ATTR(fifo_expire_async), // 异步请求FIFO超时
BFQ_ATTR(back_seek_max), // 最大反向寻道距离
BFQ_ATTR(back_seek_penalty), // 反向寻道惩罚因子
BFQ_ATTR(slice_idle), // 队列空闲时间
BFQ_ATTR(slice_idle_us), // 队列空闲时间 (微秒)
BFQ_ATTR(max_budget), // 最大预算
BFQ_ATTR(timeout_sync), // 同步请求超时 (旧接口)
BFQ_ATTR(strict_guarantees), // 严格保证模式
BFQ_ATTR(low_latency), // 低延迟模式
__ATTR_NULL
};
// 定义一个 elevator_type 结构体,用于向块层描述 bfq 调度器。
static struct elevator_type iosched_bfq_mq = {
.ops = { // 核心操作函数指针
.limit_depth = bfq_limit_depth,
.prepare_request = bfq_prepare_request,
.requeue_request = bfq_finish_requeue_request,
.finish_request = bfq_finish_request,
.exit_icq = bfq_exit_icq, // icq 退出回调
.insert_requests = bfq_insert_requests,
.dispatch_request = bfq_dispatch_request,
.next_request = elv_rb_latter_request,
.former_request = elv_rb_former_request,
.allow_merge = bfq_allow_bio_merge,
.bio_merge = bfq_bio_merge,
.request_merge = bfq_request_merge,
.requests_merged = bfq_requests_merged,
.request_merged = bfq_request_merged,
.has_work = bfq_has_work,
.depth_updated = bfq_depth_updated,
.init_sched = bfq_init_queue,
.exit_sched = bfq_exit_queue,
},
.icq_size = sizeof(struct bfq_io_cq), // 请求 icq 缓存,并指定大小
.icq_align = __alignof__(struct bfq_io_cq), // 指定 icq 的对齐要求
.elevator_attrs = bfq_attrs, // sysfs 可调参数
.elevator_name = "bfq", // 调度器名称
.elevator_owner = THIS_MODULE, // 关联到本内核模块
};
MODULE_ALIAS("bfq-iosched");
static int __init bfq_slab_setup(void)
{
bfq_pool = KMEM_CACHE(bfq_queue, 0);
if (!bfq_pool)
return -ENOMEM;
return 0;
}
/**
* @brief bfq_init - bfq 调度器模块的初始化函数。
* @return int: 成功返回0,失败返回错误码。
*/
static int __init bfq_init(void)
{
int ret;
#ifdef CONFIG_BFQ_GROUP_IOSCHED
// 步骤1: 注册 BFQ 的 cgroup I/O 控制策略。
ret = blkcg_policy_register(&blkcg_policy_bfq);
if (ret)
return ret;
#endif
ret = -ENOMEM;
// 步骤2: 创建 BFQ 内部所需的 SLAB 缓存。
if (bfq_slab_setup())
goto err_pol_unreg;
// 初始化用于估算设备峰值速率的参考工作负载持续时间。
ref_wr_duration[0] = msecs_to_jiffies(7000);
ref_wr_duration[1] = msecs_to_jiffies(2500);
// 步骤3: 向内核的电梯框架注册 bfq 调度器。
ret = elv_register(&iosched_bfq_mq);
if (ret)
goto slab_kill;
return 0;
// 错误处理 goto 链:按初始化的逆序清理已分配的资源。
slab_kill:
bfq_slab_kill();
err_pol_unreg:
#ifdef CONFIG_BFQ_GROUP_IOSCHED
blkcg_policy_unregister(&blkcg_policy_bfq);
#endif
return ret;
}
/**
* @brief bfq_exit - bfq 调度器模块的退出函数。
*/
static void __exit bfq_exit(void)
{
// 按初始化的逆序执行清理操作。
elv_unregister(&iosched_bfq_mq);
#ifdef CONFIG_BFQ_GROUP_IOSCHED
blkcg_policy_unregister(&blkcg_policy_bfq);
#endif
bfq_slab_kill();
}
module_init(bfq_init);
module_exit(bfq_exit);
MODULE_AUTHOR("Paolo Valente");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MQ Budget Fair Queueing I/O Scheduler");