LWN:让vma的匿名页不再匿名!
关注了就能看到更多这么棒的文章哦~
Not-so-anonymous virtual memory areas
By Jonathan Corbet
September 3, 2021
DeepL assisted translation
https://lwn.net/Articles/867818/
计算机术语的名字有些时候是不那么直观的。但就算是业内的长期参与人员可能也会对 anonymous memory 这个名字也会觉得有些费解。Suren Baghdasaryan 发布了一组 patch set 正是希望让 anonymous memory 不那么匿名。目前看来,有一些开发者认为这个想法很有用,不仅可以帮助克服一上来的认知偏差,而且还又复活了一个 8 年前的 patch 从而将其纳入内核。
用户空间(user space)使用的内存分为两大类:有 file-backed(有对应文件的)和 anonymous(匿名的)。file-backed page 这些内存页都是与持久性存储中的一个文件中的 page 有着直接对应的关系。当该 page 尚未被污染时,其内容与磁盘上的内容完全相同。相应地,一个匿名页(anonymous page)与文件系统中的文件就没有关联了。这些 page 用来存放进程中的数据区、堆栈等。如果一个匿名页必须得要写入持久性存储了(通常是为了回收这个 page 来挪作其他用途),那就必须要在 swap 区域先分配好空间来存放其对应内容。
每个进程中是有对应文件的 memory page 更多呢,还是匿名页更多呢?这在不同的工作场景下是不一样的。很多情况下进程中大部分 page 都是匿名页。尤其是在大量的云计算客户端的工作场景中更加是这样,这些客户端程序往往不怎么使用本地文件。安卓设备就是一个典型的例子。如果有人试图针对这种工作场景的内存使用情况进行优化,那么匿名页就会给他们带来更多挑战。因为 page 是匿名的,也就是没有它们是如何被创建的这些信息,所以很难知道各个匿名页都被用来做什么了。
这一点还是可以改善的,那就是让匿名页面变得不那么匿名。如果能够知道是用户空间的哪个子系统或函数库创建的这个 page,就会更容易弄清楚谁是最主要的使用者。例如系统中如果记录了有多少匿名页是由 jemalloc 库创建的这个信息,就可以帮助确定对 jemalloc 的使用是不是一个我们应该优先优化的目标。然而,Linux 系统并不容易(甚至不可能)获得这类信息。
要想让情况变得更好,就需要从用户空间获得一些配合,因为内核不可能知道具体是用户空间的哪个子系统正在分配当前这个 page。这组 patch set 的核心就是 Colin Cross 的一个用来帮助完成这个工作的 patch,它最早在 2013 年就发布出来了,增加了一个新的 prctl() 操作。
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, start, len, name);
这个函数会把 name 跟从 start 开始的 len 长度的匿名页关联起来。实际上这个 name 是跟用来描述这一段内存范围的虚拟内存区域(VMA)结构关联起来的。因此,实际上所有属于给定范围内的 VMA 的 page 内的分配都将得到这个 name,即使这些 page 本身不在这个范围内。每个 mmap() 调用通常会创建一个 VMA(尽管有一些情况会比较复杂),所以与任何指定 VMA 相关的所有 page 通常都是按同样的方式创建的。
每个进程的 /proc 目录下的 maps 和 smaps 文件中已经包含了很多关于该进程的 VMA 信息。在合入了这组 patch 之后,这些文件也将包含与 anonymous VMA 相关联的 name(如果之前 name 已经关联上来的话)。在接受这个 name 之前,会先检查这个 name 是否可以被 print 出来。系统工具就可以利用这些信息来将 page 与这些 name 关联起来,从而能跟创建这些 page 的子系统关联起来。
给 VMA 分配一个名字看起来并不困难,但事实上这是这个 patch 中最棘手的部分。一个系统中可以有很多进程,每个进程都可以有很多 VMA,所以这些 name 的管理方面需要能很容易地扩展。早期版本的 patch set 曾尝试直接指向用户空间所所提供的 name,这样就不需要在内核中分配内存了,但是,正如 Kees Cook 所指出的,它也带来了一些有意思的 security 问题。当时 Cook 建议直接将字符串复制到内核空间。
虽然复制字符串的做法是可行的,但仍有一个小问题:当一个进程进行 fork 时,它的 VMA 会被复制给新的子进程所用。那么现在所有这些 name 字符串也必须要被复制了。Baghdasaryan 做了一个最坏情况的测试,也就是用一个进程创建了 64000 个 VMA,给每个 VMA 分配了一个很长的 name,然后调用 fork(),结果是性能下降了近 40%。即使这种情况在现实世界的工作场景中应该不会出现,但这种下降幅度已经足以引起人们的关注了。
为了避免得到过得的抱怨,Baghdasaryan 增加了一个机制来实现 name 的公用机制,并且带有引用计数。这样一来现在 fork() 调用就只需要增加引用计数而已,不用再分配内存以及复制字符串了。采用了这个新增机制之后,最坏情况下,性能降幅 "减少到了原来降幅的 1/3 ~ 1/4",而且据说在那些合理的测试场景下都看不到有下降了。
这个功能显然是很有用的,安卓已经使用这个机制很多年了,它一直带着之前最早期的那个版本的 patch。到目前为止,review 反馈意见都集中在一些较小问题上——例如 name 中可以使用哪些字符。因此,看起来要合入这组 patch set 没有太多困难需要解决了。对于这个功能来说,八年的等待应该足够了,anonymous page 可能很快就会不再那么
anonymous 了。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~