土法炼钢兴趣小组的算法知识备份

【操作系统百科】mm_struct 与 VMA

文章导航

分类入口
os
标签入口
#mm-struct#vma#maple-tree#anon-vma#mmap-lock

目录

每个进程对内核说”我有哪些地址段”,靠 mm_struct 这个核心数据结构。里面挂着所有 VMA(Virtual Memory Area),每个 VMA 描述一段连续的 VA → 同一保护属性 + 同一 backing。

一、先看图

flowchart TD
    TASK[task_struct] --> MM[mm_struct]
    MM --> MT[maple tree<br/>VMA 索引]
    MT --> V1[VMA: text<br/>0x400000-0x600000<br/>rx, file]
    MT --> V2[VMA: heap<br/>0x600000-0x900000<br/>rw, anon]
    MT --> V3[VMA: stack<br/>0x7ffc...-0x7fff...<br/>rw, anon, growsdown]
    MT --> V4[VMA: mmap<br/>0x7f0000...<br/>rx, file .so]
    MM --> PGD[pgd → 多级页表]
    classDef meta fill:#388bfd22,stroke:#388bfd,color:#adbac7;
    classDef vma fill:#3fb95022,stroke:#3fb950,color:#adbac7;
    class TASK,MM,PGD meta
    class V1,V2,V3,V4 vma

二、mm_struct 核心字段

struct mm_struct {
    struct maple_tree mm_mt;       // VMA 索引(6.1+ 替代老 rbtree)
    pgd_t *pgd;                    // 顶级页表
    atomic_t mm_users;             // 用户态引用计数(>0 才有活进程)
    atomic_t mm_count;             // 内核引用(lazy tlb 也算一份)
    unsigned long task_size;       // 用户空间上界
    unsigned long start_code, end_code;
    unsigned long start_data, end_data;
    unsigned long start_brk, brk;
    unsigned long start_stack;
    unsigned long arg_start, arg_end;
    unsigned long env_start, env_end;
    struct rw_semaphore mmap_lock;
    ...
};

关键:

三、VMA(vm_area_struct)

struct vm_area_struct {
    unsigned long vm_start, vm_end;
    pgoff_t vm_pgoff;               // 文件偏移
    struct file *vm_file;           // NULL=匿名
    vm_flags_t vm_flags;            // VM_READ|VM_WRITE|VM_EXEC|VM_SHARED...
    const struct vm_operations_struct *vm_ops;
    struct anon_vma *anon_vma;
    ...
};

VMA 是”按段管理”思路——连续地址 + 相同属性 = 一个 VMA。mprotect 只改 vm_flags(可能拆 VMA)。

观察:

cat /proc/$$/maps
# 地址范围  权限  offset  dev  inode  pathname

四、从红黑树到 maple tree

4.1 老方案:红黑树 + 链表

5.x 及以前:VMA 同时挂红黑树(按地址索引)和链表(遍历)。

问题:

4.2 maple tree(6.1 合入)

Liam Howlett 的改造:用 B-tree 变种(maple tree)替代红黑树 + 链表。

好处:

4.3 per-VMA lock(6.4+)

mmap_lock 改成读端走 per-VMA lock,减少全局争抢。mm->per_vma_lock_seq 做无锁版本号检测。

效果:多线程 mmap 扩展性提升显著。

五、anon_vma 与 rmap

5.1 问题

内核要回收物理页,需要知道哪些 PTE 引用了它。对匿名页来说——fork 后父子共享同一物理页,谁的 PTE?

5.2 anon_vma

anon_vma
  └── anon_vma_chain → VMA1 (parent)
  └── anon_vma_chain → VMA2 (child)

每个匿名 VMA 有 anon_vma + anon_vma_chain(链接所有共享同 anon 页的 VMA)。page_referenced() / try_to_unmap() 通过 rmap 遍历所有映射者。

5.3 file rmap

文件页通过 address_space->i_mmap(interval tree)做 rmap。

六、mmap_lock 争抢与诊断

# 看 mmap_lock 争抢
perf lock contention -t -b -s 5
# 或
bpftrace -e 'tracepoint:lock:contention_begin /args->lock_addr == kaddr("mmap_lock")/ { @[comm]=count(); }'

典型触发者:

七、VMA 合并与拆分

内核尽量合并相邻同属性 VMA(减少 VMA 数量)。mprotect/mremap 可能拆分一个 VMA。

过多 VMA(> 65530 ≈ vm.max_map_count)会导致 mmap 失败:

# Elasticsearch 启动常踩
sysctl vm.max_map_count=262144

八、特殊 VMA

九、mm_struct 生命周期

fork → dup_mm → 复制 mm_struct + 所有 VMA(COW 页表)
exec → exec_mmap → 丢弃旧 mm,创建新 mm
exit → mmput → mm_users-- → =0 时 exit_mmap → 释放所有 VMA + 页表

mmget / mmput 是引用计数接口;mmgrab / mmdrop 管 mm_count(lazy TLB 用)。

十、观察工具

# VMA 数量
wc -l /proc/$$/maps

# 详细 VMA 信息
cat /proc/$$/smaps_rollup

# mm_struct 内核信息
cat /proc/$$/status | grep -E 'Vm|Rss|Swap'

# maple tree 统计(debugfs)
cat /sys/kernel/debug/maple_tree_allocations 2>/dev/null

十一、小结


参考文献

工具


上一篇TLB 工程 下一篇缺页处理

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-17 · os

【操作系统百科】task_struct 解剖

Linux 里「一个进程/线程」对应的内核数据结构是 struct task_struct,8KB 左右,几百个字段。本文把它切成 PID/凭据/内存/文件/信号/调度/namespace/追踪 八个区域,讲清楚 current 宏、thread_info、per_task 栈与 task_struct 的布局关系,以及字段变化背后的十年演进。

2026-04-27 · os

【操作系统百科】内存回收

Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。

2026-04-28 · os

【操作系统百科】交换

swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。

2026-05-03 · os

【操作系统百科】Slab/SLUB 分配器

buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。


By .