Moving the kernel to large block sizes

Moving the kernel to large block sizes

在存储和块层领域,内核中使用更大的块尺寸作为 I/O 的一个反复出现的话题。今年 5 月在 Linux 存储、文件系统、内存管理和 BPF 峰会 (LSFMM) 上的讨论中又出现了这个话题。这些讨论参与者之一 Hannes Reinecke 在 2023 年欧洲开源峰会上发表了演讲,概述了使用大块进行 I/O 的原因、这项工作的当前状态,以及未来的发展方向。

Reinecke 在 SUSE 工作“几乎有将近 20 年的历史了,可以追溯到永恒之前”,在那之前他就参与了 Linux;他曾用过的第一个内核是 1.1.5 或 1.0.5。最近,他参与了存储,尤其是 NVMe 相关方面的工作。这促使他进行了一个现在“终于取得了成果”的个人项目,即在 Linux 中使用大块。

块和页
目前,Linux 限于使用块大小不超过 PAGE_SIZE,通常是 4KB。但是,某些系统和应用程序将因使用更大的页面而受益;例如,某些数据库非常希望能够处理 16KB 的块,因为这是它们的内部组织方式。此外,某些硬件将受益于以更大的块大小处理数据,因为它减少了内部跟踪块所需的开销,从而使驱动器更有效率且更便宜。

[Hannes Reinecke]
但他问道,是否必须确定块大小,内核就不能在需要时使用任意数量的数据吗?问题在于,没有“是否进行 I/O”指令可以原子地读写任意数量的数据。每个 I/O 操作需要多条指令来进行设置、传输数据和收集结果。这会增加每个操作的延迟,因此目标是尽可能减少 I/O 操作的数量,但平衡这一点很重要。

还有一个问题,即这些 I/O 操作的正确大小应该是什么。他说,这个问题在早期曾是大量实验的主题。最终,加利福尼亚大学伯克利分校的研究人员(“当然,一切如常”)发现 512 字节在开销和 I/O 粒度之间提供了一个合理的折衷值。在这一点上,那是二十年前的事了,但我们仍然使用 512 字节——至少现在是这样。

然而,CPU 具有在页中进行操作的硬件辅助内存管理功能。例如,有支持用于确定哪些页已修改(即需要写入后备存储)的操作,这些操作在页面大小块中进行。这意味着页面的大小取决于 CPU,Linux 无法任意选择一种大小。对于 x86_64,可选的是 4KB、2MB 或 1GB;对于 PowerPC 和某些 Arm 系统,16KB 用于作为页面大小。内核有一个编译时 PAGE_SIZE 设置,它控制页面的大小。

有时需要从磁盘读取内存中的页面,或将它们的內容刷新到磁盘。对于缓冲 I/O,页面高速缓存是管理所有这些内容的内容;它使用硬件提供的已修改页面信息来确定需要写入哪些页面。由于所有这些都是以页面粒度完成的,因此以页面大小单位进行 I/O 是一种自然做法。

但是,如果你有一组连续的所有已修改的页面,则可以同时对整个页面集执行 I/O。拥有一个将多个页面作为一个单独的单元进行处理的数据结构将有助于实现这一点,这就是 Folio 的全部内容。除了缓冲 I/O 之外,还有用户空间完全控制的直接 I/O;页面高速缓存不参与其中,而且用户空间可以按它想要的方式在多个块中进行 I/O。缓冲 I/O 由文件系统通过页面高速缓存提供,并且可用于该 I/O 的几个不同接口。最旧的是缓冲区头,它的后继者(某种类型)使用 struct bio,最近还有一个 iomap,他说他将回到这个话题。然而,要以较大的尺寸进行缓冲 I/O,页面高速缓存需要转换为使用 Folio。

Folio
Folio 是一种尝试以一种常见的方式处理不同类型的页面。有普通页面、复合页面(如页面数组)和透明大页面 (THP),每个都有自己的怪癖。所有这些都可以使用 struct page 来寻址,因此内核开发人员必须知道给定页面结构实际上是一个页面,还是更复杂的东西。Folio 明确设计为处理不同类型,重要的是对于他的演讲,它可以表示多个页面,因而允许它用于更大块的 I/O。

这需要转换页面高速缓存——最终可能还需要内存管理子系统——以使用 Folio。Matthew Wilcox 在 2020 年提出了这一努力,并且自那以后在每次 LSFMM 上都进行了讨论。在这期间,它也一直是邮件列表讨论中存在争议的主题。但这项工作仍在进行中,而且还将持续几年(“我们最终会实现的”)。他展示了 6.4-rc2 内核中“sruct page”(8385)与“struct folio”(1859)的数量,作为对现状的大致了解。

