关注了就能看到更多这么棒的文章哦~
Reducing page structures for huge pages
By Jonathan Corbet
December 11, 2020
DeepL assisted translation
https://lwn.net/Articles/839737/
内核开发中会不断想办法减少执行中的额外开销,被内核占用的资源都不能用于执行用户实际想要运行的工作。其中的一点就是要减少用来管理内存的 page structure,尽管已经在尽可能地少用了,page structure 通常只占用了略超过 1.5% 的可用内存,但是这对一些用户来说还是太多了。LWN 最近介绍了使用 DMEMFS 来减少这种 overhead,但这并不是这方面唯一在进行的工作。目前有两个开发人员独立地开发出了两种解决方案,希望减少与 huge page 相关的开销。
Memory maps and tail pages
page structure,保存了内核针对系统中每一个内存 page 所了解的大部分信息:它用在什么地方、在各种列表中的位置、它对应的真实存储的位置等等。因此,系统中的每一个物理页都有一个 page structure,在最常见的 config 下,这意味着每 4096 字节(一个 page)就分配了一个 64 字节的 struct。
在早期的 Linux 中,内核会为 page structure 直接分配在数组中管理,这个数组的大小刚好可以说明系统中安装了多大的内存。这是一种很合理的做法,因为物理内存本身也就可以看做是一系列的 page。不过在那之后,内存管理子系统的逻辑变得越来越复杂。NUMA 系统中的不同 node 有不同大小的内存,甚至可能差异非常大。内存还可以在 runtime 动态加入到系统中(或从系统中移除)。虚拟化中的 guest 也可以在运行时加入部分内存(或移除)。因此,简单的、线性的内存模型不再是那么好用了。
随着时间的推移,内核对内存映射使用了几种不同的模型;完整的历史见之前的文章《Memory: the flat, the discontiguous, and the sparse》。而目前首选的模型(对于 64 位系统来说)被称为 "sparsemem-vmemmap"。它会使用系统的内存管理单元(MMU)来创造出一个假象,似乎系统仍是拥有一个简单的、线性的映射(称为 "vmemmap")。具体来说,每个体系架构都会为这个线性映射来保留部分内核地址空间。比如 x86-64 架构中,在使用四级页表时,就把它放在 0xffffea0000000000。每当系统中增加了内存(包括内核在启动时发现的 "added" 的内存),就会分配对应数量的 page struct,并为这组内存在 vmemmap 中创建映射。这样,不连续的内存块就可以看起来是连续的,从而简化了一些底层管理功能。
最终结果大致是这样的:
[vmemmap]
在一个有 4096 字节 page 和 64 字节 struct page 的系统上,每 64 个 struct page 就需要分配一个内存 page 并映射到 vmemmap 数组中。在完成了这些工作之后,就可以通过使用 page-frame number 作为 vmemmap_base 的偏移量来找到指定 page 的 struct page(这是 x86 系统上的做法)。
Compound pages(https://lwn.net/Articles/619514/) 使得情况变得更加复杂。当一组相邻的 page 组合在一起按更大粒度来管理时,就形成了一个 compound page。它最常见的用途就是在 huge page 场景,也就是系统的 CPU 和 MMU 所支持的更大的 page size。例如,x86-64 架构就实现了 2MB 和 1GB 的 huge page,使用这两种 page size 可以带来显著的性能优势。每当使用一组 single ("base") page 来创建出一个 huge page 时,相关的 struct page 都需要修改,从而反映出它们现在所代表的 compound page。
compound page 中的第一个 base page 被称为 "head" page,而所有其他 page 则被称为 "tail" page。因此,一个 2MB 的 huge page 是由一个 head page 和 511 个 tail page 组成的。head page 的 struct page 被标记为 compound page,代表了这个整体。而 tail page 的 struct page 则只包含一个指向 head page 的指针。(这种说法并不完全正确,因为前几个 tail page 里有一些关于 compound page 的 metadata,但这里可以忽略)。
因此,在与一个 2MB 的 huge page 相关联的 512 个 struct page 中,有 511 个基本上是相同的副本,上面写着 "看别的地方"。这些结构本身就占用了 8 个内存 page,其中 7 个内存页都只包含 tail page,并且内部的数据是完全一样的。
Trimming wasted page structures
本文介绍的两套 patch set 都采取了同样的方法来节省内存。最先出现的是 Muchun Song 的 patch set,目前已经是第八版了。Song 意识到,没有必要把那些充斥着相同内容的 page 都保留下来,尤其是 vmemmap 机制已经使用了 virtual mappping 机制。如果针对上图,有一个更紧凑的版本:
[tail page]
在这里,一个 2MB 的 huge page 由那 8 个完全都是 page struct 的 page 来表示,并且几乎所有的 struct page 都是 tail page,只是一个个指向 head page 的结构而已。
如果打上 Song 的 patch 的话,这个结构就被改变了。由于这 8 个 page 中的 7 个 page 都包含相同的 page 内容,所以可以用一个 page 来代替。这一个 page 可以被映射 7 次,就能填满之前的 vmemmap 数组。
[shared tail pages]
内核的其他部分,不需要任何改动。任意一个 tail page 的 struct page 看起来都和以前一样。但现在在使用 compound page 的时候,就可以省出来 6 个内容重复的 page 给系统用在其他地方了。换句话说,这就消除了 compound page 中引入的额外 75%的内存开销。
对于 1GB huge page 来说,节省的内存就更为显著了。代表 tail page 的 4096 个 page 中的 4094 个不再需要了。通用系统中往往不会多使用 1GB huge page,但在某些情况下,例如一些虚拟化场景中这种 1GB huge page 就很有用了。每一个 1GB huge page 节省近 16MB 开销,当然这会非常吸引虚拟主机提供商了。
huge page 不一定会永远保持这个状态,它们可以被返回给内核,或者出于一些考虑而被拆分开。当这种情况发生时,就必须恢复回完整的多个 struct page 集合了。在 Song 的 patch set 中,这项工作被推迟到一个 work queue 中执行,这样就可以在一个相对宽松的环境中来分配这些必需的 page 了。这种改动在分配和释放 huge page 的过程中都增加了一些额外计算,从而引入一些时间开销。Song 提供了一组性能测试结果,认为这个开销 "并不显著"。另外,由于该 patch set 禁止 vmemmap 本身使用 huge page,所以也带来了一些额外开销。如果这组改动想被接受,显然需要把这里改掉。
另一个 patch set 来自 Joao Martins。它基于同样的想法,取消了大部分包含 tail page struct 的内存 page。Song 的 patch set 专注于主内存,而 Martins 的工作是专门针对非易失性(nonvolatile) RAM 的。nonvolatile RAM 总是通过一个独立操作来加进来的,所以不需要考虑把将现有的 page 从 vmemmap 中释放出来。相反,这种 RAM 从一开始就是以 huge page 的形式来加进来的。这简化了代码,不需要动态改变 vmemmap 的这部分逻辑了,但代价就是导致这个 patch set 的适用范围比较窄。
不过,这样处理 nonvolatile RAM 的一个附加好处是,当加入一个新的 memory array 时,不需要初始化大量的 struct page。这就大大加快了把这个 RAM array 初始化好从而提供给系统来使用的过程。使用 huge page 也大大加快了将这些内存映射到内核空间的动作,这个动作在使用 DAX direct-access subsystem 时经常需要用到。
两种 patch set 似乎各有利弊。目前的问题是,内存管理开发人员并没有很大兴趣来把两个改动都合入进去。因此,要么必须选择一个放弃另一个,要么就需要以某种方式来将这两个 patch set 合并成一个满足大家需求的版本。这里的选择会需要一些时间。同时要想把底层的内存管理代码合并起来也不可能很快完成。所以这项工作可能不会在近期内合入 mainline,但从长远来看,这种优化会给 Linux 系统节省大量的内存。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~