内核同页合并

概述

KSM 是一种节省内存的去重功能,由 CONFIG_KSM=y 启用,在 2.6.32 版本中添加到 Linux 内核中。 有关其实现,请参阅 mm/ksm.chttp://lwn.net/Articles/306704/https://lwn.net/Articles/330589/

KSM 最初是为与 KVM 一起使用而开发的(其中称为内核共享内存),通过共享它们之间通用的数据,将更多的虚拟机放入物理内存中。 但它可以对生成许多相同数据实例的任何应用程序有用。

KSM 守护程序 ksmd 定期扫描已向其注册的用户内存区域,查找内容相同的页面,这些页面可以替换为单个写保护页面(如果进程稍后要更新其内容,则会自动复制)。 KSM 守护程序在一次传递中扫描的页面数量以及传递之间的时间间隔是使用 sysfs 接口配置的

KSM 仅合并匿名(私有)页面,从不合并页面缓存(文件)页面。 KSM 合并的页面最初被锁定在内核内存中,但现在可以像其他用户页面一样被换出(但当它们被换回时,共享会被中断:ksmd 必须重新发现它们的身份并再次合并)。

使用 madvise 控制 KSM

KSM 仅在应用程序已通过使用 madvise(2) 系统调用建议为可能合并的候选项的地址空间区域上运行

int madvise(addr, length, MADV_MERGEABLE)

应用程序可以调用

int madvise(addr, length, MADV_UNMERGEABLE)

取消该建议并恢复未共享的页面:之后 KSM 会取消合并在该范围内合并的任何内容。 注意:此取消合并调用可能突然需要比可用内存更多的内存 - 可能导致 EAGAIN 失败,但更有可能引起内存不足杀手。

如果 KSM 未配置到正在运行的内核中,则 madvise MADV_MERGEABLE 和 MADV_UNMERGEABLE 只是会因 EINVAL 而失败。 如果正在运行的内核是使用 CONFIG_KSM=y 构建的,则这些调用通常会成功:即使 KSM 守护程序当前未运行,MADV_MERGEABLE 仍然会注册该范围,以便在 KSM 守护程序启动时使用;即使该范围不能包含 KSM 实际可以合并的任何页面;即使 MADV_UNMERGEABLE 应用于从未是 MADV_MERGEABLE 的范围。

如果必须将内存区域拆分为至少一个 MADV_MERGEABLE 或 MADV_UNMERGEABLE 新区域,则如果进程将超出 vm.max_map_count,则 madvise 可能会返回 ENOMEM(请参阅 /proc/sys/vm/ 的文档)。

与其他 madvise 调用一样,它们旨在用于用户地址空间的映射区域:如果指定的范围包含未映射的间隙(尽管在中间映射区域上工作),它们将报告 ENOMEM,如果内部结构的内存不足,则可能会因 EAGAIN 而失败。

应用程序在使用 MADV_MERGEABLE 时应谨慎,将其使用限制在可能受益的区域。 KSM 的扫描可能会使用大量的处理能力:出于这个原因,某些安装会禁用 KSM。

KSM 守护程序 sysfs 接口

KSM 守护程序由 /sys/kernel/mm/ksm/ 中的 sysfs 文件控制,所有用户都可读,但只有 root 用户可写

pages_to_scan

ksmd 进入睡眠状态之前要扫描多少页,例如 echo 100 > /sys/kernel/mm/ksm/pages_to_scan

如果 advisor_mode 已设置为 scan-time,则 pages_to_scan 值无法更改。

默认值:100(为演示目的选择)

sleep_millisecs

ksmd 在下次扫描之前应休眠多少毫秒,例如 echo 20 > /sys/kernel/mm/ksm/sleep_millisecs

默认值:20(为演示目的选择)

merge_across_nodes

指定是否可以合并来自不同 NUMA 节点的页面。 当设置为 0 时,ksm 仅合并物理上位于同一 NUMA 节点内存区域中的页面。 这为访问共享页面带来了较低的延迟。 具有更多节点且 NUMA 距离显着的系统可能会受益于设置 0 的较低延迟。 需要最小化内存使用的小型系统可能会受益于设置 1(默认值)的更大共享。 您可能希望比较一下您的系统在每个设置下的性能,然后再决定使用哪个设置。 只有在系统中没有 ksm 共享页面时,才能更改 merge_across_nodes 设置:先将 run 设置为 2 以取消合并页面,然后在更改 merge_across_nodes 后将其设置为 1,以根据新设置重新合并。

