|
| 1 | +.. SPDX-License-Identifier: GPL-2.0 |
| 2 | +.. include:: ../disclaimer-zh_CN.rst |
| 3 | + |
| 4 | +:Original: Documentation/block/blk-mq.rst |
| 5 | + |
| 6 | +:翻译: |
| 7 | + |
| 8 | + 柯子杰 kezijie <kezijie@leap-io-kernel.com> |
| 9 | + |
| 10 | +:校译: |
| 11 | + |
| 12 | + |
| 13 | + |
| 14 | +================================================ |
| 15 | +多队列块设备 I/O 排队机制 (blk-mq) |
| 16 | +================================================ |
| 17 | + |
| 18 | +多队列块设备 I/O 排队机制提供了一组 API,使高速存储设备能够同时在多个队列中 |
| 19 | +处理并发的 I/O 请求并将其提交到块设备,从而实现极高的每秒输入/输出操作次数 |
| 20 | +(IOPS),充分发挥现代存储设备的并行能力。 |
| 21 | + |
| 22 | +介绍 |
| 23 | +==== |
| 24 | + |
| 25 | +背景 |
| 26 | +---- |
| 27 | + |
| 28 | +磁盘从 Linux 内核开发初期就已成为事实上的标准。块 I/O 子系统的目标是尽可能 |
| 29 | +为此类设备提供最佳性能,因为它们在进行随机访问时代价极高,性能瓶颈主要在机械 |
| 30 | +运动部件上,其速度远低于存储栈中其他任何层。其中一个软件优化例子是根据硬盘磁 |
| 31 | +头当前的位置重新排序读/写请求。 |
| 32 | + |
| 33 | +然而,随着固态硬盘和非易失性存储的发展,它们没有机械部件,也不存在随机访问代 |
| 34 | +码,并能够进行高速并行访问,存储栈的瓶颈从存储设备转移到了操作系统。为了充分 |
| 35 | +利用这些设备设计中的并行性,引入了多队列机制。 |
| 36 | + |
| 37 | +原来的设计只有一个队列来存储块设备 I/O 请求,并且只使用一个锁。由于缓存中的 |
| 38 | +脏数据和多处理器共享单锁的瓶颈,这种设计在 SMP 系统中扩展性不佳。当不同进程 |
| 39 | +(或同一进程在不同 CPU 上)同时执行块设备 I/O 时,该单队列模型还会出现严重 |
| 40 | +的拥塞问题。为了解决这些问题,blk-mq API 引入了多个队列,每个队列在本地 CPU |
| 41 | +上拥有独立的入口点,从而消除了对全局锁的需求。关于其具体工作机制的更深入说明, |
| 42 | +请参见下一节( `工作原理`_ )。 |
| 43 | + |
| 44 | +工作原理 |
| 45 | +-------- |
| 46 | + |
| 47 | +当用户空间执行对块设备的 I/O(例如读写文件)时,blk-mq 便会介入:它将存储和 |
| 48 | +管理发送到块设备的 I/O 请求,充当用户空间(文件系统,如果存在的话)与块设备驱 |
| 49 | +动之间的中间层。 |
| 50 | + |
| 51 | +blk-mq 由两组队列组成:软件暂存队列和硬件派发队列。当请求到达块层时,它会尝 |
| 52 | +试最短路径:直接发送到硬件队列。然而,有两种情况下可能不会这样做:如果该层有 |
| 53 | +IO 调度器或者是希望合并请求。在这两种情况下,请求将被发送到软件队列。 |
| 54 | + |
| 55 | +随后,在软件队列中的请求被处理后,请求会被放置到硬件队列。硬件队列是第二阶段 |
| 56 | +的队列,硬件可以直接访问并处理这些请求。然而,如果硬件没有足够的资源来接受更 |
| 57 | +多请求,blk-mq 会将请求放置在临时队列中,待硬件资源充足时再发送。 |
| 58 | + |
| 59 | +软件暂存队列 |
| 60 | +~~~~~~~~~~~~ |
| 61 | + |
| 62 | +在这些请求未直接发送到驱动时,块设备 I/O 子系统会将请求添加到软件暂存队列中 |
| 63 | +(由 struct blk_mq_ctx 表示)。一个请求可能包含一个或多个 BIO。它们通过 struct bio |
| 64 | +数据结构到达块层。块层随后会基于这些 BIO 构建新的结构体 struct request,用于 |
| 65 | +与设备驱动通信。每个队列都有自己的锁,队列数量由每个 CPU 和每个 node 为基础 |
| 66 | +来决定。 |
| 67 | + |
| 68 | +暂存队列可用于合并相邻扇区的请求。例如,对扇区3-6、6-7、7-9的请求可以合并 |
| 69 | +为对扇区3-9的一个请求。即便 SSD 或 NVM 的随机访问和顺序访问响应时间相同, |
| 70 | +合并顺序访问的请求仍可减少单独请求的数量。这种合并请求的技术称为 plugging。 |
| 71 | + |
| 72 | +此外,I/O 调度器还可以对请求进行重新排序以确保系统资源的公平性(例如防止某 |
| 73 | +个应用出现“饥饿”现象)或是提高 I/O 性能。 |
| 74 | + |
| 75 | +I/O 调度器 |
| 76 | +^^^^^^^^^^ |
| 77 | + |
| 78 | +块层实现了多种调度器,每种调度器都遵循一定启发式规则以提高 I/O 性能。它们是 |
| 79 | +“可插拔”的(plug and play),可在运行时通过 sysfs 选择。你可以在这里阅读更 |
| 80 | +多关于 Linux IO 调度器知识 `here |
| 81 | +<https://www.kernel.org/doc/html/latest/block/index.html>`_。调度只发 |
| 82 | +生在同一队列内的请求之间,因此无法合并不同队列的请求,否则会造成缓存冲突并需 |
| 83 | +要为每个队列加锁。调度后,请求即可发送到硬件。可能选择的调度器之一是 NONE 调 |
| 84 | +度器,这是最直接的调度器:它只将请求放到进程所在的软件队列,不进行重新排序。 |
| 85 | +当设备开始处理硬件队列中的请求时(运行硬件队列),映射到该硬件队列的软件队列 |
| 86 | +会按映射顺序依次清空。 |
| 87 | + |
| 88 | +硬件派发队列 |
| 89 | +~~~~~~~~~~~~~ |
| 90 | + |
| 91 | +硬件队列(由 struct blk_mq_hw_ctx 表示)是设备驱动用来映射设备提交队列 |
| 92 | +(或设备 DMA 环缓存)的结构体,它是块层提交路径在底层设备驱动接管请求之前的 |
| 93 | +最后一个阶段。运行此队列时,块层会从相关软件队列中取出请求,并尝试派发到硬件。 |
| 94 | + |
| 95 | +如果请求无法直接发送到硬件,它们会被加入到请求的链表(``hctx->dispatch``) 中。 |
| 96 | +随后,当块层下次运行该队列时,会优先发送位于 ``dispatch`` 链表中的请求, |
| 97 | +以确保那些最早准备好发送的请求能够得到公平调度。硬件队列的数量取决于硬件及 |
| 98 | +其设备驱动所支持的硬件上下文数,但不会超过系统的CPU核心数。在这个阶段不 |
| 99 | +会发生重新排序,每个软件队列都有一组硬件队列来用于提交请求。 |
| 100 | + |
| 101 | +.. note:: |
| 102 | + |
| 103 | + 块层和设备协议都不保证请求完成顺序。此问题需由更高层处理,例如文件系统。 |
| 104 | + |
| 105 | +基于标识的完成机制 |
| 106 | +~~~~~~~~~~~~~~~~~~~ |
| 107 | + |
| 108 | +为了指示哪一个请求已经完成,每个请求都会被分配一个整数标识,该标识的取值范围 |
| 109 | +是从0到分发队列的大小。这个标识由块层生成,并在之后由设备驱动使用,从而避 |
| 110 | +免了为每个请求再单独创建冗余的标识符。当请求在驱动中完成时,驱动会将该标识返 |
| 111 | +回给块层,以通知该请求已完成。这样,块层就无需再进行线性搜索来确定是哪一个 |
| 112 | +I/O 请求完成了。 |
| 113 | + |
| 114 | +更多阅读 |
| 115 | +-------- |
| 116 | + |
| 117 | +- `Linux 块 I/O:多队列 SSD 并发访问简介 <http://kernel.dk/blk-mq.pdf>`_ |
| 118 | + |
| 119 | +- `NOOP 调度器 <https://en.wikipedia.org/wiki/Noop_scheduler>`_ |
| 120 | + |
| 121 | +- `Null 块设备驱动程序 <https://www.kernel.org/doc/html/latest/block/null_blk.html>`_ |
| 122 | + |
| 123 | +源代码 |
| 124 | +====== |
| 125 | + |
| 126 | +该API在以下内核代码中: |
| 127 | + |
| 128 | +include/linux/blk-mq.h |
| 129 | + |
| 130 | +block/blk-mq.c |
0 commit comments