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

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

相关推荐
Suger9999 小时前
CentOS中设置yum源
linux·运维·centos
qziovv9 小时前
vim操作
linux·编辑器·vim
我好饿110 小时前
elk日志系统
linux·log
Nimsolax10 小时前
Linux线程互斥与同步
linux
dessler10 小时前
Hadoop HDFS-回收站(Trash)
linux·运维·hdfs
张童瑶10 小时前
Linux Cent OS7离线安装Go环境(最新版本)
linux·运维·golang
椎名澄嵐10 小时前
★ Linux ★ 信号
linux·运维·服务器·开发语言·c++
牛奶咖啡1311 小时前
Linux中实现可执行文件或脚本在全局可用
linux·设置可执行程序全局可用·设置脚本全局可用·linux默认执行目录·linux环境变量
情深不寿31711 小时前
传输层————TCP
linux·网络·c++·tcp/ip
christine-rr12 小时前
【25软考网工】第五章(10) Internet应用
linux·网络·经验分享·笔记·软考