[toc]

block/blk-mq.c 多队列块层核心(Multi-Queue Block Layer Core) 现代存储I/O性能的基石

历史与背景

这项技术是为了解决什么特定问题而诞生的?

blk-mq(Block Multi-Queue)框架的诞生是为了从根本上重构Linux的块设备层,以适应现代存储硬件的飞速发展,特别是高速闪存设备(SSD、NVMe)的出现

传统的Linux块层(single-queue block layer)是为机械硬盘(HDD)设计的,其核心是一个per-device的单一请求队列。这种设计在HDD时代是合理的,因为磁盘的机械寻道时间是主要瓶颈。然而,随着能够处理数十万甚至数百万IOPS(每秒I/O操作数)的闪存设备的普及,这个单一队列的设计成为了新的、严重的性能瓶颈:

  • 锁争用:在多核CPU系统上,所有核心提交I/O时都必须争抢保护这个单一队列的全局锁。这种锁争用导致CPU无法有效扩展,即使硬件有能力处理更多请求,软件层面也无法将请求高效地提交给硬件。
  • 缓存行弹跳:对单一队列的频繁访问导致其数据所在的缓存行在不同CPU核心的缓存之间来回失效和同步,造成了巨大的性能开销。
  • 无法发挥硬件并行性:现代NVMe等设备在硬件层面就支持多个并行的提交和完成队列(Submission/Completion Queues)。单队列的软件栈完全无法利用这种硬件并行性。
  • I/O调度器成为瓶颈:传统的I/O调度器(如CFQ)设计复杂,也围绕着单一队列工作,在高IOPS场景下其本身也成为了开销。

blk-mq框架,主要由Jens Axboe主导开发,旨在通过引入多队列模型来解决以上所有问题,从而释放现代存储的全部潜力。

它的发展经历了哪些重要的里程碑或版本迭代?

blk-mq的演进是Linux I/O栈的一次革命性变革。

  • 引入 (Kernel 3.13)blk-mq框架首次被合并到内核中,最初只支持少数几个测试驱动。
  • 功能完善 (Kernel 3.16)blk-mq达到了功能完备的状态,开始支持更多的驱动。
  • SCSI多队列 (scsi-mq):在blk-mq的基础上,SCSI子系统也被改造为多队列模式,使得SATA、SAS等传统设备也能从blk-mq中受益。
  • 成为默认:随着其稳定性和性能优势得到验证,blk-mq逐渐成为新驱动的默认选择,并在RHEL 8等主流企业发行版中成为默认配置。
  • 移除单队列层 (Kernel 5.0)blk-mq最终完全取代了传统的单队列块层实现,旧的代码被正式从内核中移除,标志着多队列时代的全面到来。

目前该技术的社区活跃度和主流应用情况如何?

blk-mq是当前Linux内核唯一的块层实现,是整个I/O栈的绝对核心。

  • 核心地位:所有块设备驱动(NVMe, SATA, SCSI, virtio-blk等)都构建在blk-mq之上。
  • 性能基石:它是io_uring等最新异步I/O接口能够实现极致性能的基础。
  • 社区状态:作为块层的维护者,Jens Axboe和社区持续对其进行优化,以支持新的硬件特性、提升效率(如轮询I/O、写者节流等),并完善其I/O调度器框架。

核心原理与设计

它的核心工作原理是什么?

blk-mq的核心是一个两级队列架构,旨在最大限度地减少锁争用并利用硬件并行性。

  1. 第一级:软件暂存队列 (Software Staging Queues)

    • blk-mq为系统中的每个CPU核心都创建一个独立的软件队列(struct blk_mq_ctx)。
    • 当一个进程在某个CPU上发起I/O时,请求(bio)会被提交到该CPU专属的软件队列中。
    • 由于每个CPU操作自己的队列,因此在提交路径上几乎没有锁争用,完美地解决了传统单队列模型的最大瓶颈。
  2. 第二级:硬件分派队列 (Hardware Dispatch Queues)

    • 这一级队列(struct blk_mq_hw_ctx)直接映射到存储硬件实际拥有的物理提交队列。 一个NVMe设备可能有多个硬件队列,而一个SATA设备可能只有一个。
    • blk-mq框架负责将软件队列中的请求分派到硬件队列中。通常,会有一个或多个软件队列映射到一个硬件队列。
    • 这个分派过程是触发实际I/O的最后一步。请求被放入硬件队列后,设备驱动就会拾取它并发送给硬件。

