LWN:用 msharefs 共享页表!
共 3076字,需浏览 7分钟
·
2022-08-03 05:48
关注了就能看到更多这么棒的文章哦~
Sharing page tables with msharefs
By Jonathan Corbet
July 15, 2022
DeepL assisted translation
https://lwn.net/Articles/901059/
页表项(PTE, page-table entry)本身相对较小,在大多数系统上只需要 8 个字节就可以指向(refer to)一个 4096 字节的 page。因此,看起来它带来的开销并不怎么让人担忧,而且历史上来说内核几乎没有费力去减少页表的内存消耗。但是,如果这 8 个字节的方案被复制来支持非常多的进程时,就会带来较大的损失了。来自 Khalid Aziz 的 msharefs 这个 patch set 就是解决这个问题的一个修改方案,但是它在内存管理社区看起来很难被接受。
在 Linux(也包括其他大多数操作系统)上,一个进程的决定性特征之一就是有一个独立的地址空间。因此,管理该地址空间状态的 page-table 对每个进程来说都是私有内容(尽管一个进程中的线程还是会共享页表的)。因此,如果两个进程对物理内存中的同一个 page 都有映射,那么每个进程都有一个独立的 page-table entry 来管理这个 page。因此,PTE 的开销会随着映射每个 page 的进程的数量而线性增加。
尽管如此,这种开销通常不算是什么问题,但这世界上总有一些人在做一些离奇的工作。正如这组 patch 的 cover letter 中所述:
在一个拥有 300GB SGA [Oracle 系统全局区域,https://docs.oracle.com/database/121/ADMQS/GUID-A3319550-AB7A-4429-9A58-4B90E4B3D0F5.htm] 的数据库服务器上,当 1500 多个客户试图共享这个 SGA 时,尽管系统拥有 512GB 的内存,却还是出现了系统崩溃的情况。在这台服务器上,在最坏的情况下,所有 1500 个进程映射 SGA 中的所有 page 的话就需要 878GB 以上的空间来存放 PTE 了。如果这些 PTEs 可以被共享,那么可以节省海量内存。
因此这个工作的目标就是能让这些 PTE 共享,这一点在 5 月份的 Linux 存储、文件系统、内存管理和 BPF 峰会上讨论过。当时,Aziz 提出了一个新的系统调用(mshare())来管理这种共享。目前的 patchset 已经修改了接口,现在根本不需要新的系统调用了。
即使没有新增系统调用,这些进程仍然需要有办法来明确请求把特定范围内存的页表进行共享。目前的 patch set 提供了另一个内核虚拟文件系统 msharefs 来实现这个目标;这个文件系统需要被挂载在/sys/fs/mshare。读取该文件系统中的 mshare_info 文件就可以得知要让内存区域能够共享页表所需的最小 alignment 需求。
下一步是在/sys/fs/mshare 下创建一个文件,这个名称应该是根据相关应用程序来的。然后,使用 mmap() 调用将该文件映射到进程的地址空间里。传递给 mmap() 的 size 会决定内存共享区域的 size。编者阅读代码的理解是,最好在映射时提供一个明确的地址,目前似乎没有自动机制来帮助选择符合 alignment 要求的地址。在该区域被映射之后,就可以像其他内存范围一样直接使用了。
创建这样的区域的目的是为了让其他进程也能对它进行映射。其他任何进程都需要从打开第一个进程创建的 msharefs 文件开始,然后从中读取出这样的一个结构:
struct mshare_info {
unsigned long start; unsigned long size;};
start 和 size 字段分别提供了区域被映射到的地址和 size 信息;新进程应该将这些值(以及打开的 msharefs 文件)传递给它自己的 mmap() 调用来对共享区域进行 map。之后,该区域就像是跟其他共享内存区域一样进行映射的了,但还是有几个重要的差异,将在下面描述。
一个进程的地址空间是由 mm_struct 结构所描述的;系统中的每个进程(除内核线程外)都有一个这样的结构。msharefs patchset 通过为每个共享区域来创建一个新的 mm_struct 结构,从而改变了这个结构和其所属进程之间一直以来沿袭的一一映射的关系。用来描述这个区域的 page table 属于这个独立的结构,而不是属于任何进程的 mm_struct。每当一个进程要映射这个区域时,相关的 vm_area_struct(VMA)将包含一个指向这个特殊 mm_struct 的指针。最终结果是,所有映射这个区域的进程不仅共享内存,而且还共享与之相关的页表。
当然,这节省了原本用于存放重复页表的内存,但这样做也会带来一些其他令人惊讶的结果。例如,用 protect() 改变该区域内存的 protection 情况,就会影响到所有共享该区域的进程;而对于普通的共享内存,只有调用者进程才会看到 protection 发生的变化。同样,内存区域可以用 mremap() 来完全进行 remap,所有用户都会看到这种变化。
看起来,使用 mremap() 实际上是 PTE 共享内存区域方案的预期效果之一。用来创建这个区域的 mmap()调用将用 anonymous memory 来 populate 这个区域;无法要求使用 file-backed memory。但是可以使用 mremap() 来把原始 mapping 都 dump 出来,并在之后替换成 file-backed memory。因此,应用程序如果想要同时使用 shared page table 以及 file-backed memory 的话,就不得不执行这个额外的步骤来设置好。
在 LSFMM 会议上,开发人员明确表示看起来这整个方案有点可怕。到目前为止,对这组 patch 的回应(从内存管理的角度来看)相对比较少,只有 David Hildenbrand 例外,他正在推进另一种不同解决方案。他更希望看到一种机制,能在 mapping 被共享时就自动共享页表,而不需要在应用层面进行修改。这种方案将可以使这个 sharing 带来的好处能更容易地被利用,同时也暴露出更少的内存管理内部细节。
不过,自动共享需要有不同的语义(semantics);否则,当另一个进程中的 mprotect()或 mremap()调用改变了这些 mapping 时,应用程序肯定会受到惊吓的。虽然在 Aziz 的这一版本的 patch 中没有说明,但从 LSFMM 会议上得到的感觉是需要来改变语义。如果是这样的话,完全的自动共享将是不可能实现的,因为应用程序总是需要选择是否允许这种行为。
不管怎么说,在进入 mainline 之前,这个特定的 patchset 需要更多的工作和讨论。在那之前,依赖于在大量进程之间共享内存的应用程序将继续为 page-table 支付高额成本。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~