默认值:1(与早期版本一样,跨节点合并)

run
  • 设置为 0 以停止 ksmd 运行,但保留合并的页面,

  • 设置为 1 以运行 ksmd,例如 echo 1 > /sys/kernel/mm/ksm/run

  • 设置为 2 以停止 ksmd 并取消合并当前合并的所有页面,但保留为下次运行注册的可合并区域。

默认值:0(必须更改为 1 才能激活 KSM,除非禁用 CONFIG_SYSFS)

use_zero_pages

指定是否特殊处理空页(即仅包含零的已分配页)。当设置为 1 时,空页会与内核零页合并,而不是像通常那样彼此合并。这可以提高具有着色零页的架构的性能,具体取决于工作负载。启用此设置时应谨慎,因为它可能会降低某些工作负载的 KSM 性能,例如,如果候选合并页的校验和与空页的校验和匹配。此设置可以随时更改,仅对更改后合并的页面有效。

默认值:0(早期版本中的正常 KSM 行为)

max_page_sharing

每个 KSM 页允许的最大共享数。 这会强制执行去重限制,以避免涉及遍历共享 KSM 页的虚拟映射的虚拟内存操作出现高延迟。 最小值为 2,因为新创建的 KSM 页至少有两个共享者。 此值越高,KSM 合并内存的速度越快,去重因子越高,但对于任何给定的 KSM 页,最坏情况下的虚拟映射遍历速度可能会越慢。 减慢此遍历速度意味着在交换、压缩、NUMA 平衡和页面迁移期间发生的某些虚拟内存操作将具有更高的延迟,从而降低这些虚拟内存操作的调用者的响应能力。 其他未参与执行虚拟映射遍历的 VM 操作的任务的调度程序延迟不受此参数的影响,因为这些遍历本身总是对调度友好的。

stable_node_chains_prune_millisecs

指定 KSM 检查达到去重限制的页面的元数据以查找过期信息的频率。 较小的毫秒值将以较低的延迟释放 KSM 元数据,但它们会使 ksmd 在扫描期间使用更多的 CPU。 如果没有单个 KSM 页面达到 max_page_sharing,则它是一个空操作。

smart_scan

历史上,KSM 在每次扫描时都会检查每个候选页面。 它没有考虑历史信息。 启用智能扫描后,先前未去重的页面将被跳过。 跳过这些页面的频率取决于已尝试但失败的去重次数。 默认情况下,此优化已启用。pages_skipped 指标显示设置的有效性。

advisor_mode

advisor_mode 选择当前顾问。 支持两种模式:none 和 scan-time。 默认值为 none。 通过将 advisor_mode 设置为 scan-time,启用扫描时间顾问。 有关 advisor 的部分详细解释了扫描时间顾问的工作原理。

adivsor_max_cpu

指定 ksmd 后台线程的 cpu 百分比使用率上限。 默认值为 70。

advisor_target_scan_time

指定扫描所有候选页面的目标扫描时间(以秒为单位)。 默认值为 200 秒。

advisor_min_pages_to_scan

指定扫描时间顾问的 pages_to_scan 参数的下限。 默认值为 500。

adivsor_max_pages_to_scan

指定扫描时间顾问的 pages_to_scan 参数的上限。 默认值为 30000。

KSM 和 MADV_MERGEABLE 的有效性显示在 /sys/kernel/mm/ksm/

general_profit

KSM 的效果如何。 计算如下所述。

pages_scanned

正在扫描用于 ksm 的页面数

pages_shared

正在使用的共享页数

pages_sharing

有多少站点正在共享它们,即节省了多少

pages_unshared

有多少唯一但反复检查是否合并的页面

pages_volatile

有多少页面的更改速度太快,无法放入树中

pages_skipped

“智能”页面扫描算法跳过了多少页面

full_scans

扫描了所有可合并区域的次数

stable_node_chains

达到 max_page_sharing 限制的 KSM 页数

stable_node_dups

重复的 KSM 页数

ksm_zero_pages

在去重时,有多少仍映射到进程的零页面由 KSM 映射。

use_zero_pages 启用/曾经启用时,pages_sharing + ksm_zero_pages 的总和表示 KSM 实际节省的页面数。 如果 use_zero_pages 从未启用,则 ksm_zero_pages 为 0。