这个两级模型巧妙地将“无锁的per-cpu请求提交”和“与硬件能力匹配的并行分派”结合起来,实现了极高的可伸scaling。

它的主要优势体現在哪些方面?

  • 极高的可伸缩性:通过per-cpu队列,blk-mq在多核系统上的性能可以近乎线性地随CPU核心数增加而增长。
  • 低延迟:消除了锁争用,大大缩短了I/O请求在内核中的处理路径和延迟。
  • 充分利用硬件:能够完美匹配并利用现代NVMe等多队列硬件的并行处理能力。
  • 对传统硬件亦有提升:即使对于只有单个硬件队列的传统SATA设备,per-cpu的软件队列也依然能通过减少提交端的锁争用而带来性能提升。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 调度器模型的改变:传统的I/O调度器(如CFQ, Deadline)是为单队列设计的,无法直接用于blk-mq。为此,blk-mq引入了一个新的调度器框架,并开发了新的调度器(如mq-deadline, bfq, kyber)。
  • 对低性能设备可能过犹不及:对于非常慢的设备(如一些SD卡或虚拟化环境下的慢速磁盘),blk-mq带来的多队列开销可能不会带来明显好处,甚至理论上会略有增加。但在现代硬件上,这基本不成问题。
  • 配置复杂性blk-mq的内部结构比单队列模型更复杂,虽然对用户透明,但对于内核开发者和深度性能调优者来说,理解其工作原理需要更多知识。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

作为当前唯一的块层实现,它适用于所有与块设备交互的场景。它带来的好处在以下场景中尤为突出:

  • 高性能存储(NVMe SSDs):这是blk-mq的“原生”应用场景,只有blk-mq才能发挥出NVMe设备数百万IOPS的性能。
  • 多核服务器:在拥有大量CPU核心的服务器上,blk-mq的per-cpu设计是防止I/O性能瓶颈的关键。
  • 数据库和虚拟化:这些I/O密集型应用能够从blk-mq带来的低延迟和高吞吐量中直接受益。

是否有不推荐使用该技术的场景?为什么?

在现代Linux内核中(5.0及以后),已经没有不使用blk-mq的选项了,因为它已经完全取代了旧的实现。所有块设备I/O都会流经blk-mq框架。

对比分析

请将其 与 其他相似技术 进行详细对比。

blk-mq (多队列) vs. 传统单队列块层

特性 blk-mq (Multi-Queue) 传统单队列块层 (Single-Queue Legacy)
核心数据结构 Per-CPU的软件队列 + Per-Hardware-Queue的硬件队列。 Per-Device的单一请求队列。
锁模型 提交路径几乎无锁。锁的粒度非常细。 一个全局的、per-device的粗粒度锁,保护整个请求队列。
CPU伸缩性 极高。性能随CPU核心数线性增长。 。在多核环境下迅速因锁争用而达到瓶颈。
硬件并行性 完全支持。可以直接映射到硬件的多个队列。 不支持。无法利用硬件的多队列能力。
I/O调度器 使用专门的mq调度器(mq-deadline, bfq, kyber, none)。 使用传统调度器(noop, deadline, cfq)。
适用设备 通用,尤其针对高速闪存设备(NVMe)进行了优化。 主要为机械硬盘(HDD)设计。
当前状态 当前标准(自Kernel 5.0起为唯一实现)。 已被移除(自Kernel 5.0起)。

block/blk-mq.c:多队列块设备 I/O 框架实现

block/blk-mq.c 是 Linux 内核中实现多队列块层(Multi-Queue Block Layer, blk-mq)的核心源文件。该框架的设计目标是为了在现代多核处理器和高速存储设备(如 NVMe SSD)上实现高 I/O 性能和高扩展性。


一、 核心功能

blk-mq.c 的核心功能是提供一个全新的、可扩展的 I/O 调度框架,以替代传统的单队列块设备层。它通过将 I/O 提交和完成路径分解到多个队列中,从根本上减少了锁竞争,使得 I/O 性能能够随 CPU 核心数的增加而线性增长。

该文件包含了 blk-mq 框架的初始化、运行时管理、I/O 提交流程和完成回报处理的所有核心逻辑。


二、 解决的技术问题

