Linux内核6.6 内存管理(11)反向映射RMAP

1.概念

正向映射:

从VA到PA,沿着MMU硬件的脚步。malloc创建虚拟地址且没有和物理地址关联时,开始访问,就会触发下面的流程

反向映射产生的来由

1)物理内存短缺:虚拟内存常常大于物理内存/把暂时不用的物理内存swap到交换分区。如何判断哪些物理内存暂时不用?LRU算法/第二次机会法

2) 写时复制(COW):当多个进程共享同一物理页时,修改其中一个进程的映射需要知道其他映射的位置。

3) 页面回收:断开所有映射的用户PTE才能回收页面

4) 页面迁移:将一个物理页从一个 NUMA 节点迁移到另一个节点

1)找到该页被哪些进程映射;

2)更新这些进程的页表,使它们指向新的物理页;

3)确保在迁移过程中不会出现数据竞争或访问错误。

反向映射:追踪物理页被哪些虚拟地址映射 

2.数据结构

https://elixir.bootlin.com/linux/v6.11/source/include/linux/rmap.h#L31

(1)struct anon_vma

root和rb_root的区别和联系

数据结构类型

普通指针(指向anon_vma实例)

红黑树根节点(rb_root_cached结构)

组织形式

树状层级结构(父子关系)

区间索引树(基于地址范围排序)

核心功能

管理anon_vma实例的继承关系

快速查找与anon_vma关联的 VMA

应用场景

写时复制(COW)中的父子进程内存共享

内存区域的区间查询与管理

锁机制

无单独锁(依赖整体结构的锁)

使用rwsem读写信号量

关联结构

parent字段配合形成树

anon_vma_chain结构配合形成索引

(2)struct anon_vma_chain

same_vma字段和rb字段

数据结构类型

链表节点(list_head

红黑树节点(rb_node

组织形式

单向链表(连接到同一 VMA 的实例)

红黑树(按地址区间排序)

核心功能

从 VMA 反向查找关联的anon_vma

anon_vma快速查找特定地址范围的 VMA

应用场景

VMA 修改通知、内存回收遍历

物理页回收、写时复制(COW)

锁机制

使用mmap_lockpage_table_lock

使用anon_vma->rwsem

遍历效率

O (n)(遍历链表)

O (log n)(红黑树查找)

3.rmap流程

folio:一个或多个连续物理页的集合,作为一个整体进行管理

(1)增加反向映射

https://elixir.bootlin.com/linux/v6.11/source/mm/memory.c#L4620

按照页面映射(4)的文章来看

 do_anonymous_page
 	└──folio_add_new_anon_rmap 为新的匿名页添加rmap

(2)写时复制创建

写时复制路径(COW):
   └── do_wp_page()
       └── wp_page_copy()
           ├── folio_add_new_anon_rmap()  ← 新页
           ├── folio_remove_rmap_pte()    ← 旧页
           └── folio_put(old_folio) 释放旧页

(3)try_to_unmap: 尝试解除一个物理页(folio)在所有进程中的页表映射关系。

1. try_to_unmap(folio, flags)
   └── 判断是否设置 TTU_RMAP_LOCKED

2. 是 → 调用 rmap_walk_locked(folio, &rwc)
   否 → 调用 rmap_walk(folio, &rwc)

3. rmap_walk_*() 根据页类型选择:
   ├── rmap_walk_anon()
   		└── rwc->rmap_one回调到rwc的try_to_unmap_one
				try_to_unmap_one(folio, vma, address, flags)
				↓
				folio_test_hugetlb(folio)
				  └── 是 → 调用 huge_ptep_clear_flush() 或 huge_pmd_unshare()
				↓
				folio_test_anon(folio)
				  └── 是 → 后续可能写入 swap
				↓
				vma->vm_flags & VM_LOCKED
				  └── 是 → 调用 mlock_vma_folio(),跳过回收
				↓
				清除 PTE:
				  ├── should_defer_flush() 为真 → ptep_get_and_clear()
				  └── 否 → ptep_clear_flush()
				↓
				pte_dirty(pteval)
				  └── 是 → 调用 folio_mark_dirty()
				↓
				处理特殊情况:
				  ├── PageHWPoison(subpage) → make_hwpoison_entry() + set_pte_at() (替换为特殊pte)
				  ├── MADV_FREE 页:(判断是否可丢弃)
				  │   └── 判断 folio_ref_count() 和 folio_mapcount()
				  │       └── 满足条件 → 直接 discard
				  │       └── 否则 → set_pte_at() 恢复映射
				  ├── 匿名页:(写入swap)
				  │   └── page_swap_entry() → swp_entry_to_pte()
				  │   └── swap_duplicate()、arch_unmap_one()
				  │   └── set_pte_at() 设置 swap PTE
				  └── 文件页:(更新计数器)
					  └── dec_mm_counter(mm, mm_counter_file(folio))
				↓
				移除反向映射:
				  ├── hugetlb 页 → hugetlb_remove_rmap()
				  └── 普通页 → folio_remove_rmap_pte()
				↓
				如果是 mlock 页 → mlock_drain_local()
				↓
				减少引用计数 folio_put(folio) 
				↓
				返回是否成功

  • xx_get():增加引用计数,表示“我现在也在使用这个对象”;
  • xx_put():减少引用计数,表示“我不再使用这个对象了”。

当引用计数降为 0,说明没有任何地方再使用这个对象,内核就可以安全地释放它。

#嵌入式##嵌入式笔面经分享##牛客创作赏金赛#
全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务