pages_sharingpages_shared 的高比率表示良好的共享,但 pages_unsharedpages_sharing 的高比率表示浪费精力。 pages_volatile 包含几种不同的活动,但那里的高比例也表示 madvise MADV_MERGEABLE 的使用不佳。

pages_sharing/pages_shared 的最大可能比率受 max_page_sharing 可调参数的限制。 要增加比率,必须相应地增加 max_page_sharing

监控 KSM 收益

KSM 可以通过合并相同的页面来节省内存,但也会消耗额外的内存,因为它需要生成许多 rmap_items 来保存每个扫描页面的简要 rmap 信息。 其中一些页面可能会被合并,但有些页面在多次检查后可能无法合并,这是消耗的无利可图的内存。

  1. 如何确定 KSM 是否在系统范围内节省内存或消耗内存? 以下是一个简单的近似计算供参考

    general_profit =~ ksm_saved_pages * sizeof(page) - (all_rmap_items) *
                      sizeof(rmap_item);
    

    其中 ksm_saved_pages 等于系统的 pages_sharing + ksm_zero_pages 的总和,并且可以通过将 pages_sharingpages_sharedpages_unsharedpages_volatile 相加来轻松获得 all_rmap_items。

  2. 通过以下近似计算,可以类似地获得单个进程中的 KSM 收益

    process_profit =~ ksm_saved_pages * sizeof(page) -
                      ksm_rmap_items * sizeof(rmap_item).
    

    其中 ksm_saved_pages 等于 ksm_merging_pagesksm_zero_pages 的总和,两者都显示在 /proc/<pid>/ksm_stat 目录下,并且 ksm_rmap_items 也显示在 /proc/<pid>/ksm_stat 中。 进程收益也显示在 /proc/<pid>/ksm_stat 中,为 ksm_process_profit。

从应用程序的角度来看,ksm_rmap_itemsksm_merging_pages 的高比率意味着 madvise 应用策略不佳,因此开发人员或管理员必须重新考虑如何更改 madvise 策略。 举个例子供参考,一个页面的大小通常为 4K,并且在 32 位 CPU 架构上,rmap_item 的大小分别为 32B,在 64 位 CPU 架构上为 64B。 因此,如果 64 位 CPU 上的 ksm_rmap_items/ksm_merging_pages 比率超过 64,或者在 32 位 CPU 上超过 128,则应删除该应用程序的 madvise 策略,因为 ksm 收益约为零或为负。

监控 KSM 事件

/proc/vmstat 中有一些计数器可用于监控 KSM 事件。 KSM 可能有助于节省内存,这是一种权衡,可能会在 KSM COW 或交换复制时遭受延迟。 这些事件可以帮助用户评估是否以及如何使用 KSM。 例如,如果 cow_ksm 增加太快,用户可能会减少 madvise(, , MADV_MERGEABLE) 的范围。

cow_ksm

在用户尝试写入 KSM 页面时,每次 KSM 页面触发写时复制 (COW) 时都会递增,我们必须进行复制。

ksm_swpin_copy

每次在交换时复制 KSM 页面时都会递增,请注意,KSM 页面可能会在交换时复制,因为 do_swap_page() 无法完成重建跨 anon_vma KSM 页面所需的所有锁定。

顾问

KSM 的候选页面数量是动态的。 可以经常观察到,在应用程序启动期间,需要处理更多的候选页面。 如果没有顾问,则 pages_to_scan 参数需要针对最大候选页面数进行大小调整。 扫描时间顾问可以根据需求更改 pages_to_scan 参数。

可以启用顾问,以便 KSM 可以自动适应要扫描的候选页面数量的变化。 实现了两个顾问:none 和 scan-time。 如果为 none,则不启用顾问。 默认值为 none。

扫描时间顾问会根据观察到的扫描时间更改 pages_to_scan 参数。 pages_to_scan 参数的可能值受 advisor_max_cpu 参数的限制。此外,还有一个 advisor_target_scan_time 参数。此参数设置扫描所有 KSM 候选页的目标时间。 advisor_target_scan_time 参数决定扫描时间顾问扫描候选页面的积极程度。较低的值会使扫描时间顾问扫描更积极。这是扫描时间顾问配置中最重要的参数。

初始值和最大值可以使用 advisor_min_pages_to_scanadvisor_max_pages_to_scan 进行更改。默认值对于大多数工作负载和用例来说是足够的。

在扫描完成后,将重新计算 pages_to_scan 参数。

-- Izik Eidus, Hugh Dickins, 2009 年 11 月 17 日