内核同页合并¶
概述¶
KSM 是一种节省内存的去重功能,通过 CONFIG_KSM=y 启用,在 2.6.32 版本中添加到 Linux 内核。有关其实现,请参见 mm/ksm.c
,以及 http://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 而失败,但更可能引发 Out-Of-Memory killer。
如果 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(默认值)带来的更大的共享。在决定使用哪个设置之前,您可能希望比较系统在每个设置下的性能。
merge_across_nodes
设置只能在系统中没有 ksm 共享页面时更改:首先设置 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 检查达到去重限制的页面的元数据中是否存在陈旧信息的频率。较小的 milllisecs 值将以较低的延迟释放 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_sharing
与 pages_shared
的高比率表示良好的共享,但 pages_unshared
与 pages_sharing
的高比率表示浪费精力。pages_volatile
包含几种不同的活动,但那里的高比例也表明 madvise MADV_MERGEABLE 的使用不当。
pages_sharing/pages_shared
比率的最大可能值受 max_page_sharing
可调参数的限制。要增加该比率,必须相应地增加 max_page_sharing
。
监控 KSM 利润¶
KSM 可以通过合并相同的页面来节省内存,但也会消耗额外的内存,因为它需要生成许多 rmap_items 来保存每个扫描页面的简短 rmap 信息。其中一些页面可能会被合并,但有些页面在多次检查后可能无法合并,这些都是无利可图的内存消耗。
如何在系统范围内确定 KSM 是节省内存还是消耗内存?以下是一个简单的近似计算,供参考
general_profit =~ ksm_saved_pages * sizeof(page) - (all_rmap_items) * sizeof(rmap_item);
其中 ksm_saved_pages 等于系统的
pages_sharing
+ksm_zero_pages
的总和,并且可以通过将pages_sharing
、pages_shared
、pages_unshared
和pages_volatile
相加来轻松获得 all_rmap_items。可以通过以下近似计算类似地获得单个进程中的 KSM 利润
process_profit =~ ksm_saved_pages * sizeof(page) - ksm_rmap_items * sizeof(rmap_item).
其中 ksm_saved_pages 等于
ksm_merging_pages
和ksm_zero_pages
的总和,它们都显示在目录/proc/<pid>/ksm_stat
下,并且 ksm_rmap_items 也显示在/proc/<pid>/ksm_stat
中。进程利润也显示在/proc/<pid>/ksm_stat
中,作为 ksm_process_profit。
从应用程序的角度来看,ksm_rmap_items
与 ksm_merging_pages
的高比率意味着糟糕的 madvise 应用策略,因此开发人员或管理员必须重新考虑如何更改 madvise 策略。给出一个参考示例,页面的大小通常为 4K,rmap_item 的大小在 32 位 CPU 架构上单独为 32B,在 64 位 CPU 架构上为 64B。因此,如果 ksm_rmap_items/ksm_merging_pages
比率在 64 位 CPU 上超过 64,或在 32 位 CPU 上超过 128,则应删除该应用程序的 madvise 策略,因为 ksm 利润大约为零或为负。
监控 KSM 事件¶
/proc/vmstat 中有一些计数器可用于监控 KSM 事件。KSM 可能有助于节省内存,这是一种权衡,可能会导致 KSM COW 或交换复制时的延迟。这些事件可以帮助用户评估是否或如何使用 KSM。例如,如果 cow_ksm 增加得太快,用户可能会减少 madvise(, , MADV_MERGEABLE) 的范围。
- cow_ksm
每次 KSM 页面触发写时复制 (COW) 时都会递增,当用户尝试写入 KSM 页面时,我们必须创建一个副本。
- 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_scan
和 advisor_max_pages_to_scan
更改初始值和最大值。默认值足以满足大多数工作负载和用例。
扫描完成后,会重新计算 pages_to_scan
参数。
-- Izik Eidus, Hugh Dickins, 2009 年 11 月 17 日