Linux内核学习轨迹第五部:反向映射RMAP机制全解析(第八小节)
8. 反向映射RMAP机制全解析
8.1 反向映射的核心设计目标
- 快速查找页表项:给定一个物理页,能快速找到所有映射了这个页的进程页表项,用于修改页表项(比如回收页时清除页表项、页迁移时修改页表项的物理地址);
- 最小化内存开销:反向映射的元数据开销必须极小,不能占用大量内存,尤其是系统中有海量进程、海量物理页的场景;
- 高性能的操作接口:反向映射的遍历、添加、删除操作必须高效,不能成为内存管理的性能瓶颈;
- 支持复杂场景:支持私有映射、共享映射、写时复制、线程共享地址空间、大页等复杂场景。
8.2 反向映射的核心分类
- 文件页的反向映射:基于struct address_space和优先级搜索树(Interval Tree)实现,用于页缓存中的文件页,包括可执行文件、共享库、数据文件的映射页;
- 匿名页的反向映射:基于struct anon_vma和anon_vma_chain实现,用于没有后备文件的匿名页,包括堆、栈、mmap匿名映射的页。
8.3 文件页的反向映射实现
8.3.1 核心数据结构
struct address_space:每个打开的文件对应一个address_space实例,管理该文件的页缓存,是文件页反向映射的核心锚点,定义在include/linux/fs.h中,核心字段:
struct address_space { // 文件的宿主inode struct inode *host; // 页缓存的XArray,存储该文件的所有缓存页 struct xarray i_pages; // 映射了该文件的所有VMA的区间树,反向映射的核心 struct maple_tree i_mmap; // 地址空间的自旋锁 rwlock_t i_mmap_rwsem; // 脏页、回写相关字段 unsigned long nrpages; struct writeback_control *writeback; };核心字段i_mmap:Maple Tree(枫树树,Linux 6.2+替换了之前的优先级搜索树),存储所有映射了该文件的VMA,每个VMA对应文件的一个偏移区间,通过文件偏移可以快速找到所有映射了该偏移的VMA。
struct vm_area_struct:每个文件映射的VMA,会被插入到对应文件的address_space->i_mmap区间树中,key是VMA映射的文件偏移区间[vm_pgoff, vm_pgoff + (vm_end - vm_start)/PAGE_SIZE]。
8.3.2 文件页反向映射的核心流程
- 从物理页的struct page结构体中,获取对应的address_space(page->mapping)和页内偏移page->index;
- 加i_mmap_rwsem读锁,遍历address_space->i_mmap区间树,找到所有映射了该文件偏移page->index的VMA;
- 对于每个找到的VMA,计算该页在VMA中的虚拟地址:vma->vm_start + (page->index - vma->vm_pgoff) * PAGE_SIZE;
- 通过VMA所属的mm_struct(vma->vm_mm)和虚拟地址,遍历页表,找到对应的页表项;
- 对页表项执行对应的操作:比如内存回收时清除页表项、页迁移时修改页表项的物理地址、设置页表项为只读(COW)等。
- 文件页的反向映射,只需要存储文件的address_space和页内偏移,不需要为每个页存储额外的元数据,内存开销极小;
- Maple Tree的区间查询效率极高,O(logn)时间复杂度就能找到所有映射了该偏移的VMA;
- 同一个文件的多个共享映射、私有映射,都会被插入到同一个i_mmap区间树中,一次遍历就能找到所有映射了该页的VMA。
8.4 匿名页的反向映射实现
8.4.1 核心设计思想
8.4.2 核心数据结构
struct anon_vma:匿名页反向映射的核心锚点,管理所有映射了同一组匿名页的VMA,定义在include/linux/rmap.h中:
struct anon_vma { // 保护该结构的自旋锁 struct rw_semaphore rwsem; // 所有链接到该anon_vma的VMA的链表 struct rb_root rb_root; // 该anon_vma对应的根VMA struct vm_area_struct *root_vma; // 引用计数 atomic_t refcount; };核心字段rb_root:红黑树,存储所有链接到该anon_vma的VMA,通过虚拟地址可以快速找到对应的VMA。
struct anon_vma_chain:连接VMA和anon_vma的桥梁,每个VMA和anon_vma的对应关系,都有一个anon_vma_chain实例,定义在include/linux/rmap.h中:
struct anon_vma_chain { // 对应的VMA struct vm_area_struct *vma; // 对应的anon_vma struct anon_vma *anon_vma; // 加入anon_vma->rb_root的红黑树节点 struct rb_node rb; // 加入VMA的anon_vma_chain链表的节点 struct list_head same_vma; };每个VMA有一个anon_vma_chain链表,链接了所有和该VMA关联的anon_vma;
每个anon_vma有一个红黑树,链接了所有和该anon_vma关联的VMA。
struct page中的反向映射字段:匿名页的page->mapping字段,设置为PAGE_MAPPING_ANON,表示这是匿名页,page->anon_vma指向该页对应的anon_vma,page->index是该页在VMA中的虚拟地址偏移。
8.4.3 匿名页反向映射的核心流程
- 从物理页的struct page结构体中,确认是匿名页(PAGE_MAPPING_ANON),获取对应的anon_vma(page_anon_vma(page))和页的虚拟地址偏移page->index;
- 加anon_vma->rwsem读锁,遍历anon_vma->rb_root红黑树,找到所有包含该偏移的VMA;
- 对于每个找到的VMA,计算该页在VMA中的虚拟地址:vma->vm_start + (page->index - vma->vm_pgoff) * PAGE_SIZE;
- 通过VMA所属的mm_struct和虚拟地址,遍历页表,找到对应的页表项;
- 对页表项执行对应的操作:清除页表项、修改物理地址、设置只读等。
8.4.4 fork()场景下的反向映射处理
- 子进程复制父进程的匿名VMA,创建新的VMA结构体;
- 为子进程的VMA创建新的anon_vma_chain实例;
- 把子进程的VMA,通过anon_vma_chain链接到父进程的anon_vma中;
- 同时,为子进程的VMA创建新的anon_vma,用于子进程后续新建的匿名页;
- 父子进程的匿名页的页表项都设置为只读,后续写入时触发COW异常。
8.5 反向映射的核心应用场景
- 内存回收:内核回收一个物理页时,必须通过反向映射找到所有映射了该页的页表项,清除页表项,刷新TLB,然后才能释放物理页。如果没有反向映射,内存回收完全无法实现;
- 写时复制COW:COW异常处理时,需要通过反向映射找到所有共享该页的页表项,修改页表项,确保只有触发异常的进程复制新页,其他进程的页表项保持不变;
- 页迁移:内存碎片整理、内存热插拔、NUMA平衡时,需要把物理页从一个地址迁移到另一个地址,必须通过反向映射找到所有映射了该页的页表项,修改页表项的物理地址,刷新TLB,保证进程访问不受影响;
- KSM同页合并:内核同页合并机制(KSM),会把内容相同的匿名页合并为一个只读页,减少内存占用,必须通过反向映射找到所有映射了原页的页表项,修改为指向合并后的页;
- 页错误处理:处理页被换出swap、页被锁定等异常时,需要通过反向映射找到对应的页表项,修改页表项的状态。
8.6 工程实践与认知纠正
1.反向映射的内存开销认知
- 文件页的反向映射,元数据存储在文件的address_space中,每个文件只需要一个区间树,内存开销极小;
- 匿名页的反向映射,anon_vma和anon_vma_chain的数量和VMA的数量成正比,而不是和物理页的数量成正比,哪怕系统中有几十GB的内存,反向映射的元数据开销也只有几MB,完全可以忽略;
- 内核做了大量的优化,比如anon_vma的复用、红黑树的高效管理,最小化内存开销。
2.反向映射的性能优化
- 用Maple Tree替换了传统的优先级搜索树,区间查询的性能提升了30%以上;
- 反向映射的遍历采用RCU机制,读操作不需要加排他锁,允许多个CPU并发遍历;
- 内存回收时,优先回收只有一个映射的页(_mapcount == 0),不需要遍历反向映射,提升回收效率;
- 页迁移时,优先迁移映射数量少的页,减少反向映射的遍历开销。
3.反向映射的调试与排查
- 开启CONFIG_DEBUG_RMAP内核配置,开启反向映射的调试检查,检测非法的VMA链接、引用计数泄漏等问题;
- 用/proc/pid/maps查看进程的VMA,确认匿名VMA的anon_vma是否正常;
- 用crash工具分析内核coredump,查看anon_vma的红黑树、VMA的链表,定位反向映射的异常。
4.认知纠正:匿名页的共享范围
