各位同仁,下午好。今天我们聚焦于一个在现代虚拟化环境中至关重要的内存优化技术:Kernel Samepage Merging (KSM)。随着云计算和虚拟桌面基础设施(VDI)的普及,单一物理主机上运行成百上千个虚拟机已是常态。这些虚拟机,尤其是那些基于相同操作系统镜像或运行相似应用栈的实例,不可避免地会拥有大量内容完全相同的内存页。这些重复的内存页不仅浪费了宝贵的物理RAM,还限制了单个主机所能承载的虚拟机密度。KSM正是为了解决这一挑战而生,它在内核层面默默工作,智能地扫描、识别并合并这些重复的内存页,从而显著提升内存利用率。
我们将深入探讨KSM的内部工作机制,从其扫描策略、哈希算法、到合并与解合并的整个生命周期,并结合实际的内核数据结构和伪代码来解析其精妙之处。
一、 内存虚拟化的挑战与KSM的应运而生
在虚拟化环境中,每个虚拟机都拥有自己独立的虚拟地址空间和内存映射。尽管这些虚拟机运行在同一个物理主机上,但它们的操作系统和应用程序通常会加载许多相同的文件、库和数据结构到内存中。例如:
- 克隆虚拟机(Cloned VMs):从同一模板启动的数百个VDI桌面,它们的操作系统代码段、共享库、甚至大部分用户空间数据在启动时几乎完全相同。
- 同构应用服务器:运行相同Web服务器、数据库或应用栈的虚拟机群。
- 闲置或低负载虚拟机:即使是不同的虚拟机,在闲置状态下也可能共享大量空闲页或系统缓存页。
如果没有KSM这类机制,这些重复的内存页将各自占用一份物理RAM,导致巨大的内存浪费。例如,如果一个操作系统镜像需要2GB内存,而有100个这样的虚拟机在运行,理论上就需要200GB物理内存。但实际上,其中可能只有一小部分是虚拟机特有的数据,大部分都是重复的。
KSM的核心理念是内存去重:它识别出物理内容相同的内存页,并将它们替换为单个共享的物理页。当任何一个共享的虚拟机试图修改这个共享页时,KSM会利用写时复制(Copy-on-Write, CoW)机制为其创建一个私有副本,从而确保了内存隔离性和数据完整性。通过这种方式,KSM能够显著提高物理主机的内存利用率,允许在不增加物理RAM的情况下运行更多虚拟机,或为现有虚拟机提供更多可用内存。
二、 KSM核心机制:扫描、哈希与合并
KSM的工作流程可以概括为三个主要阶段:扫描、哈希和合并。它由一个专用的内核线程 ksm_thread 在后台持续执行。
2.1 扫描阶段:寻找合并候选页
KSM并非扫描所有的内存页。它只关注那些被特定标记为“可合并”的内存区域。在Linux内核中,这通常通过虚拟机监视器(VMM)或用户空间应用程序通过 madvise() 系统调用(使用 MADV_MERGEABLE 标志)来指定。只有属于这些区域的页才会被KSM考虑。
ksm_thread 会遍历系统中的每一个 mm_struct(代表一个进程的内存描述符)。对于每个 mm_struct,它会进一步遍历其虚拟内存区域(VMAs),查找带有 VM_MERGEABLE 标志的VMA。一旦找到这样的VMA,KSM就会从中逐页扫描。
内核伪代码示例:扫描循环
// ksm_thread 的主循环
void ksm_do_scan(void) {
struct ksm_scan *ksm_scan = &ksm_scan; // KSM全局扫描状态
// 每次扫描一小批页,然后休眠,避免过度消耗CPU
for (int i = 0; i < ksm_scan->pages_to_scan; i++) {
// 1. 获取下一个要扫描的 mm_struct
struct mm_struct *mm = ksm_get_next_mm(ksm_scan);
if (!mm) { // 如果所有 mm_struct 都已扫描一遍,则重置并开始新一轮
ksm_reset_scan(ksm_scan);
mm = ksm_get_next_mm(ksm_scan);
if (!mm) break; // 仍然没有 mm_struct,退出
}
// 2. 在当前 mm_struct 中找到下一个可合并的 VMA 和页
struct vm_area_struct *vma = ksm_find_next_vma(mm, &ksm_scan->addr);
if (!vma) { // 当前 mm_struct 扫描完毕
ksm_finish_mm_scan(mm);
continue;
}
// 3. 获取物理页
pte_t *pte;
spinlock_t *ptl;
struct page *page = ksm_get_page_from_vma(vma, ksm_scan->addr, &pte, &ptl);
if (page && ksm_is_page_mergeable(page)) {
// 4. 将页内容哈希
unsigned long hash = ksm_calculate_page_hash(page);
// 5. 尝试合并页面
ksm_try_to_merge_page(mm, vma, ksm_scan->addr, page, pte, ptl, hash);
} else if (page) {
// 页不可合并,释放pte锁
pte_unmap_unlock(pte, ptl);
}
// 6. 更新扫描地址,准备扫描下一个页
ksm_scan->addr += PAGE_SIZE;
}
}
这里 ksm_is_page_mergeable 会检查页是否是常规页、不是匿名页交换页、不是脏页等,以确保其适合合并。
2.2 哈希阶段:生成页内容的指纹
一旦KSM找到一个候选页,它就需要计算该页内容的哈希值。这个哈希值是页内容的“指纹”,用于快速比较不同页是否具有相同的内容。Linux内核通常使用CRC32或其他快速哈希算法来计算页的哈希值,因为它需要非常高效地处理大量页。由于页内容是4KB,计算哈希必须足够快。
哈希函数考虑:
- 速度:计算哈希是KSM最频繁的操作之一。
- 碰撞抵抗性:虽然完全避免碰撞是不可能的,但我们需要一个能有效减少碰撞的哈希函数,以避免将不同内容的页误判为相同。
- 分布性:哈希值应均匀分布,以优化哈希树的性能。
KSM使用两个红黑树(R-B tree)来管理哈希值:
stable_tree(稳定树):存储已经成功合并的物理页的哈希值。每个节点(stable_node)代表一个已合并的物理页,并包含该页的哈希值以及指向该物理页的指针。unstable_tree(不稳定树):存储那些内容相同但尚未被合并,或者刚刚被扫描到的页的哈希值。unstable_node结构与stable_node类似,但它不直接指向物理页,而是包含指向原始页的指针。
为什么需要两棵树?
unstable_tree 主要用于发现新的重复页。当KSM扫描到一个新页时,它首先计算哈希并尝试在 stable_tree 中查找。如果找不到,它会尝试在 unstable_tree 中查找。如果在 unstable_tree 中找到了匹配项,说明之前已经扫描过一个内容相同的页,这时就可以将这两个页合并,并将它们移动到 stable_tree 中。如果在 unstable_tree 中也找不到,那么这个新页会被添加到 unstable_tree 中,等待后续扫描发现其重复项。这种机制允许KSM在两个扫描周期内发现并合并重复页。
内核伪代码示例:哈希与树查找
// 简化后的 ksm_try_to_merge_page 逻辑
int ksm_try_to_merge_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long addr, struct page *page,
pte_t *pte, spinlock_t *ptl, unsigned long hash) {
struct ksm_mm_slot *mm_slot = ksm_get_mm_slot(mm); // 获取当前mm_struct的KSM槽位
// 1. 尝试在 stable_tree 中查找哈希匹配项
struct stable_node *stable_node = ksm_stable_tree_lookup(hash);
if (stable_node) {
// 如果找到,并且页内容确实相同(二次确认,防止哈希碰撞)
if (ksm_pages_are_identical(page, stable_node->ksm_page)) {
// 合并操作:将当前页的pte指向 stable_node->ksm_page
// ... (更新pte, 增加stable_node->ksm_page的引用计数, 减少当前页引用计数)
// ... (添加 rmap_item 到 stable_node->rmap_list)
ksm_merge_page(mm, vma, addr, page, pte, ptl, stable_node);
return 0; // 合并成功
}
// 哈希碰撞,内容不相同,继续
}
// 2. 尝试在 unstable_tree 中查找哈希匹配项
struct unstable_node *unstable_node = ksm_unstable_tree_lookup(hash);
if (unstable_node) {
// 如果找到,并且页内容确实相同
if (ksm_pages_are_identical(page, unstable_node->page)) {
// 发现两个相同页,现在可以合并它们,并创建新的 stable_node
stable_node = ksm_create_stable_node(hash, unstable_node->page);
// 合并第一个页 (unstable_node->page) 到 stable_node
// ... (更新 unstable_node->page 的pte,将其添加到 stable_node->rmap_list)
ksm_merge_page(unstable_node->mm, unstable_node->vma,
unstable_node->addr, unstable_node->page,
unstable_node->pte, unstable_node->ptl, stable_node);
// 合并第二个页 (当前页) 到 stable_node
ksm_merge_page(mm, vma, addr, page, pte, ptl, stable_node);
// 将 stable_node 插入 stable_tree
ksm_stable_tree_insert(stable_node);
// 从 unstable_tree 中移除 unstable_node
ksm_unstable_tree_remove(unstable_node);
return 0; // 合并成功
}
// 哈希碰撞,内容不相同,继续
}
// 3. 既不在 stable_tree 也不在 unstable_tree 中找到匹配项,将当前页添加到 unstable_tree
unstable_node = ksm_create_unstable_node(hash, mm, vma, addr, page, pte, ptl);
ksm_unstable_tree_insert(unstable_node);
return -EAGAIN; // 稍后再试
}
2.3 合并阶段:实现CoW共享
当KSM发现两个或更多页的内容相同时,它会选择其中一个物理页作为“共享页”,并将其注册到 stable_tree 中。然后,所有这些内容相同的虚拟页的页表项(PTEs)都会被修改,使其指向这个共享的物理页。
合并步骤:
- 选择共享页:通常是第一个被发现的、内容相同的物理页。
- 更新页表项(PTE):对于所有重复的虚拟页,其对应的PTE会被修改,使其指向共享页。
- 旧的物理页(如果不是共享页本身)的引用计数会减少,如果降为0,则会被释放。
- 共享页的引用计数会增加。
- PTE会被标记为只读。这是关键一步,为CoW机制做准备。
- 跟踪共享页:KSM需要知道哪些
mm_struct的哪些虚拟地址正在共享哪个stable_node。为此,它使用rmap_item结构。每个rmap_item关联一个mm_struct和一个虚拟地址,并被链接到其对应的stable_node的一个链表上。
内核伪代码示例:实际合并操作 ksm_merge_page (简化)
void ksm_merge_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long addr, struct page *old_page,
pte_t *pte, spinlock_t *ptl,
struct stable_node *stable_node) {
struct page *shared_page = stable_node->ksm_page;
// 1. 获取并锁定页表
// pte 和 ptl 已经在调用方获取
// 2. 创建或更新 rmap_item 以跟踪共享关系
struct rmap_item *rmap_item = ksm_get_rmap_item(mm, addr);
if (!rmap_item) {
rmap_item = ksm_alloc_rmap_item(mm, addr);
// ... 添加 rmap_item 到 mm_slot 的 rmap_list
}
rmap_item->stable_node = stable_node;
list_add(&rmap_item->list, &stable_node->rmap_list); // 链接到 stable_node
// 3. 更新 PTE 指向共享页,并设置为只读
pte_t entry = pte_mkpte(shared_page, vma->vm_page_prot);
entry = pte_wrprotect(entry); // 设置为只读
set_pte(pte, entry);
// 4. 刷新TLB,使新的PTE生效
flush_tlb_page(vma, addr);
// 5. 更新页的引用计数
get_page(shared_page); // 共享页引用计数增加
page_remove_anon_rmap(old_page, vma->vm_start); // 从旧页的rmap中移除
put_page(old_page); // 旧页引用计数减少,可能释放
// 6. 更新 KSM 统计信息
atomic_long_inc(&ksm_pages_shared); // 增加共享页计数
atomic_long_inc(&ksm_pages_sharing); // 增加正在共享的页实例计数
// 7. 解锁pte
pte_unmap_unlock(pte, ptl);
}
KSM相关数据结构表
| 结构体名称 | 描述 | 关键字段 | 描述 | 字段 | 类型 | 描述 |
| :—————— | :——————————————————————————————————————-| :—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-| ksm_page | struct page * | 指向 KSM 共享的物理页。 |
| rb_node | rb_node | 用于在 KSM 的红黑树中存储 stable_node 或 unstable_node。 |
| hash | unsigned long | 共享页内容的哈希值。 |
| rmap_list | struct list_head | 链接所有指向此 stable_node 的 rmap_item 列表。 |
| list | list_head | 用于链接 rmap_item 到 mm_slot->rmap_list (所有可合并的VMA) 和 stable_node->rmap_list (所有共享该稳定页的项)。 | mm_slot | struct ksm_mm_slot * | 指向拥有此页的 mm_struct 对应的 KSM 槽位。