传统的 Linux 块设备层为每个块设备维护一个单一的请求队列(request_queue)。所有 CPU 对该设备的 I/O 请求都必须通过获取一个全局锁来访问此队列。在多核系统和高 IOPS 设备上,这个单一的锁会成为严重的性能瓶颈,导致 CPU 无法充分利用硬件能力。

blk-mq 通过以下设计解决了这个问题:

  1. 消除单一队列锁:用多个无锁或几乎无锁的队列取代单一的全局锁队列。
  2. 提升扩展性:使 I/O 吞吐能力能够随着 CPU 核心数和硬件队列数的增加而有效提升。
  3. 适应现代硬件:为 NVMe 等具有多个硬件提交队列的设备提供一个原生、高效的软件模型。

三、 核心设计与数据结构

blk-mq 的设计核心是一个两级队列结构:软件队列和硬件队列。

1. 软件提交队列 (Software Submission Queues)

  • 数据结构: struct blk_mq_ctx
  • 设计: blk-mq 为每个 CPU 核心创建一个或多个软件提交队列 (ctx)。当一个运行在特定 CPU 上的进程发起 I/O 请求时,该请求会被放入该 CPU 对应的 ctx 队列中。
  • 目的: 因为每个 CPU 操作自己的本地队列,所以在 I/O 提交路径上几乎没有跨 CPU 的锁竞争。这极大地降低了 I/O 提交时的延迟。

2. 硬件分发队列 (Hardware Dispatch Queues)

  • 数据结构: struct blk_mq_hw_ctx
  • 设计: 硬件分发队列 (hctx) 直接映射到存储设备物理上的命令队列。一个 hctx 可以服务于一个或多个 ctx
  • 目的: hctx 是软件层和硬件驱动之间的接口。当 hctx 被运行时,它会从其关联的 ctx 队列中拉取请求,然后通过驱动程序提供的操作函数将这些请求分发给硬件。

3. 请求跟踪与管理 (Request Tracking)

  • 数据结构: struct blk_mq_tagsstruct blk_mq_tag_set
  • 设计: blk-mq 使用一个基于标签(tag)的系统来跟踪在途的 I/O 请求。每个 request 在被分配时都会获得一个唯一的整数标签。这个标签会伴随请求一直传递到硬件。
  • 目的: 当硬件完成一个操作时,它会通过中断通知驱动程序哪个标签的命令已完成。驱动程序可以使用这个标签在 O(1) 时间复杂度内快速定位到原始的 struct request 结构体,从而高效地处理 I/O 完成事件。

4. 驱动程序接口

  • 数据结构: struct blk_mq_ops
  • 设计: blk-mq 是一个通用框架,它定义了一组标准的操作函数,具体的设备驱动程序必须实现这些函数。
    • queue_rq: 将一个 request 提交给硬件。这是 blk-mq 调用驱动程序的核心函数。
    • complete: 由驱动程序在 I/O 完成时调用,用于通知 blk-mq 框架。
    • init_hctx / exit_hctx: 用于初始化和清理硬件队列相关的资源。
  • 目的: 将通用的队列管理逻辑与特定的硬件操作逻辑解耦。

四、 I/O 请求在 blk-mq 中的生命周期

  1. 提交阶段 (Submission):

    • 上层代码(如文件系统)调用 submit_bio() 来提交一个 bio
    • blk_mq_submit_bio() 被调用。它会获取当前 CPU 对应的 blk_mq_ctx
    • 系统从 blk_mq_tags 中分配一个 request 结构体和一个唯一的标签。
    • bio 的信息被填充到 request 中。
    • request 被放入当前 CPU 的 blk_mq_ctx 软件队列中。
  2. 分发阶段 (Dispatch):

    • 在某个时间点(例如,队列中有足够多的请求,或者延时触发),__blk_mq_run_hw_queue() 被调用,开始处理一个硬件队列 (hctx)。
    • hctx 会轮流检查其关联的所有 ctx 软件队列。
    • 如果 ctx 中有请求,hctx 会将它们移动到自己的分发列表里。
    • I/O 调度器(如 mq-deadline, bfq)在此时介入,对分发列表中的 request 进行排序或调度。
    • blk-mq 框架遍历调度后的 request 列表,对每一个 request 调用驱动程序注册的 .queue_rq() 函数。
    • 驱动程序在 .queue_rq() 函数中生成硬件命令,并将命令与 request 的标签一同发送给存储设备。
  3. 完成阶段 (Completion):

    • 存储硬件完成 I/O 操作,并触发一个中断。
    • 驱动程序的中断处理函数被执行。它从硬件获取已完成命令的标签。
    • 驱动程序使用标签调用 blk_mq_complete_request()blk_mq_end_request() 来通知 blk-mq 框架。
    • blk-mq 使用标签快速找到对应的 request 结构体。
    • blk-mq 执行收尾工作:释放标签,调用 biobi_end_io 回调函数,最终释放 request 结构体。

