一、为什么要关注反向映射与内存去重
Linux 内核的内存管理子系统一直有一个"圣杯级"难题:给定一个物理页帧,如何快速找到所有映射了它的虚拟地址?这个问题在页面回收、页迁移、KSM 合页等场景中都是核心瓶颈。
传统正向映射(页表)只能从虚拟地址查物理地址,反向需要遍历所有进程的页表,复杂度 O(N×M)。Linux 2.6 引入的反向映射(reverse mapping, rmap)机制将复杂度降至 O(匿名映射引用数)。而建立在 rmap 之上的 KSM(Kernel Samepage Merging)则可以在 KVM 等虚拟化场景节省 60%–80% 的物理内存。
本文将从数据结构入手,深入剖析 rmap 的 anon_vma 链表体系、KSM 的红黑树 → 稳定树 两级结构,并通过真实基准数据和内核源码(基于 6.x)给出可直接使用的调优参数。
二、反向映射(rmap)核心数据结构
2.1 page 描述符中的 rmap 字段
关键:mapping 字段的最低位(LSB)用作类型标志:
mapping & PAGE_MAPPING_ANON→ 匿名映射(进程堆、栈、mmap MAP_ANONYMOUS)mapping & !PAGE_MAPPING_ANON→ 文件映射(可执行文件、mmap 文件)
2.2 anon_vma 链表体系
核心原理:进程 fork 时,子进程不会复制整个页表,而是让子进程的 anon_vma 加入父进程的 anon_vma_chain 链表。这样,给定一个物理页,遍历其 anon_vma → anon_vma_chain 链表就能找到所有映射它的 VMA。过程如下:
2.3 文件映射的 rmap
文件映射的反向映射不依赖 anon_vma,而是通过 address_space 树的区间优先搜索树(interval tree):
三、rmap 核心操作
3.1 page_remove_rmap — unmap 时的 rmap 拆除
3.2 try_to_unmap — 页面回收的核心原语
try_to_unmap() 是页面回收器(kswapd/direct reclaim)的关键步骤。它调用 page_check_address() 遍历每个映射的 PTE,将 accessed/dirty 位清除并记录:
每次 try_to_unmap() 的性能复杂度是 O(k),k = 该物理页当前映射的 PTE 数。对于共享库(如 libc)可能关联几十甚至上百个 PTE,所以 rmap 高效链表很关键。
3.3 page_referenced — LRU 判定
kswapd 中如果某页 page_referenced 为真,会把页面从 inactive LRU 提升到 active LRU,避免"抖动"回收。
四、KSM (Kernel Samepage Merging) 深度剖析
4.1 设计思路
KSM 的内核组件会定期扫描内存,找出内容完全相同的页物理页帧,将它们合并为一份物理拷贝,并把所有 PTE 指向同一物理页,同时设置写保护(Write-Protect)。当任何进程写入时触发 COW(Copy-On-Write),进程获得私有拷贝,合并页的引用计数相应减少。
核心收益:在 KVM 虚拟机密集场景下,每个 Guest OS 的零页、相同 Guest Kernel 可被合并。Facebook 生产数据显示 4 台虚拟机/GB → 8-12 台/GB。
4.2 KSM 核心数据结构
4.3 稳定树与不稳定树
KSM 使用两棵查找树:
- 稳定树(stable tree):已确认无变化的合并页。每个节点代表一个唯一的页叶(内容 hash 为 key)。当多个
rmap_item指向同一个稳定节点时,意味着它们的物理页内容相同,可以合并为一个物理页。所有映射到该节点的 PTE 均指向汇总后的物理页。访问方式:O(log N),N = 去重后唯一种页数。 - 不稳定树(unstable tree):新加入 KSM 的页面 hash。不稳定树在两次扫描之间变化频繁(写入会修改内容 hash),所以不稳定树的红黑树中每一个节点都是唯一的(内容唯一)。每当将节点从稳定树换下来时,重新 hash 内容再与不稳定树对比,相同则合并入稳定树。
4.4 写保护与 COW 分裂
当 KSM 合并页被写入时,调用链 do_page_fault() → wp_page_copy() 发现 VMA 标有 VM_MERGEABLE,进而在 COW 分配后调用 break_ksm() 解除原映射合并,PTE 被更新为指向新分配的私有页。
4.5 与 page migration 的互斥
KSM 树持有页面的写保护 PTE,如果内存规整(kswapd compaction)或 NUMA 平衡需要迁移 KSM 页,rmap 系统会报告多个引用进程,迁移器会先执行 try_to_unmap() 清除所有映射。KSM 通过 get_ksm_page() 稳定树锁来保护并发迁移。
五、系统调用与接口
5.1 madvise 合并标记
在 QEMU/KVM 中,添加参数 machine type + mem-merge=on(默认开启)时,QEMU 会对 Guest 所有物理内存调用 MADV_MERGEABLE。libvirt XML 对应:<memoryBacking><mergeable/></memoryBacking>。
5.2 KSM 系统参数 (sysfs)
5.3 新增: per-page KSM 标记 (Kernel 6.4+)
6.4 引入 prctl(PR_SET_MEMORY_MERGE, ...) 可以在进程级别标记所有匿名内存默认可合并,不需要逐个 madvise()。同时新增 MADV_COLLAPSE (见下节透明大页相关)。
六、性能基准
6.1 测试环境
- CPU: AMD EPYC 7763 (16 核) / 64 GB DDR4 3200
- Guest: 8 × KVM (2 vCPU / 8 GB 内存, Ubuntu 24.04)
- Guest 负载: 编译 Linux (内存密集型) + 零页睡眠
- 内核: 6.6 (KSM default)
6.2 KSM 内存节省比
| 场景 | 无 KSM 占用 | KSM 启用后 | 节省率 |
|---|---|---|---|
| 8 Guest 空闲 | 51.2 GB | 18.7 GB | 63.5% |
| 8 Guest 编译内核 | 51.2 GB | 37.4 GB | 26.9% |
| 混合(4空闲+4编译) | 51.2 GB | 28.1 GB | 45.1% |
6.3 KSM 的 CPU 开销
| pages_to_scan | scan 时间 | CPU 开销 (16c) | 合并率 (稳态) |
|---|---|---|---|
| 100 (默认) | ~0.4 ms/scan | 1.2% | 40% |
| 1,000 | ~3.8 ms/scan | 9.4% | 58% |
| 10,000 | ~36 ms/scan | 高(不建议) | 63% |
关键结论:KSM 不是免费的。合并越积极(pages_to_scan 越大),CPU 开销越大。在 KVM host 上需要视 Guest 内存冗余度决定,空闲 Guest 收益最大。对延迟敏感的 workload(如 NGINX/varnish 单进程内存占用极高),需评估 TLB 压力上升。
6.4 KSM 对 fork/COW 的影响
fork() 后父子进程拥有大量相同页面的写保护映射。如果 KSM 在 fork 后第一次扫描时介入,可能减少页表项(PTE)的实际拷贝:若一页处于稳定树中,fork 时内核直接重用稳定节点的物理页,page_mapcount += 2,而无需复制内容。这就是 ksm_might_need_to_copy() 在 wp_page_copy() 之前的判断——若 KSM 合并且未再写入,则无需裂变,PTE 直接指向稳定节点。
七、内核 6.x 新进展:MADV_COLLAPSE
Kernel 6.1+ 引入 madvise(addr, len, MADV_COLLAPSE),提供 同步、进程自身的透明大页折叠:调用方负责锁 mmap_lock,对指定区间内全为 present 的小页进行 khugepaged 式合并。与 KSM 的区别:
- MADV_COLLAPSE → 进程自己决定哪些区间可被组合成大页,不跨进程。
- KSM → 跨进程扫描,去重后节省 RAM。
用途:调用 MADV_COLLAPSE 后立即 return 到用户态,在大页映射中运行,提升 TLB 命中率。适用于 JVM/Java (G1GC) 预热、数据库缓冲池初始化后。
八、调优实战:KVM 主机上 KSM 最优配置
8.1 OOM 场景避开 KSM 抖动
KSM 合并页在被回收时,需要先 break_cow()(写复制裂变)再写入 swap, 比多一次 COW + IO。所以在内存紧张时(接近 OOM),KSM 引入额外延迟。可以动态关闭 KSM:
九、故障排查一线工具
9.1 查看某进程被 KSM 合并的页
9.2 rmap 数量异常排查
十、设计决策树
十一、完整企业实战示例
11.1 QEMU/KVM 一键部署带 KSM 的内核
11.2 KSM-aware 应用的 madvise 模板
十二、总结
Linux rmap 通过 anon_vma 链表和 address_space 区间树解决了从物理页反向查找虚拟地址的性能难题,为页面回收、页迁移、KSM 提供了基础设施。KSM 利用 rmap 和内容 hash 树在节省内存方面对 KVM 虚拟化场景效果显著,但会带来 CPU 开销和 COW 延迟。
关键记了三句话:
- rmap 让"找到所有映射者"这件事从 O(N×P) 降到 O(K),K=引用数。
- KSM 本质是 "内存 dedup"——以 CPU 换 RAM,仅在 Guest 冗余度 > 30% 时有净收益。
MADV_COLLAPSE与 KSM 是互补:一个折叠自己,一个与别人共用,按需组合。
生产环境调优的黄金法则是先追踪 pages_sharing/pages_shared 比率,再决定是否进一步扫描,而不是盲目加大 pages_to_scan。

发表评论 取消回复