阅读Linux 4.0内核RMAP机制的代码,画出父子进程之间VMA、AVC、anon_vma和page等数据结构之间的关系图。
Linux 4.0 内核 RMAP 机制深度解析
1. RMAP 核心数据结构关系图
关键关系说明:
1.每个进程的 mm_struct 管理多个 VMA 区域
2.每个 VMA 通过 anon_vma_chain (AVC) 链接到 anon_vma
3.父进程和子进程共享同一个 anon_vma 结构
4.匿名页面通过 page→mapping 指向所属的 anon_vma
5.anon_vma 通过红黑树管理所有关联的 anon_vma_chain
2. 核心数据结构详解
2.1 struct vm_area_struct (VMA)
struct vm_area_struct {
struct mm_struct *vm_mm; // 所属内存管理器
unsigned long vm_start; // 起始虚拟地址
unsigned long vm_end; // 结束虚拟地址
struct list_head anon_vma_chain; // AVC 链表头
struct anon_vma *anon_vma; // 指向anon_vma
};
2.2 struct anon_vma_chain (AVC)
struct anon_vma_chain {
struct vm_area_struct *vma; // 关联的VMA
struct anon_vma *anon_vma; // 关联的anon_vma
struct list_head same_vma; // 同VMA的AVC链表
struct rb_node rb; // 红黑树节点
};
2.3 struct anon_vma
struct anon_vma {
struct rw_semaphore rwsem; // 读写锁
atomic_t refcount; // 引用计数
struct anon_vma *root; // 根anon_vma
struct rb_root rb_root; // AVC红黑树根
};
2.4 struct page
struct page {
unsigned long flags; // 标志位
struct address_space *mapping; // 指向anon_vma或address_space
atomic_t _mapcount; // 映射计数
atomic_t _count; // 引用计数
};
3. 父子进程创建时的 RMAP 建立流程
3.1 进程 fork 的关键代码 (kernel/fork.c)
static __latent_entropy struct task_struct *copy_process(...)
{
// 创建新进程的mm_struct
retval = -EAGAIN;
p = dup_task_struct(current);
// 复制内存映射
retval = copy_mm(clone_flags, p);
}
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
// 共享或复制内存映射
if (clone_flags & CLONE_VM) {
atomic_inc(¤t->mm->mm_users);
tsk->mm = current->mm;
} else {
tsk->mm = dup_mm(tsk);
}
}
static struct mm_struct *dup_mm(struct task_struct *tsk)
{
// 复制整个mm_struct
mm = allocate_mm();
memcpy(mm, oldmm, sizeof(*mm));
// 复制所有VMA
dup_mmap(mm, oldmm);
}
3.2 VMA 复制过程 (kernel/fork.c)
static __latent_entropy int dup_mmap(struct mm_struct *mm,
struct mm_struct *oldmm)
{
// 遍历所有VMA进行复制
for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {
// 复制VMA结构
tmp = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
*tmp = *mpnt;
// 关键:复制anon_vma链接
anon_vma_fork(tmp, mpnt);
}
}
3.3 anon_vma 复制核心 (mm/rmap.c)
int anon_vma_fork(struct vm_area_struct *vma, struct vm_area_struct *pvma)
{
// 获取父进程的anon_vma
struct anon_vma *anon_vma = pvma->anon_vma;
// 如果父进程有anon_vma链接
if (anon_vma) {
// 创建新的anon_vma_chain
struct anon_vma_chain *avc = anon_vma_chain_alloc();
// 将新VMA连接到父进程的anon_vma
anon_vma_chain_link(vma, avc, anon_vma);
// 增加父进程anon_vma的引用计数
anon_vma->refcount++;
}
// 将父进程的anon_vma复制给子进程
vma->anon_vma = anon_vma;
}
4. 代码案例:父子进程共享页面
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
int *shared_var = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
*shared_var = 42;
printf("Parent[%d] initial value: %d\n", getpid(), *shared_var);
pid_t pid = fork();
if (pid == 0) { // Child process
printf("Child[%d] start value: %d\n", getpid(), *shared_var);
*shared_var = 100; // COW will happen here
printf("Child[%d] modified value: %d\n", getpid(), *shared_var);
} else { // Parent process
sleep(1); // Wait child modify
printf("Parent[%d] final value: %d\n", getpid(), *shared_var);
}
return 0;
}
运行结果:
Parent[1234] initial value: 42
Child[1235] start value: 42
Child[1235] modified value: 100
Parent[1234] final value: 42
5. 内核 RMAP 工作流程分析
5.1 COW 触发过程
子进程写共享页面触发缺页异常
// arch/x86/mm/fault.c
dotraplinkage void __kprobes do_page_fault(...)
{
__do_page_fault(regs, error_code, address);
}
static void __kprobes __do_page_fault(...)
{
// 处理写保护错误
if (error_code & PF_WRITE)
flags |= FAULT_FLAG_WRITE;
fault = handle_mm_fault(mm, vma, address, flags);
}
5.2 COW 处理 (mm/memory.c)
static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pte_t *page_table, pmd_t *pmd,
spinlock_t *ptl, pte_t orig_pte)
{
// 获取原始页面
old_page = vm_normal_page(vma, address, orig_pte);
// 检查是否可以重用(只有1个引用)
if (PageAnon(old_page) && !PageKsm(old_page) &&
page_count(old_page) == 1) {
reuse = true;
}
// 需要创建新页面的情况
if (unlikely(!reuse)) {
// 分配新页面
new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, address);
// 复制父页内容
copy_user_highpage(new_page, old_page, address, vma);
// 设置新页的反向映射
page_add_new_anon_rmap(new_page, vma, address);
}
}
5.3 RMAP 反向查找 (mm/rmap.c)
void rmap_walk(struct page *page, struct rmap_walk_control *rwc)
{
// 匿名页处理
if (PageAnon(page)) {
anon_vma = page_anon_vma(page);
if (!anon_vma)
return;
// 遍历所有链接的VMA
anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root,
pgoff, pgoff) {
struct vm_area_struct *vma = avc->vma;
// 执行指定操作
rwc->rmap_one(page, vma, address, rwc->arg);
}
}
}
6. 性能优化关键点
6.1 KSM 合并相同页面
// mm/ksm.c
static int unmerge_and_remove_all_rmap_items(void)
{
// 合并相同内容页面
err = try_to_merge_with_ksm_page(rmap_item, page, NULL);
}
6.2 反向映射高效查找
// mm/interval_tree.c
void anon_vma_interval_tree_insert(
struct anon_vma_chain *node, struct rb_root *root)
{
// 红黑树插入操作
rb_insert_augmented(&node->rb, root, &anon_vma_interval_tree_augment);
}
6.3 RCU 保护锁机制
// mm/rmap.c
struct anon_vma *page_lock_anon_vma_read(struct page *page)
{
// 使用RCU保护并发访问
rcu_read_lock();
anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
if (atomic_inc_return(&anon_vma->refcount) > 1)
gotref = true;
}
7. 实际生产环境配置建议
7.1 /proc/sys/vm 优化参数
# 增大相同页面扫描范围
echo 1000 > /proc/sys/vm/ksm_pages_to_scan
# 减少扫描间隔
echo 100 > /proc/sys/vm/ksm_sleep_millisecs
# 开启透明大页合并
echo always > /sys/kernel/mm/transparent_hugepage/khugepaged/defrag
7.2 cgroup 内存控制
# 限制进程组写时复制内存
echo 2G > /sys/fs/cgroup/memory/user.slice/memory.max
# 设置KSM共享策略
echo 1 > /sys/fs/cgroup/memory/user.slice/memory.ksm_share
总结
Linux 内核的 RMAP 机制通过精巧的数据结构设计实现高效的反向映射:
父子进程共享:通过 anon_vma 连接父进程和子进程的 VMA
写时复制:COW 机制延迟物理内存分配直到真正需要时
高效查找:红黑树结构实现 O(log n) 复杂度的反向映射
内存优化:KSM 机制合并相同页面提高内存利用率
锁机制优化:RCU 读写锁保护并发访问
这种设计在高内存压力场景下(如虚拟机、容器环境)尤其重要,能显著减少内存冗余,提高系统整体性能。