五、 关键源码函数分析

  • blk_mq_init_queue(struct blk_mq_tag_set *set):
    blk-mq 模式下创建和初始化一个 request_queue 的核心函数。它负责分配和设置 blk_mq_ctxblk_mq_hw_ctx 数组。

  • blk_mq_submit_bio(struct bio *bio):
    bio 进入 blk-mq 系统的主要入口点。负责获取 request 并将其排入软件队列。

  • __blk_mq_run_hw_queue(struct blk_mq_hw_ctx *hctx):
    驱动硬件队列运行的核心逻辑。它负责从软件队列获取请求,并调用驱动的 .queue_rq 函数。

  • blk_mq_dispatch_rq_list(struct blk_mq_hw_ctx *hctx, struct list_head *list):
    负责运行 I/O 调度器并实际调用驱动 .queue_rq 的函数。

  • blk_mq_complete_request(struct request *rq):
    驱动在 I/O 完成时调用的标准函数,用于启动请求的完成处理流程。

  • blk_mq_tag_get(struct blk_mq_hw_ctx *hctx) / blk_mq_tag_put(struct blk_mq_hw_ctx *hctx, unsigned int tag, unsigned int sw_tag):
    用于从 blk_mq_tags 池中分配和释放请求标签的函数。


六、 总结

block/blk-mq.c 文件实现了 Linux 的高性能块设备 I/O 框架。其技术核心是采用了两级队列模型(per-CPU 软件队列和硬件映射队列)来最小化锁竞争,并利用基于标签的请求跟踪机制实现高效的 I/O 完成处理。这个框架为现代多核系统和高速存储硬件提供了必需的可扩展性高性能,是当前 Linux I/O 栈的基石。

I/O Completion Softirq: I/O请求完成(completion)路径的”下半部”(bottom half)

这两个函数共同构成了Linux blk-mq(多队列块I/O)子系统中I/O请求完成(completion)路径的”下半部”(bottom half)。它们是整个I/O流程的终点, 负责在硬件已经完成读写操作后, 执行所有必要的软件清理工作, 并通知上层代码(如文件系统或应用程序)I/O已完成。

核心原理 (中断上半部 vs. 软中断下半部):

这是一个经典的、为了最大化系统响应性和吞吐量而设计的两阶段中断处理模型:

  1. 上半部 (Top Half - The Hardware Interrupt): 当块设备(如SD卡控制器)完成一个I/O操作时, 它会触发一个硬件中断。内核的中断处理程序(ISR)会立即执行。这个”上半部”的代码被设计得极其快速和精简。它只做最少的工作: 找到是哪个request完成了, 然后调用llist_add将其**无锁地(lock-lessly)**推入当前CPU核心的完成链表blk_cpu_done中, 最后触发一个BLOCK_SOFTIRQ软中断。完成这些后, 它就立即退出, 让CPU可以去响应其他更重要的中断。
  2. 下半部 (Bottom Half - The Softirq): 内核会在稍后一个更安全、更合适的时机(通常是在从中断返回之前, 或者在内核线程中)来执行所有待处理的软中断。此时, blk_done_softirq函数就会被调用。这个”下半部”运行在允许中断的上下文中, 可以执行比上半部复杂得多的、耗时更长的操作, 而不会阻塞新的硬件中断。

这两个函数就是这个”下半部”的实现。


blk_done_softirq: 软中断入口点

