关注了就能看到更多这么棒的文章哦~
An overview of kernel samepage merging (KSM)
By Jake Edge
November 29, 2023
LPC
ChatGPT translation
https://lwn.net/Articles/953141/
[2023-12-08 Fri 12:57]
在2023 Linux Plumbers Conference(LPC)的Kernel Summit track上,Stefan Roesch 主持了一场关于内核同页合并(KSM, kernel samepage merging)的讨论。他概要介绍了这一功能,并描述了KSM的一些最新改动。他演示了应用程序如何启用 KSM 来对其内存去掉重复部分,以及如何评估该功能是否适用于新的工作场景。此外,他还提供了来自他在 Meta 工作场所的一些实际数据。
KSM 基础知识
KSM 的概念是“非常简单”的:完全共享同一个副本,去除重复的匿名页。它于 2009 年添加到内核中,因此不是新功能,但在过去两年中引起了更多关注。最初的用例是为了对虚拟机(VM)的内存去重,但还有其他用例。
为了完成其工作,KSM 有一个内核线程 ksmd,会扫描启用了 KSM 的虚拟内存区域(VMAs)中的匿名页面,Roesch 称之为“候选页面(candidate pages)”。它按三个主要阶段来完成工作,使用页面内容的哈希值迅速跟其他页面的哈希值进行比较,从而以确定页面是否重复(或其内容是否发生更改)。为每个候选页面都创建一个 rmap_item
以跟踪其哈希值;如果候选的哈希经常更改,它就不适合去重。
在第二阶段,任何未更改的候选页面都会被添加到“不稳定(unstable)”树中;如果发现候选页面本身已经在不稳定树上,就会被移到“稳定(stable)”树上。此时,具有相同内容的其他页面也会被切换过来到稳定树上使用单个 page。会使用写时复制(CoW)机制确保对任何副本的写入得到正确处理。
有两种方法可以将匿名页面添加到候选集中。以前的方法是使用 madvise()
系统调用,而新的方法使用 prctl()
系统调用;后者是由 Roesch 开发的。并非所有的内存区域都适合使用 KSM,因此对于使用 DAX、hugetlb 和共享 VMA 的区域都需要排除在外。
madvise()
机制使用 MADV_MERGEABLE
标志来指示 KSM 这个内存区域可以进行合并;其页面将被添加到候选中。这种方法的问题在于,你必须猜测哪些内存区域将受益,因为对于该区域的去重效果好坏无法得到有效的反馈。
新的基于 prctl()
的方法在 6.4 内核中添加的, PR_SET_MEMORY_MERGE
标志可用于在进程的所有兼容 VMAs 中启用 KSM。这个设置在进程 fork 时也会继承,因此对于任何子进程来说,KSM 也将在兼容 VMA 上启用。 PR_GET_MEMORY_MERGE
标志可用于查询进程是否启用了 KSM。
KSM 的系统范围配置通过 /sys/kernel/mm/ksm sysfs 接口完成;该目录中有多个文件,用于监控和配置该功能。run文件用于在系统上启用或禁用该功能,pages_to_scan 确定 ksmd
每次唤醒时扫描多少页面,而 sleep_millisecs
设置扫描的频率。后两者决定了 KSM 运行的激进程度。
对于监控目的来说,sysfs 目录中有一些文件,还有/proc/PID 目录中的文件。特别是/proc/PID/ksm_stat 文件包含有关该进程的 KSM 的一些信息,而 6.6 内核为 smaps 和 smaps_rollups 文件添加了一些额外的 KSM 信息。这些信息可用于查看哪些 VMAs 受益于 KSM。
/sys/kernel/mm/ksm 中的监控文件包括 KSM 的系统范围测量,例如 pages_shared 表示通过 KSM 共享的页面数,pages_sharing 表示对 KSM 共享页面的引用数(即有多少页面正在进行去重),pages_unshared 表示独特且未共享的不变页面的数量,而 pages_volatile 表示变化过快的页面数。为了 6.6 版本添加了 pages_scanned 文件,用于计算扫描的总页面数,该文件可以与 full_scans
(扫描完成的次数)结合使用,以确定扫描阶段完成了多少工作。
在 6.4 内核之前,有一个挑战是无法确定扫描需要多长时间。为解决这个问题,Roesch 在 KSM 中添加了一些 tracepoints,允许测量扫描时间;ksm_start_scan 和 ksm_stop_scan 是两个最重要的 tracepoints,还有一些对于更专业的调查也很有用。
在 Meta
然后,他介绍了 Meta 如何使用 KSM。Instagram Web 应用在旧服务器系统上遇到了内存和 CPU 压力的问题。这个工作负载的特点是有单独一个控制器进程以及 32 个或更多的工作进程;工作进程的数量根据系统的大小而变化。这些工作进程在启动时将它们的解释器加载到内存中,它们还共享许多其他按需加载的数据结构。
Meta 的工程师们认为 KSM 对于这种工作负载会很有效,因为有很多内存可以共享。当时,启用 KSM 的唯一方法是通过 madvise()
调用。工作进程在由 systemd 启动的控制组(cgroups)中运行,因此提出了进程级别的 KSM 启用的想法,以及在 fork()
跨进程继承该状态的想法。
这就是为什么 6.4 版本添加了 prctl()
标志。与此同时,systemd 修改 之后添加了 MemoryKSM
参数,以启用 systemd 服务的 KSM。这种方法的优势是应用程序代码根本无需更改即可充分利用 KSM。
当他首次在这个工作负载上测试 KSM 时,Roesch 说,“结果可以说非常令人失望”;实际上几乎没有内存共享发生。他意识到默认的 pages_to_scan
值设置为 100,这显然“远远不够低”;后来他注意到文档说,默认值仅用于演示目的。当时也没有 tracepoints 可用,这使得更难追踪问题。
事实证明,在 Instagram 的工作负载上, pages_to_scan
的一个合适的折衷值是 4000-5000。他测试过的其他工作负载需要 2000-3000 的参数值。重要的是人们要知道,这个值需要进行更改;查看内存节省和执行完整扫描所需的时间是确定最佳值的很好的指标。如果执行完整扫描需要 20 分钟,这表明 pages_to_scan
太低;Meta 试图将扫描时间保持在两到三分钟左右。
优化
一旦 Meta 开始更仔细地查看扫描过程,就清楚地看到 KSM 在扫描大量页面,特别是在工作进程启动期间的初始阶段。即使在达到某种稳态之后,仍然有许多页面被反复扫描,但它们是唯一的,因此永远不会被共享。这就引出了把跳过页面作为一种优化以减少 CPU 使用率的想法。
“智能扫描”优化功能已经合并到6.7内核,它在每个 rmap_item
中存储了一个跳过计数(skip count),该计数决定是否在处理中跳过该页面。每次发现页面再次变得唯一时,这个跳过计数会增加(最多跳过八个扫描周期)。智能扫描默认启用,它每个周期减少 10-20%的扫描页面数。
目前正在讨论的优化可以帮助调整扫描页面的数量。现在,该值需要根据需要扫描超过两倍的页面数的启动时间来设置;一旦达到稳态, pages_to_scan
值可以减少。其他工作负载表现出类似的行为,因此"自动调整"优化可以管理页面扫描的侵略性。该想法是确定扫描所有候选页面所需的时间目标,这是自动调整将尝试优化的内容。还将设置最小和最大 CPU 使用百分比,以限制扫描。
到目前为止,自动调整的结果令人鼓舞。在启动时, pages_to_scan
设置为 5000-6000,但在系统达到稳态时,该值会减少到 2500 甚至更低。这导致 ksmd
的 CPU 使用率节省了 20-30%。使用目标扫描时间和 CPU 使用限制进行配置对管理员更有意义,他说。
评估新的工作负载
启用 KSM 的最简单方法是使用 prctl()
标志来给进程打开这个功能。这可以通过更改应用程序本身来实现,使用 systemd 参数、或者通过使用一个在程序加载时调用的 LD_PRELOAD
库来完成。最后一种选择是可行的,但前两种更有优势,他说。
下一步是在代表性工作负载上运行程序。可以查看/sys/kernel/mm/ksm/general_profit 文件,了解节省了多少内存;该数值排除了 KSM 本身使用的内存。还可以查看/proc 文件以获取更多有关每个进程的信息。
然而,为了获得有意义的数据,有必要重新运行具有不同 pages_to_scan 值的测试。页面扫描的侵略性取决于工作负载,因此运行足够长时间的测试以获取全景是非常重要的。他再次强调, pages_to_scan
的默认值根本不足够,因此需要进行调整。
通常情况下,应用程序具有受益于 KSM 的某些 VMA,而其他 VMA 则没有受益。/proc/PID/smaps 文件现在包含有关 KSM 的条目,可帮助显示哪些 VMA 受益最多。一旦知道了这一点,就可以删除 prctl()调用,并仅对那些 VMA 进行单独的 madvise()调用。他提出的一个一般建议是,较小的页面大小更适合与 KSM 一起使用,因为更有可能进行共享。
如今,评估新的工作负载以启用 KSM 需要在启用 KSM 的情况下运行实验,但可能存在无法启用 KSM 或无法运行这些实验的情况。他对评估工作负载的方法有一些想法,并正在寻求反馈。其中一个是内核内方法,另一个使用drgn内核调试器。
目前,他只是为 drgn 拼凑了一些东西,尚未发布,但其想法是遍历所有 VMAs 并收集页面的哈希,将其存储在 Python 字典中。可以处理这些信息来查看有多少可以共享。这相当简单,但也相当慢;如果只检查了少数进程,则“可能可以”,但如果要分析整个系统,则“我们需要更进一步的功能”。
内核内的替代方法将提供一种计算页面哈希以便评估共享的方法。更先进的方案实际上将维护不稳定和稳定的树,但不进行合并;这将提供关于可以进行多少共享的更准确的信息,但代价更高。这是他正在考虑的一些想法,因为 Meta 还有其他可能受益于 KSM 的工作负载,但运行实验以确定哪些工作负载受益较多是非常耗时的。
然而,需要考虑与 KSM 相关的一些安全问题,尽管“如果您控制您的工作负载,这就不太令人担忧”。然而,KSM 存在已知的侧信道攻击——他在幻灯片中链接了两篇论文——因此在决定使用 KSM 时应考虑到这一点。此外,KSM 并不适用于所有工作负载;特别是对于延迟敏感的工作负载,KSM 不是一个好的选择。
他总结了在内核中的 KSM 相关的在 6.1、6.4、6.6 以及即将到来的 6.7 中的改动,并向即将到来的自动调整功能致以敬意。他还赞扬了他的一些同事对该功能的贡献,以及 systemd 开发人员在解决这个难题方面的帮助。
Omar Sandoval 问自动调整是在内核中完成的还是由用户空间驱动的。Roesch 说这一切都是在 kernel 里基于三个参数(目标扫描时间、CPU 最小/最大)完成的。这些参数有默认值,对于大多数工作负载来说应该是可以的,但可能需要根据页面数和 CPU 可用性进行微调。
另一个问题是启用 KSM 的 CPU 和内存开销。Roesch 说文档中有一个计算内存开销的公式,但开销并不大;开销之一是 rmap_item
条目,其中包括在其基础上 overlay 的不稳定树以及稳定树。 CPU 开销取决于扫描的侵略性有多大;在典型的 Instagram Skylake 系统上,在启动过程中,“我们看到 ksmd 内核后台线程的 CPU 使用率高达 60%”,在稳态下降到 30%左右。
[我要感谢 LWN 的旅行赞助商,Linux 基金会,对我前往 LPC 的协助。]
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~