然后他转向了缓冲区头,它们存在于 0.01 内核中,因此它们是用于 Linux 中 I/O 的原始结构。每个缓冲区头用于一个 512 字节的磁盘扇区,它链接到一个特定的页面结构,并在缓冲区高速缓存中进行内部缓存(以节省访问它时的 I/O)。大多数文件系统仍在使用缓冲区头,它们也在一个块设备的伪文件系统中使用。页面高速缓存只是在内核的历史中稍后的时间才出现的,因为在早期,缓冲区高速缓存就足够了。

struct buffer_head很复杂,因此在 2.5 内核中,struct bio 被引入作为一个设备驱动器的“基本 I/O 结构”。它允许矢量化 I/O 进出页面数组,将 bio 结构路由和重新路由到不同的设备,并且从页面高速缓存中抽象出来。如今,缓冲区头是在 bio 基础设施之上实现的。许多文件系统,例如 AFS、CIFS、NFS 和 FUSE,直接使用 struct bio,因而无需依赖缓冲区头。

最后,还有 iomap “或 Christoph Hellwig 疯了”;Reinecke 说,Hellwig 受够了现有的 I/O 接口,并创建了 iomap 作为替代。Iomap 是一个现代接口,已经使用 Folio;它为文件系统提供了一种指定 I/O 如何映射的方式,并将其余工作留给块层。几个文件系统已转换为使用 iomap,包括 XFS、Btrfs 和 Zonefs,因此对那些与 folio 转换相关的文件无需进一步的操作。不过,iomap 有一个问题区域是文档,文档有点难以找到并且通常是过时的,因为 iomap 正在积极开发中。

更换缓冲区头?
他说,存储社区长期以来一直达成共识,即“缓冲区头必须消失”。他在今年的 LSFMM 上主持了有关该主题的讨论。想法是缓冲区头是使用一种古老结构的遗留接口,因此用户应该转换为 struct bio 或 iomap。但是,ksummit-discuss 邮件列表上最近的谈话包含了 Linus Torvalds 的不同意见。

Reinecke 表示,这种反应的激烈程度或许表明应该选择不同的路径来达到大块大小的目标。转换为 Folio 很有用,但只影响页面高速缓存和内存管理子系统;缓冲区头假设 I/O 将在子页面粒度(即 512 字节)中完成,因此需要解决这个问题。一条路径可能是将所有内容转换为 iomap 然后删除缓冲区头,另一个是更新缓冲区头以便处理较大的 I/O 大小。

他说,在理想情况下,所有文件系统都将转换为使用 iomap;它是一个“现代接口,而且实际上是一个相当不错的接口”。但是,正如 ksummit-discuss 线程所示,有一些遗留文件系统缺少一个活跃的维护者——或者根本没有维护者。很多时候遗留文件系统几乎没有文档,也没有真正的方法来测试对其所做的更改。除此之外,转换任何文件系统(无论是否为遗留文件系统)都将要求开发人员为正在进行转换的文件编写更好的 iomap 文档。

另一种可能性是简单地删除缓冲区头;Hellwig 的一个补丁集允许从内核中编译缓冲区头,它已合并到 6.5 内核中。打开该补丁集将意味着禁用所有使用缓冲区头的文件系统,这在这一点上并不完全现实,Reinecke 说。特别是,对于引导 UEFI 系统所需的 FAT 文件系统,在这样的内核中不存在。

在 LSFMM,Josef Bacik 提出了将缓冲区头转换为使用 Folio 的想法,以便它能够同时处理子页面和超页面的 I/O。虽然这不是 Reinecke 会选择的方向,但他开始考虑它了。如果代码未针对子页面 I/O 作出全面假设,那么这种转换可能是非常简单的,或者“它可能成为一场彻头彻尾的噩梦”,因为该假设无处不在。

那天晚些时候,他在查看缓冲区头代码后坐在酒吧里,对他的邻居“抱怨得很厉害”。他想知道如何能指望有人能够转换它们,因为它们与页面如此紧密地联系在一起。然后他意识到他的邻居是 Andrew Morton,他说:“当年我写它的时候,它非常棒——而且它仍然有效,不是吗?”

于是,Reinecke 开始重新考虑将缓冲区页头转换为组合页的想法,但需要解决一些问题。一方面,缓冲区页头和 iomap 从根本上不兼容。例如,页面结构中有一个 void 指针,该指针指向缓冲区页头或 iomap 结构,具体取决于使用哪一个;在页面高速缓存中查看页面时,了解您所拥有的是很重要的。“混合搭配方法”需要仔细考虑。他说,将会很难审查更改,因为难以发现对 PAGE_SIZE 的依赖性。