这是一个非常简短的函数, 它的唯一作用就是作为BLOCK_SOFTIRQ软中断的官方入口点, 并将工作分派给实际的处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* __latent_entropy 属性是一个给内核随机数生成器(RNG)的提示.
* 它表明这个函数的执行时机(软中断的触发时间)与硬件事件(I/O完成)相关,
* 具有一定的不可预测性. 内核的RNG可以利用这些时间戳的抖动作为熵(entropy)的来源之一,
* 来提高随机数的质量.
*/
static __latent_entropy void blk_done_softirq(void)
{
/*
* 调用核心处理函数 blk_complete_reqs.
* this_cpu_ptr(&blk_cpu_done) 是一个高效的宏,
* 用于获取当前正在执行的CPU核心的 blk_cpu_done 链表头的指针.
* 这确保了每个CPU只处理自己核上的完成列表, 实现了完美的扩展性.
*/
blk_complete_reqs(this_cpu_ptr(&blk_cpu_done));
}

blk_complete_reqs: 批量处理已完成的请求

这是真正执行清理工作的核心函数。它被设计用来高效地处理一批(a batch of)已完成的请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
* blk_complete_reqs: 处理一个完成链表中的所有请求.
* @list: 指向一个CPU的 blk_cpu_done 完成链表的头.
*/
static void blk_complete_reqs(struct llist_head *list)
{
/*
* 这一行是整个函数最高效、最关键的部分, 包含两个原子操作:
* 1. llist_del_all(list):
* 这是一个无锁链表操作, 它会原子地将 list 指向的整个链表"摘下",
* 并返回一个指向被摘下链表的第一个节点的指针. list 本身则被原子地置为空.
* 这个操作极快, 它使得硬件中断的"上半部"可以立即开始向这个(现在为空的)
* 链表中添加新的已完成请求, 而不会与我们接下来要做的处理发生任何冲突.
*
* 2. llist_reverse_order(...):
* 无锁链表(llist)本质上是一个LIFO(后进先出)的栈. 这意味着最新完成的请求
* 位于链表头. 然而, 为了公平起见, I/O请求应该尽可能按照FIFO(先进先出)
* 的顺序来完成. 这个函数会高效地将摘下的LIFO链表反转成FIFO顺序.
*
* 最终, 'entry' 指向一个与全局完成列表完全分离的、按FIFO顺序排列的本地请求链表.
*/
struct llist_node *entry = llist_reverse_order(llist_del_all(list));
struct request *rq, *next;

/*
* 使用 llist_for_each_entry_safe 宏来安全地遍历我们本地的请求链表.
*/
llist_for_each_entry_safe(rq, next, entry, ipi_list)
/*
* 这是最终的完成分派(dispatch)步骤:
* - rq->q: 获取该请求所属的请求队列(request_queue).
* - ->mq_ops: 获取该队列的多队列操作函数表.
* - ->complete(rq): 调用由块设备驱动程序(如SD/MMC驱动, NVMe驱动)
* 在初始化时注册的 specific complete 函数.
*
* 这个 `complete` 函数会执行所有最终的清理工作, 例如:
* - 将数据从DMA缓冲区复制到最终的内存位置.
* - 更新请求的状态, 报告成功或失败.
* - 唤醒正在睡眠等待此I/O完成的进程.
* - 释放 `request` 结构体本身.
*/
rq->q->mq_ops->complete(rq);
}

blk_mq_init: 初始化多队列块I/O(Block MQ)子系统

此函数是Linux内核多队列块I/O层(Multi-Queue Block Layer, blk-mq)的全局初始化入口。blk-mq是现代Linux内核中用于与块设备(如SD卡, NVMe SSDs, SATA驱动器)交互的高性能框架。它的核心原理是为系统中的每个CPU核心都提供独立的I/O提交队列, 从而极大地减少了在多核系统上因争夺单一I/O队列锁而产生的瓶颈

blk_mq_init的作用就是建立并激活blk-mq框架最基础的、与CPU相关的底层基础设施, 特别是I/O请求的完成(completion)处理机制CPU生命周期(热插拔)管理。它为所有后续注册的blk-mq设备驱动程序铺平了道路。

