关注了就能看到更多这么棒的文章哦~
Moving the kernel to large block sizes
By Jake Edge
September 27, 2023
OSSEU
ChatGPT translation
https://lwn.net/Articles/945646/
使用更大的块大小(block size)进行I/O内核是存储和块层领域经常讨论的话题。这个话题在2023年5月的Linux存储、文件系统、内存管理和BPF峰会(LSFMM)的讨论中提出。在那些讨论中的一位参与者Hannes Reinecke在2023年的Open Source Summit Europe上发表了一次关于为什么要使用更大块进行I/O、该工作的当前状态以及可能会引发的发展方向的概述演讲。
Reinecke在SUSE工作了"差不多有20年了",在那之前他就已经参与了Linux;他的第一个内核版本是1.1.5或1.0.5。最近他一直参与存储工作,特别是NVMe。这导致了他的一个个人项目"终于实现了",就是在Linux中能够使用更大的块。
块和页面
目前,Linux受限于使用不大于 PAGE_SIZE
(通常为4KB)的块大小。但有一些系统和应用程序会受益于使用更大的页面;例如,一些数据库希望能够使用16KB的块,因为这是它们内部组织数据的方式。此外,一些硬件也会受益于处理更大块大小的数据,因为这会减少内部跟踪block所需的开销,从而使驱动器更加高效和便宜。
[[https://static.lwn.net/images/2023/osseu-reinecke-sm.png]]
但是,他问道,是否一定要有一个块大小?内核不能在需要的时候直接使用任意数量的数值吗?问题在于,没有一个原子性地读取或写入任意数量数据的"do I/O"指令。每个I/O操作都需要多个指令来设置它,传输数据并收集结果。这会增加每个操作的延迟,因此目标是尽量减少执行的I/O操作数量,但也需要权衡。
有一个问题是这些I/O操作的合适大小应该是多少。他说,在早期阶段,这是一个大量实验的主题。最终,加利福尼亚大学伯克利分校的研究人员("当然,像往常一样")找出了512字节是在开销和I/O粒度之间有合理折衷值的大小。这是二十年前的事情了,但我们目前仍然使用512字节—至少目前是这样。
然而,CPU有硬件辅助的内存管理,它以页面为单位运行。例如,有API可以确定哪些页面是脏的(即需要写入后备存储),就是以页面大小来执行的。这意味着页面的大小取决于CPU,Linux不能随意选择大小。对于x86_64,选择包括4KB、2MB或1GB;对于PowerPC和一些Arm系统,使用16KB作为页面大小。内核有一个编译时的 PAGE_SIZE
设置,规定了页面的大小。
有时需要从磁盘读取内存中的页面,或将其内容刷新到磁盘。对于缓冲I/O,页面缓存(page cache)进行了所有这些内容的管理;它使用硬件提供的脏页信息来确定需要写入哪些页面。由于所有这些都是以页面为单位完成的,因此以页面大小单位进行I/O是很自然的。
但是,如果有一系列连续的页面都是脏的,那么你可以一次性对所有页面进行I/O。拥有一个处理多个页面作为单个单元的数据结构将有助于这一点,这正是folios的目的。除了缓冲I/O (buffered I/O)之外,还有直接I/O(direct I/O),由用户空间来完全控制;页面缓存不涉及其中,用户空间可以按需进行多个块的I/O。文件系统通过页面缓存提供了缓冲I/O,并且有各种接口用于该I/O。最早的是缓冲头(buffer heads),它的(某种程度上的)继任者使用 struct bio
,而最近则有iomap,他说他会回头再谈。然而,为了以更大的尺寸进行缓冲I/O,页面缓存需要转换为使用folios。
Folios
folios是一种处理不同类型页面的尝试。有普通页面、复合页面(就像是若干页面组成的数组)和透明巨大页面(THPs),每种页面都有其自己的特点。然而,它们都可以使用 struct page
来访问,因此内核开发人员必须知道给定的页面结构实际上是否是一个页面—或者更复杂的东西。folios专门设计用于处理不同类型,对于他的演讲来说,重要的是它可以表示不止一个页面,因此允许它用于更大块的I/O。
这需要将页面缓存—以及可能最终的内存管理子系统—转换为使用folios。这项工作由Matthew Wilcox于2020年提出,并在每次LSFMM上进行了讨论。它也曾经是一些有争议的邮件列表讨论的主题。但工作仍在进行中,将继续进行几年("最终我们会成功的")。他展示了6.4-rc2内核中" struct page
"(8385)与" struct folio
"(1859)的统计数量来展示事情的大致进度。
然后,他转向了缓冲头(buffer heads),它们出现在0.01内核中,因此是Linux中I/O的原始结构。每个缓冲头用于一个单独的512字节磁盘扇区,它跟特定的页面结构(page structure)关联起来,并在buffer cache中进行缓存(以节省访问时的I/O)。大多数文件系统仍在使用缓冲头,并且它们也用在了关于块设备的一个伪文件系统中。页面缓存(page cache)在内核历史中是比较后期才出现的,因为在早期这个buffer cache就足够了。
struct buffer_head
相对复杂,因此在2.5内核中,引入了 struct bio
作为设备驱动程序的"基本I/O结构"。它允许对页面数组进行矢量化I/O,将bio结构发送到各种设备,并从页面缓存中抽象出来。如今,缓冲头是在bio基础设施之上实现的。有许多文件系统,如AFS、CIFS、NFS和FUSE,直接使用 struct bio
,因此不依赖于缓冲头。
最后,还有iomap,"或者说是Christoph Hellwig有点发疯";Hellwig对现有的I/O接口感到不满,并创建了iomap作为替代,Reinecke说。Iomap是一个现代化的接口,已经使用folios;它为文件系统提供了一种指定I/O应该如何映射的方式,并将其余部分留给block层。已经将几个文件系统转换为使用iomap了,包括XFS、Btrfs和Zonefs,因此对于那些涉及folios转换的文件系统,无需再做其他工作。然而,iomap的一个问题领域是文档,文档有点难找,而且常常过时,因为iomap正在积极开发中。
替换缓冲头?
存储社区长期以来一直有一个共识,即"缓冲头必须消失",他说。他在今年的LSFMM上主持了一个关于这个话题的讨论。这种思路是,缓冲头是一种遗留接口,使用一种古老的结构,因此用户应该转换为 struct bio
或iomap。但是,最近在ksummit-discuss邮件列表上的一次Linus Torvalds的不同意见引起了争议。
回应的强烈程度可能表明,应该选择不同的路径来实现更大的block size的目标,Reinecke说。转换为folios是有用的,但只影响了页面缓存和内存管理子系统;缓冲头假定I/O将以子页面粒度(即512字节)进行,因此需要解决这个问题。一个可能的路径是将所有内容转换为iomap,然后删除缓冲头,另一种可能是更新缓冲头以处理更大的I/O大小。
在理想的情况下,所有文件系统都应该转换为使用iomap,他说;这是一个"现代化的接口,而且实际上是一个非常好的接口"。但是,正如ksummit-discuss的帖子所显示的,有一些旧的文件系统没有积极维护者—或者根本没有维护者。对于这些旧的文件系统,通常几乎没有文档,也没有真正可靠的测试改动的方法。此外,转换任何文件系统(无论是遗留还是不是)都需要更好的iomap文档,以供进行转换的开发人员使用。
另一个可能性是简单地删除缓冲头;Hellwig提供了一个允许将缓冲头编译出内核的补丁集,已合并到了6.5内核中。启用该选项将意味着禁用所有使用缓冲头的文件系统,在当前阶段这并不完全现实,Reinecke说。特别是FAT文件系统,它是用于引导UEFI系统的,如果在这样的内核中禁用了它,将无法引导。
在LSFMM上,Josef Bacik提出了将缓冲头转换为使用folios的想法,以便处理小于一个页面(sub-page)或者超级页面(super-page)的I/O。虽然这不是Reinecke最初选择的方向,但他开始考虑这个想法。这种转换可能要么非常简单,如果代码没有关于sub-page I/O的什么假设;要么就可能是一场完全的噩梦,因为这种假设普遍存在。
那天晚上,他在看了缓冲头代码后坐在酒吧里,对他的邻居大声抱怨。他不知道怎么能指望有人去转换它们,因为它们与页面紧密相关。然后他意识到他的邻居是Andrew Morton,他说:"在我实现这个代码的时代,它还不错—而且它仍然能工作,不是吗?"
因此,Reinecke开始重新考虑将缓冲头转换为folios的想法,但需要解决许多问题。首先,缓冲头和iomap在根本上是不兼容的。例如,在页面结构中有一个void指针,根据使用的是哪种结构,它要么指向缓冲头,要么指向iomap结构;在查看页面缓存中的页面时,了解你拥有哪种结构非常重要。需要谨慎考虑"混搭方法"。他说,review这些更改将会很困难,因为很难发现对 PAGE_SIZE
的依赖关系。
所有这些使他开始怀疑,是否值得付出这么多努力来实现使用更大的block size 来进行I/O的总体目标。"我认为是值得的…但这只是我的看法。"但他确实知道,数据库确实希望能够进行更大的I/O操作,希望支持更大的I/O操作对文件系统来说也会更高效。在很大程度上,文件系统已经在更大的块中进行I/O操作。除了这些好处之外,驱动供应商希望出于效率、容量和实现更低成本的设备而使用更大的块。
进展
Reinecke在上周完成了他的补丁集。然而,就像在开源世界中经常发生的情况一样,大约在同一时间出现了另一种实现。Luis Chamberlain和他在三星的同事发布了不同的补丁集,涵盖了很多相同的领域。在演讲中,Reinecke说,他正在提出自己的补丁来解决这些问题,但他将在不久的将来与三星的人合作,将这两种方法结合起来。
总体想法是将缓冲头从页面转换为folios。这样,所有的I/O仍然会小于所附加的单元,因此缓冲头代码中的假设仍然会得到满足。folios将指向单个缓冲头或缓冲头列表的指针。在进行此转换时需要记住一些事情;首要的是内存管理子系统仍然以 PAGE_SIZE
为单位工作,而页面缓存和缓冲缓存已经转换为folios。
但是,为了进行I/O,缓冲头使用bio机制,它以512字节块为单位工作。他说,这实际上已经固定在块层及其驱动程序中,无法在不付出巨大努力的情况下更改。但实际的I/O由较低层驱动程序处理,这些驱动程序已经将相邻的块合并为较大的单元。因此,页面缓存中的folios可以传递给块层,块层将以512字节块的方式枚举它们,将结果交给驱动程序,驱动程序将它们重新组装为较大的单元。尽管这不是解决问题的显而易见的方法,但一切都"应该正常工作"。
所以这就是他的补丁集的核心。当然,还有其他工作要做,包括审核页面缓存,以确保它分配了与底层驱动程序使用的大小相同的folios,并确保它以folios大小的步长递增,而不是以页面为单位递增。他还需要为块驱动程序添加一个接口,以向页面缓存报告其块大小。一切都很顺利,甚至可能太顺利了,因为NFS希望使用128MB的块—并得到了—至少直到虚拟机出现内存不足的情况。这个特定的测试"巧妙地证明了使用更大size的block会导致更高程度的内存碎片化",如果真的需要这样的证明的话。
完成了吗?
尽管这些补丁使内核能够与具有大block size的驱动程序通信,但仍然存在一个问题:没有驱动程序在使用更大的block size,原因当然是"因为没有人可以与它们通信"。他已经有了更新block ramdisk驱动程序(brd)以支持更大block的测试目的的补丁。然后,该驱动程序可以用作NVMe目标的后备设备(backing device),以便可以使用更大的block size进行测试。"这非常酷,但当然还需要一些测试。"
还需要一些其他的工作。QEMU需要更新以支持更大的block size,需要使用它们来测试驱动程序,还需要测试其他子系统,如SCSI。除此之外,还需要与三星的工作统一起来。一旦所有这些都准备就绪,就会进行审查,然后需要处理审查引起的问题,然后才能将这项工作推送给上游。
内存碎片问题仍然没有解决。未来,系统可能会具有各种不同的block size的设备;在这方面,16KB不应该是一个主要问题,但在未来可能会有更大的块大小。内存管理层继续以page size的block来工作,这将导致额外的碎片化。如果系统可以切换到与更大的block size相同的粒度使用内存,那么一切都变得完美了—但这假定只有一个block size,这可能并不成立。
一个可能的解决方案,可能值得独立实施,是将SLUB分配器切换为使用更高级别的folios,而不是基于page的粒度。然后,如果 alloc_page()
用户转换为使用SLUB,它将消除分配的碎片问题。不过,再次强调,这仍然要求只能有一个block size。他会很乐意听取在存在更大的block size的情况下改善碎片化情况的其他想法。
他在演讲结束时提出了一个建议,"以防你真的很无聊":在block层及其512字节导向性方面仍然存在改进空间。他认为,将block层切换到使用folios并不是一项适合胆小的人的任务,但他认为这是可行的。bio结构不直接存储数据,而是使用 struct bio_vec
以向量化形式存储数据。虽然block层中有大约4000个使用 bio_vec
的情况,但这些情况也许可以转换为使用folios而不是page。
[我要感谢LWN的旅行赞助商,Linux基金会,为前往OSSEU的比尔巴奥提供旅行援助。]
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~