所有这一切都使他开始怀疑,使用更大块大小进行 I/O 的总体目标是否真的值得付出所有这些努力。“我认为是.... 但那只是我。”但他确实知道数据库确实希望能够进行更大的 I/O 操作,希望是支持更大的 I/O 操作也会对文件系统更有效率。在大多数情况下,文件系统已经以更大的块进行 I/O 操作了。除了这些好处之外,驱动器供应商还希望使用更大的块来提高效率、容量,并最终降低设备成本。

进度
Reinecke 一直在努力完成他的补丁,并在上周完成了补丁集。然而,就像开源世界中经常发生的那样,在同一时间左右出现了另一种实现。路易斯·张伯伦和他在三星的同事们发布了一个不同的补丁集,涵盖了大部分内容。在演讲中,Reinecke 说他正在展示自己的补丁来解决这些问题,但不久后他会与三星的人合作,将这两种方法结合起来。

总体思路是将缓冲区页头切换为附加到组合页而不是页面。这样,所有 I/O 仍将小于附加的单元,因此仍将满足缓冲区页头代码中的假设。组合页将指向单个缓冲区页头或指向缓冲区页头的列表。此转换有一些需要牢记的事项;最重要的是,内存管理子系统仍然以 PAGE_SIZE 为单位运行,而页面高速缓存和缓冲区高速缓存已移至组合页。

但是,为了执行 I/O,缓冲区页头使用以 512 字节块运行的 bio 机制。他说,这在整个块层及其驱动程序中都得到了有效的硬连线,没有巨大的努力无法进行更改。但是,实际的 I/O 由较低层级的驱动程序处理,后者已经将相邻的块合并到更大的单元中。因此,页面高速缓存中的组合页可以传递给块层,后者将按 512 字节块对它们进行枚举,将结果传递给将它们重新组装成更大单元的驱动程序。尽管这不是解决问题的方法,但所有这些“应该都能运行”。

因此,这就是他的补丁集所做的核心工作。当然,还有其他工作要做,包括审计页面高速缓存,以确保其分配的基础驱动器使用的尺寸的组合页,并确保其以组合页大小的步长递增,而不是按页面递增。他还需要添加一个接口,以便块驱动程序向页面高速缓存报告其块大小。所有这些都运行得很好,甚至可能太好了,因为 NFS 想要 128MB 块——并且得到了它——至少在虚拟机陷入内存不足状态之前是这样。如果实际需要此类证明,则该特定测试“巧妙地证明了所有大块都导致更高的内存碎片”。

做完了吗?
虽然这些补丁程序使内核能够与大块大小的驱动器通信,但这很好,但仍然存在一个问题:没有大块大小的驱动器,“因为没有人可以与它们通信”。他提供了补丁程序来更新块 ramdisk 驱动程序 (brd) 以支持更大的块,用于测试目的。然后该驱动程序可以用作 NVMe 目标设备,以便可以使用大块大小对其进行测试。“这很酷,但当然仍然需要进行一些测试。”

还需要一些片段。需要更新 QEMU 以支持大的块大小,需要使用它们来训练驱动程序,并且需要测试其他子系统(例如 SCSI)。除此之外,还需要与三星的工作进行统一。一旦所有这些都准备就绪,在将这项工作插入上游之前,还将进行审查并处理由此产生的后果。

内存碎片问题仍然没有解决。将来,系统很可能会有块大小不同的设备;16KB 在这方面不应成为主要问题,但随着时间的推移,甚至更大的块大小也是可能的。内存管理层继续以页面大小块工作,这会导致额外的碎片。如果系统可以切换到以与较大块大小相同的粒度使用内存,那就万事大吉了——但这假设只有一个较大块大小,这可能不成立。一个可能的解决方案是切换 SLUB 分配器以使用高阶组合页,而不是页面粒度,这本身可能值得一试。然后,如果 alloc_page() 用户被转换为使用 SLUB,它将消除分配的碎片问题。然而,再一次,这依赖于存在一个大的块大小。他很希望听到其他人在较大的块大小存在的情况下改善碎片情况的方法。

他在演讲中提出了一个建议,“以防你真的无聊”:仍然有块层及其可以改进的 512 字节方向。他认为,将块层切换为使用组合页并不是胆小的人可以做到的,但这应该是可以做到的。bio 结构不直接储存数据,而是使用 struct bio_vec 在矢量化形式中获取数据。虽然块层中约有 4,000 个 bio_vec 用法,但也许可以将其转换为使用组合页而不是页面。

[我要感谢 LWN 的旅行赞助商 Linux 基金会为去毕尔巴鄂参加 OSSEU 提供的旅行援助。]

原文地址:Moving the kernel to large block sizes [LWN.net]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值