关键机制初始化:

  1. Per-CPU 完成处理 (Completion Handling):

    • Lock-less Done Lists: init_llist_head(&per_cpu(blk_cpu_done, i))为每个CPU核心创建一个无锁(lock-less)链表。当硬件完成一个I/O操作并通过中断通知CPU时, 中断处理程序(ISR)会将完成的请求以极快的速度、无锁地添加到当前CPU的”done”链表中。
    • Remote Completion CSD: INIT_CSD(...)初始化了一个”Call Single Data”结构, 这是内核中用于高效执行跨核函数调用(IPI)的机制。它用于处理一个请求在CPU-A上提交, 但其完成中断却在CPU-B上触发的场景。此时, CPU-B可以使用这个机制来”通知”CPU-A去处理这个完成的请求。
    • Softirq: open_softirq(BLOCK_SOFTIRQ, blk_done_softirq)是整个完成路径的核心。它注册了一个软中断(softirq)处理函数blk_done_softirq。硬件中断处理程序在将请求放入”done”链表后, 只会触发一个BLOCK_SOFTIRQ软中断就立即退出。内核会在稍后安全、合适的时机执行blk_done_softirq。这个函数会真正地处理”done”链表中的所有请求, 包括唤醒等待的进程、释放资源等。这种”中断上半部/下半部”的机制确保了硬件中断的路径尽可能短, 提高了系统响应性。
  2. CPU热插拔支持 (CPU Hotplug):

    • cpuhp_setup_state_*系列函数注册了一系列回调, 用于在CPU被动态地添加到系统(online)或从系统中移除(offline/dead)时, 对blk-mq的per-cpu数据结构进行相应的管理。这确保了当一个CPU上线时, 它的队列会被正确初始化; 当它下线时, 其队列中任何待处理的请求都会被迁移到其他CPU, 并且其占用的资源会被干净地释放, 从而保证了系统在动态CPU拓扑变化下的健壮性。

代码逐行解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
* __init 宏表示这是一个仅在内核启动时执行的初始化函数.
*/
static int __init blk_mq_init(void)
{
int i;

/*
* 遍历系统中所有"可能"存在的CPU (由NR_CPUS配置决定).
* 在单核系统上, 这个循环只会执行一次 (i=0).
*/
for_each_possible_cpu(i)
/*
* 为每个CPU的 blk_cpu_done 变量初始化一个无锁链表(llist)的头.
* 这个链表用于暂存已由硬件完成, 等待软中断处理的I/O请求.
*/
init_llist_head(&per_cpu(blk_cpu_done, i));
for_each_possible_cpu(i)
/*
* 为每个CPU初始化一个"Call Single Data"(CSD)结构.
* 当需要在一个远程CPU上完成请求时, 会调用 __blk_mq_complete_request_remote 函数.
* 在单核系统上, 这个机制不会被使用.
*/
INIT_CSD(&per_cpu(blk_cpu_csd, i),
__blk_mq_complete_request_remote, NULL);
/*
* 注册块设备层的软中断(softirq)处理函数.
* 当硬件中断完成后, 会触发 BLOCK_SOFTIRQ, 内核稍后会调用 blk_done_softirq.
* blk_done_softirq 会处理 blk_cpu_done 链表中的请求. 这是I/O完成的核心机制.
*/
open_softirq(BLOCK_SOFTIRQ, blk_done_softirq);

/*
* 注册一个CPU热插拔(hotplug)状态的回调.
* 当一个CPU被宣告"死亡"(dead)且不可恢复时, blk_softirq_cpu_dead 会被调用,
* 用于清理与该CPU相关的软中断资源. 在单核系统上不会被调用.
*/
cpuhp_setup_state_nocalls(CPUHP_BLOCK_SOFTIRQ_DEAD,
"block/softirq:dead", NULL,
blk_softirq_cpu_dead);
/*
* 注册CPU"死亡"状态的多实例回调.
* 当CPU死亡时, blk_mq_hctx_notify_dead 会被调用, 通知所有 blk-mq 驱动
* 去清理与该CPU相关的硬件上下文(hctx)队列.
*/
cpuhp_setup_state_multi(CPUHP_BLK_MQ_DEAD, "block/mq:dead", NULL,
blk_mq_hctx_notify_dead);
/*
* 注册CPU"上线/下线"状态的多实例回调.
* 当CPU上线时, blk_mq_hctx_notify_online 会被调用, 以激活该CPU的硬件队列.
* 当CPU下线时, blk_mq_hctx_notify_offline 会被调用, 以停用并迁移该CPU的队列.
*/
cpuhp_setup_state_multi(CPUHP_AP_BLK_MQ_ONLINE, "block/mq:online",
blk_mq_hctx_notify_online,
blk_mq_hctx_notify_offline);
return 0;
}
/*
* subsys_initcall() 将此函数注册为在内核启动的"子系统初始化"阶段被调用.
* 这个阶段确保了CPU和中断子系统已经就绪.
*/
subsys_initcall(blk_mq_init);