LWN:移除block层的 bounce buffer!

关注了就能看到更多这么棒的文章哦~

Block-layer bounce buffering bounces out of the kernel

By Jonathan Corbet
May 29, 2025
Gemini flash translation
https://lwn.net/Articles/1022655/

随着上世纪 90 年代末的临近,许多内核开发工作致力于改进对那些安装了巨大内存量的 32 位系统的支持。在 90 年代,在一个这样的系统中拥有超过 1GB 的内存被认为是令人震惊的。为了支持这些难以想象的巨大系统而做出的许多折衷方案至今仍保留在内核中。其中一个折衷方案——块设备层(block layer)中对 I/O 请求的 bounce buffering(回弹缓冲)——终于在 6.16 版本中被移除,这距离它引入已经超过四分之一个世纪。

一个 32 位指针只能寻址 4GB 内存,这对程序在这种系统上能使用的地址空间大小设置了一个硬限制。然而,Linux 包含了一些架构上的选择,这些选择更严重地限制了可用内存的数量。4GB 的虚拟地址空间(virtual address space)包含了用户空间(user-space)和内核空间(kernel-space)内存;分离这些空间可以提供更多的虚拟地址空间,但这会带来巨大的性能开销。内核还将所有物理内存(physical memory)映射到其地址空间的一部分,这使得内核可以轻松直接访问系统中的每个页面。这意味着内核的地址空间部分必须大于内核管理的物理内存量。

当时大多数配置将虚拟地址空间最高的 1GB 留给内核,剩余 3GB 用于用户空间。但是,由于内核要求所有物理内存都映射到其空间,它只能使用远少于 1GB 的物理内存——其 1GB 中的一部分必须用于内核自身、vmalloc 区域(vmalloc area)等等。到上世纪 90 年代末,拥有比这更多内存的系统变得普遍;事实上,通过一些硬件技巧,可以将超过 4GB 的物理内存安装到 32 位系统中。但 Linux 无法使用那部分内存;这通常被视为一个问题。

当时采用的解决方案是一个被称为“高内存”(high memory)的概念。任何超出内核直接寻址能力的内存都被归入此类;它可以映射到用户空间,但内核无法直接访问它,除非创建显式(且临时的)映射。因此,高内存通常无法用于任何类型的内核数据结构。将这部分内存提供给用户空间解决了一个紧迫的问题,但内核无法将高内存用于自身目的很快就带来了新的问题;系统缺乏足够的低内存(low memory)来高效运行。

1999 年 11 月发布的 2.3.27 内核版本包含了缓解此问题的重要一步;具体来说,它将页缓存(page cache)(一个大型内核数据结构)移到了高内存中。要使这一迁移奏效,必须应对许多挑战,其中之一是当时使用了许多块驱动(block drivers),并且没有一个是在考虑高内存的情况下编写的。如果其中一个驱动被提供了一个不在直接可访问内存中的缓冲区,结果会非常糟糕。即使对于开发版内核(2.3.27 就是开发版),这通常也被视为一个问题。

快速修复所有驱动是不现实的,因此需要一个权宜之计。因此,在这个版本中,块层引入了 bounce buffering 的概念。如果一个 I/O 请求涉及高内存中的缓冲区,而负责处理该请求的驱动无法处理高内存,数据就会被复制(即“回弹”)到一个低内存缓冲区。对于写入操作,回弹发生在将缓冲区交给驱动之前;对于读取操作,会给驱动一个低内存缓冲区,操作完成后再将其内容复制回高内存。数据可以(长期)保存在高内存中,而驱动可以继续工作而无需修改。

Bounce buffering 是那种没人喜欢的解决方案。在 I/O 操作之前或之后复制数据只会使这些操作变慢。bounce buffer 还会增加操作使用的内存,如果系统内存压力大,并且正在写入数据以便回收和重用其占用的页面,这可能会带来特别的问题。但是,bounce buffering 使得将页缓存移出低内存成为可能,因此这是开发社区愿意付出的代价。

有趣的是,添加到 2.3.27 中的 bounce buffering 代码包含了一条注释,指出当计划对块代码进行大规模重写时,它将在 "moved to the block layer in 2.5" 中移至块层。那次重写确实作为 2.5 开发系列(始于 2001 年底)的首批改动之一完成了,但这项特定的迁移直到 2014 年的 3.16 内核才发生。

在 2025 年,很少能找到需要高内存概念的系统;开发者们多年来一直在讨论完全移除高内存支持,但在一些 32 位系统上仍然需要它。然而,在 64 位系统上,高内存没有任何用处,因为内核再次能够将其地址空间容纳令人震惊的巨大内存量。

除此之外,多年来仍在活跃使用的块驱动数量有所下降,而剩余的驱动往往不再需要 bounce buffering。使用内核 DMA API(DMA 接口)的现代驱动无需担心缓冲区的放置位置;驱动几乎肯定不会直接访问该缓冲区,并且 DMA 层确保缓冲区被正确放置。老硬件的驱动可能仍然需要直接操作缓冲区;可以教会它们在高内存情况下创建临时映射,从而避免使用 bounce buffer。因此,使用 bounce buffering 机制的用户数量已经下降,但块层持续支持着这项功能。

Christoph Hellwig 在五月初查看 bounce buffering 时发现,只有四个驱动仍然使用它;除一个之外,所有驱动都用于可能已不再普遍使用的老旧设备(例如,其中两个是并口驱动)。即使是仍在大量使用的使用 bounce buffer 的驱动——USB 存储驱动——也只为使用程序化 I/O(programmed I/O)的过时主机控制器需要 bounce buffer。因此 Hellwig 整理了一套补丁集,使得这四个驱动在高内存支持配置的系统上无法加载,从而将 bounce buffer 的用户数量降至零。至此,bounce buffering 代码本身可以(并且已被)移除。

即使在 Hellwig 的系列补丁之后,内核中并非完全没有 bounce buffering;有些时候这是不可避免的。然而,剩余的回弹通常在 DMA 和 swiotlb 层中处理,并在块层之外需要时可用。

这套补丁集包含在 6.16 版本的首批合入中,它从内核中移除了近 300 行代码,也许更重要的是,它从块 I/O 路径中剔除了一个令人烦恼的特殊情况。一个来自高内存时代的旧遗迹已被移除,这或许让内核更接近于完全移除高内存支持。这些通常都被认为是好的事情。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值