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的区别和联系
数据结构类型 | 普通指针(指向 | 红黑树根节点( |
组织形式 | 树状层级结构(父子关系) | 区间索引树(基于地址范围排序) |
核心功能 | 管理 | 快速查找与 |
应用场景 | 写时复制(COW)中的父子进程内存共享 | 内存区域的区间查询与管理 |
锁机制 | 无单独锁(依赖整体结构的锁) | 使用 |
关联结构 | 与 | 与 |
(2)struct anon_vma_chain
same_vma字段和rb字段
数据结构类型 | 链表节点( | 红黑树节点( |
组织形式 | 单向链表(连接到同一 VMA 的实例) | 红黑树(按地址区间排序) |
核心功能 | 从 VMA 反向查找关联的 | 从 |
应用场景 | VMA 修改通知、内存回收遍历 | 物理页回收、写时复制(COW) |
锁机制 | 使用 | 使用 |
遍历效率 | 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,说明没有任何地方再使用这个对象,内核就可以安全地释放它。
#嵌入式##嵌入式笔面经分享##牛客创作赏金赛#