阅读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

  1. 核心数据结构详解

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; // 引用计数

};

  1. 父子进程创建时的 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(&current->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;

}

  1. 代码案例:父子进程共享页面

#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

  1. 内核 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);

}

}

}

  1. 性能优化关键点

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;

}

  1. 实际生产环境配置建议

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 读写锁保护并发访问

这种设计在高内存压力场景下(如虚拟机、容器环境)尤其重要,能显著减少内存冗余,提高系统整体性能。

相关推荐
翻斗花园牛姥姥16 小时前
嵌入式系统与51单片机全解析
linux
金色天际线-16 小时前
Linux 文本处理四剑客:cut, sort, uniq, tr
linux·运维·服务器
kebeiovo17 小时前
Linux 环境配置 Boost 库详细步骤
linux·运维·服务器
mljy.18 小时前
Linux《进程信号(上)》
linux
泽虞18 小时前
《LINUX系统编程》笔记p8
linux·运维·服务器·c语言·笔记·面试
正在努力的小河21 小时前
Linux并发与竞争
linux·运维·服务器
三坛海会大神55521 小时前
正则表达式与grep文本过滤详解
linux·运维·正则表达式
码农hbk1 天前
Linux signal 图文详解(二)信号发送
linux·服务器
東雪蓮☆1 天前
Shell 编程 —— 正则表达式与文本处理实战
linux·运维·服务器