作者: Jonathan Corbet
2026年5月11日
LSFMM+BPF
某些 CPU 架构能够以多种不同的基础页(base-page)大小运行;使用较大的页大小通常可以获得更好的性能,但代价是增加内存消耗。其他架构的限制则更多。在 2026 年的 Linux 存储、文件系统、内存管理和 BPF 峰会(Linux Storage, Filesystem, Memory Management, and BPF Summit)上,内存管理分论坛的两场会议探讨了在底层内核不支持 64KB 页的情况下,如何让进程以 64KB 页大小运行的选择。第一场会议重点讨论了让每个进程拥有自己的页大小,而第二场会议则涉及为 x86 系统引入 64KB 页。
进程级页大小
使用 64KB 页可以提高性能,但这样做也会产生内部碎片(internal fragmentation)和大量的内存浪费。这种内存使用的代价往往限制了较大基础页大小的使用。Ryan Roberts 和 Dev Jain(远程)提出了一项计划,旨在允许进程以与系统整体不同的页大小运行,试图兼顾两者的优点。

Roberts 首先指出,在大页系统和小页系统之间存在性能差距。通过 “随机选择的一组基准测试”,使用较大的页大小可以获得 2-17% 的性能提升。但随之而来的内存消耗增加促使人们坚持使用许多架构支持的标准 4KB 页大小。某些最新处理器中提供的连续 PTE 支持(物理上连续的页可以共享一个转译后备缓冲区 (Translation Lookaside Buffer, TLB) 条目)提供了一定帮助,但即便使用了该特性,性能差距依然存在。
性能差异的原因有很多。在软件方面,较大的页大小意味着更少的页错误(page fault)和内核中更短的最近最少使用 (Least-Recently-Used, LRU) 列表。在硬件方面,较大的页可以提高 TLB 的利用率;运行 64KB 页的系统,其 TLB 覆盖的内存区域是原来的 16 倍。Arm CPU 可以缓存最后一次页表遍历(page-table walk)的结果,从而加速落在同一个页表项 (Page-Table Entry, PTE) 页内的地址转换;较大的页大小增加了该缓存的覆盖范围。此外,使用较大的页还会使页表更加紧凑,减少它们对 TLB 和缓存的影响。
Roberts 说,目前有一些针对架构层面的工作旨在消除这种性能差距,但这些工作的结果在未来几年内还无法应用。因此,有理由探索在软件层面可以做些什么。一种可能性是给每个进程分配自己的页大小,这样受益于大页的进程可以使用大页,而不会增加系统整体的内存负担。特别是 Arm 架构支持这种概念,允许内核保持 4KB 页大小,同时允许单个进程以更大的页大小运行。
Jain 接手介绍了提议的实现,该实现分为三层。第一层是 “ABI 适配器”(ABI adaptor),旨在隐藏内核页大小与任何给定进程页大小之间的差异。每个进程的页大小存储在 mm_struct 结构中;它在进程派生(fork)时被保留,但可以通过 execve() 调用进行更改。各种系统调用(例如 mmap() )将修改长度和对齐参数以匹配内核的页大小。Jain 说,这项工作相当直接,但 ioctl() 调用可能需要更多关注。ELF 加载器经过增强,可以理解具有不同页大小进程的对齐需求。在各种 /proc 文件的实现中加入了不少技巧,使得以 64KB 页运行的进程看到的结果就像是在 64KB 内核上运行一样。
第二层是对内核内存管理子系统的一系列修改。事实证明,用于实现透明大页 (Transparent Huge Pages, THP) 的许多代码路径可以被复用,以便在 4KB 内核上为使用较大页大小的进程提供 64KB 页。对于这类进程,分配请求将指定页大小作为最小可接受的分配大小;更大的页(直至 PMD 级的大页大小)仍然是可能的。
页缓存 (Page Cache) 自身也面临挑战,因为它是由系统中所有进程共享的。一种选择是始终在那里使用 64KB 的 folio,但在缓存小文件时这会浪费相当多的内存。因此,页缓存大部分时间仍然使用 4KB 页。如果一个 64KB 进程通过 mmap() 映射一个文件,该文件的所有 4KB folio 都将从页缓存中丢弃,随后任何新的 folio 都将以较大的尺寸添加到缓存中。
Kiryl Shutsemau 询问是否所有文件系统现在都支持页缓存中的大 folio;Matthew Wilcox 给予了否定回答,称某些文件系统是 “偷懒的家伙”(lazy slackers),尚未添加该支持。他说,最大的问题是 Btrfs。Wilcox 建议,作为丢弃页缓存条目的替代方案,内核可以在不超出文件末尾的前提下直接使用 64KB folio。
Lorenzo Stoakes 表示这项工作看起来相当具有侵入性,并质疑为什么不能更多地利用多尺寸透明大页 (multi-size Transparent Huge Pages, mTHPs),它也能提供许多相同的好处。Roberts 回答说,mTHPs 并不能提供大页所能提供的所有硬件级收益。Stoakes 还担心广泛使用较大的页大小可能会给内存管理子系统的整理 (compaction) 代码带来巨大压力。
由于时间紧迫,Roberts 跳过了一些预定的讨论(包括第三层,即处理不同尺寸页表的架构特定代码),直接转到了待解决问题列表。其中第一个问题与当内核在 64KB 进程的上下文中运行时,尝试执行需要 4KB 页大小的操作会发生什么有关。一种选择是让进程回退到 4KB 页;这将确保功能正确性,但会损失性能。另一种选择是让操作失败;Roberts 说,这个想法 “看起来” 更简单,但需要在整个内核中散布大量的页大小检查。
用户空间 ABI 兼容性是一个挑战;内核在被 64KB 进程查询时可以假装以 64KB 页运行,但它永远无法模拟所有内容。例如,某些 /proc 文件根本无法隐藏内核正在使用 4KB 页的事实。当 64KB 进程读取 /proc/=/PID=/=/pagemap 时,也无法表示一个 4KB 进程。还有一些系统调用和其他特性(例如 userfaultfd() )是无法模拟的。
Roberts 说,处理这些问题的一种方法是 “削减”(defeature)64KB 进程的功能。具有不同页大小的进程彼此不可见,页大小大于内核页大小的进程将无法使用 userfaultfd() 等特性。任何无法向 64KB 进程正确表示的操作都将直接失败。
Roberts 最后总结道,虽然允许进程拥有不同的页大小带来了好处,但也存在一些棘手的问题。添加这一特性还会给内存管理子系统带来相当大的变动。不过,这些收益可能确实值得付出这些努力。
x86 的 64KB 基础页大小
对于受益于较大基础页的工作负载来说,使用较大的基础页可能是一个不错的解决方案,但存在一个小问题:包括 x86 在内的一些次要架构不支持以较大的基础页大小运行。在下一场会议中,Shutsemau 提出了一种在 x86 系统上绕过此限制的方法。不过,这个想法遭到了在场开发者的某种程度的怀疑。

Shutsemau 开始说道,在 Arm 处理器上,使用 64KB 基础页可以为 “一个非常重要的工作负载” 带来 1.7% 的性能提升;他希望也将这种提速带到 x86 系统上。使用较大的页将减少系统内存映射(memory map)的内存开销,允许轻松(且能提升性能)的 TLB 合并、更快的 I/O 操作以及更容易分配 1GB 的巨页。他说,这样做需要将内核中系统页大小的概念一分为二。
目前, PAGE_SIZE 宏在整个内核中用于表示硬件的基础页大小。Shutsemau 将逐步淘汰该宏,转而使用 PTE_SIZE (描述硬件视角的基础页大小)和 PG_SIZE (内核管理且用户空间可见的页大小)。只有当 PTE_SIZE 和 PG_SIZE 相等时,才会定义 PAGE_SIZE 宏。他说,页帧号(Page-frame numbers)将始终引用 PTE_SIZE 帧。
毋庸置疑,内核中有许多地方需要更改以反映这种新的世界观。创建页表项将变得更加复杂,因为必须考虑 ( PG_SIZE ) 页内的偏移量;所有处理 PTE 的函数都将获得一个新的偏移量参数。虽然内核管理的是 64KB 页,但用户空间看到的页大小仍将一如既往地是 4KB。因此,在这种系统上成功运行不需要对用户空间进行任何更改。
Shutsemau 说,最具挑战性的部分是页错误处理,因为必须为每个发生错误的页映射多个 PTE 。用户空间仅被维持在 4KB 的对齐要求上,这意味着虚拟内存区域 (Virtual Memory Areas, VMAs) 可能在 64KB 页的中间开始或结束。因此,页错误处理程序最终可能在发生错误时仅映射页的一部分;在这种情况下,页中未映射的部分将直接被浪费掉。未对齐的页也可能导致内存浪费。
Wilcox 说,在这些系统上,写时复制 (Copy-on-Write, COW) 错误将变得更加昂贵,因为它们必须将周围的基础页也加载进来以填满一个 64KB 页。相反,David Hildenbrand 担心 userfaultfd() 将如何实现;它可能需要一个新的操作来安装单个 PTE 而不是整个页。
Hildenbrand 还建议,最好在整个系统中统一使用 64KB 页大小;他说这会让大家的生活都变得更轻松。Shutsemau 回答说,这实际上只是将复杂性转移到了架构代码中,架构代码必须实现一个基础页大小较大的假象,并对内核其余部分隐藏细节。转向较大的基础页大小还会破坏一些应用程序。Hildenbrand 对后一点并不表示同情,称这类程序要么应该被修复,要么就只在 4KB 系统上运行。
Jason Gunthorpe 说,在 Arm 系统上已经有很多关于 64KB 页大小的经验了。他说,用户往往会反对,因为总会有那么一个只能在 4KB 页下运行的特殊应用程序。另一位与会者询问,既然内核对 mTHPs 的支持正随着时间的推移变得越来越好,为什么还需要这种复杂性。Shutsemau 说,这个想法的部分问题在于并非所有文件系统都支持较大的 folio。坚持较小的基础页大小也使得系统更难分配较大的内存块。
在内存浪费的话题上,Hildenbrand 提出了创建 “负阶 folio”(negative-order folios)来表示子页(sub-page)内存块的可能性。还有人建议使用 slab 分配器进行子页分配,但这并非在所有情况下都有效。
随着会议接近尾声,Shutsemau 承认他并没有看到大家对他的提议有太多热情。他询问根本的反对意见是什么。Hildenbrand 回答说,在当前的内核中,零阶 folio 就是单个页;改变这一理解将涉及对 folio 处理方式的重大改变。他要求一种更简洁的方法,一种不需要 “怪异的页部分接口”(weird part-of-page interfaces)就能达到预期目标的方法。
Gunthorpe 说,根本的限制在于必须有一种方法来运行需要 4KB 页大小的旧应用程序。最好能找到一种方法,在对内核干扰最小的情况下,在具有较大基础页大小的系统上解决这个问题。会议结束时,Hildenbrand 表示,内核中的其他工作正在解决 Shutsemau 提议更改背后的许多动机。鉴于此,他建议 64KB 基础页可能并非未来;正确的道路可能是更好地优化 4KB 页系统的运行。
LWN 评论概述:
[本文引发了关于内存管理复杂性与性能收益之间权衡的讨论。]
部分评论者指出内核已经拥有大 folio 机制,这能以更稳健的方式提供类似收益,而无需引入新的怪异特性,目前的问题在于并非所有代码都已完成转换。也有人提到,为了 1.7% 的性能提升而引入如此巨大的复杂性是否值得,特别是在面对内存内碎片等副作用时。此外,针对旧应用的兼容性问题,有人提议是否可以让旧应用运行在传统虚拟机中,而让现代应用原生利用大页。关于 “负阶 folio” 的提议,Hildenbrand 澄清那并非他的本意,可能只是在讨论过程中产生的某种构想。
关注了就能看到更多这么棒的文章哦~ 全文完LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

27

被折叠的 条评论
为什么被折叠?



