一、反向映射
在 Linux 系统里,针对匿名映射的管理,需要一种机制来明确一个物理页 page 在不同进程的 vm_area_struct 中的具体虚拟地址位置。这主要依靠反向映射机制,结合 anon_vma 和 vm_area_struct 的信息达成。
(一)反向映射的核心数据结构
page结构中的反向映射信息
每个物理页page会依据映射类型包含不同的指针。对于文件映射,包含一个struct address_space指针;对于匿名映射,则包含一个struct anon_vma指针。在匿名映射场景下,page->mapping指向其所属的anon_vma,而anon_vma维护着一个红黑树,该红黑树记录了所有映射该匿名区域的vm_area_struct,这些vm_area_struct可能来自不同进程,代表着跨进程共享该匿名页的虚拟内存区域(VMA)。vm_area_struct中的关键字段vm_mm:指向所属进程的mm_struct(地址空间),用于区分不同进程的 VMA。vm_start:该 VMA 在所属进程虚拟地址空间中的起始地址。vm_pgoff:表示文件或匿名映射在该vm_area_struct里的起始页偏移量(以页为单位)。对于文件映射,代表从文件起始处开始的页偏移;对于匿名映射,代表在匿名映射区域内的页偏移。
(二)从 page 定位到具体虚拟地址的步骤
若有一个匿名页 page,要找出它在所有共享进程中的虚拟地址位置,可按以下步骤操作:
- 通过
page->mapping获取anon_vma
对于匿名页,page->mapping指向其所属的anon_vma。anon_vma的红黑树(vma_list)存储了所有映射该匿名区域的vm_area_struct,这些vm_area_struct可能来自不同进程。 - 遍历红黑树中的所有
vm_area_struct
对红黑树中的每个vma执行以下操作:- 获取所属进程的地址空间:通过
vma->vm_mm得到该 VMA 所属进程的mm_struct。 - 计算虚拟地址:
- 首先,借助
page->index和vma->vm_pgoff确定该页在vma里的相对位置,计算公式为:
页在vm_area_struct内的相对页偏移 =page->index - vm_area_struct->vm_pgoff - 然后,根据相对页偏移计算该页对应的虚拟地址,计算公式为:
虚拟地址 =vm_area_struct->vm_start + (页在 vm_area_struct 内的相对页偏移 * PAGE_SIZE)
- 首先,借助
- 获取所属进程的地址空间:通过
(三)关键区别:匿名映射的 vm_pgoff 是 “局部偏移”
- 文件映射的
pgoff:基于文件的逻辑偏移,所有进程映射同一文件偏移时,pgoff相同,可直接通过address_space->i_pages进行全局索引。 - 匿名映射的
vm_pgoff:仅表示页在当前 VMA 映射区域内的相对位置(例如,第n个页)。不同进程的 VMA 可以有相同的vm_pgoff,但虚拟地址会因vm_start不同而不同。
(四)反向映射的典型场景:写时复制(COW)
当匿名页被多个进程共享时,若其中一个进程修改该页,就会触发写时复制机制。此时,内核会通过 page->mapping->anon_vma 找到所有共享该页的 VMA。对于每个 VMA(除当前进程外),内核会检查是否需要分离(例如,子进程的 VMA 可能继续共享,父进程的 VMA 需创建新页)。在分离时,内核会通过 vma->vm_start + vm_pgoff×PAGE_SIZE 确定原虚拟地址,并更新页表使其指向新分配的物理页。
(五)示例说明
【示例一】
假设存在一个 vm_area_struct 结构体 vma,其 vm_start = 0x10000,vm_pgoff = 2,页大小 PAGE_SIZE = 4KB(即 0x1000)。现在有两个物理页 page1 和 page2 都映射到这个 vma 上,page1->index = 2,page2->index = 3。
- 对于
page1- 页在
vma内的相对页偏移:page1->index - vma->vm_pgoff = 2 - 2 = 0 - 对应的虚拟地址:
vma->vm_start + (0 * PAGE_SIZE) = 0x10000
- 页在
- 对于
page2- 页在
vma内的相对页偏移:page2->index - vma->vm_pgoff = 3 - 2 = 1 - 对应的虚拟地址:
vma->vm_start + (1 * PAGE_SIZE) = 0x10000 + 0x1000 = 0x11000
- 页在
综上所述,尽管 vm_area_struct 里只有一个 vm_pgoff 字段,但通过结合物理页的 index 字段,就能计算出每个物理页在 vm_area_struct 里的相对位置,进而得到对应的虚拟地址,从而可以索引到同一个 vm_area_struct 的不同位置。同时,同一物理页在不同进程中的虚拟地址由各自 VMA 的 vm_start 和 vm_pgoff 共同决定,而 anon_vma 的红黑树仅用于跟踪哪些 VMA 共享了该页。
【示例二】
假设条件:
- 页大小:假设页大小
PAGE_SIZE = 4KB(即 4096 字节,在计算中用0x1000表示十六进制)。 - 匿名页信息:有一个匿名页,其
page->index = 3。 - 进程 A 的
vm_area_struct(VMA1):vm_start = 0x10000vm_pgoff = 2
- 进程 B 的
vm_area_struct(VMA2):vm_start = 0x20000vm_pgoff = 1
计算过程:
- 计算该匿名页在进程 A 的
vm_area_struct中的虚拟地址- 计算相对页偏移:
根据公式 页在vm_area_struct内的相对页偏移 =page->index - vm_area_struct->vm_pgoff,可得该匿名页在 VMA1 内的相对页偏移为:3 - 2 = 1 - 计算虚拟地址:
根据公式 虚拟地址 =vm_area_struct->vm_start + (页在 vm_area_struct 内的相对页偏移 * PAGE_SIZE),可得该匿名页在进程 A 中的虚拟地址为:0x10000 + (1 * 0x1000) = 0x11000
- 计算相对页偏移:
- 计算该匿名页在进程 B 的
vm_area_struct中的虚拟地址- 计算相对页偏移:
同样根据相对页偏移公式,该匿名页在 VMA2 内的相对页偏移为:3 - 1 = 2 - 计算虚拟地址:
再根据虚拟地址计算公式,该匿名页在进程 B 中的虚拟地址为:0x20000 + (2 * 0x1000) = 0x22000
- 计算相对页偏移:
二、正向映射
(一)正向映射的核心流程
当进程访问一个文件映射的虚拟地址时,内核通过以下步骤找到对应的物理页(页缓存):
- 根据虚拟地址找到
vm_area_struct(VMA)
通过进程的虚拟地址空间(mm_struct)的红黑树或链表,快速定位包含该地址的vm_area_struct。 - 计算虚拟地址相对于 VMA 的偏移
虚拟地址偏移:offset_in_vma = 虚拟地址 - vm_start(单位为字节,例如0x10004 - 0x10000 = 4 字节)。 - 转换为文件逻辑页偏移(
pgoff)
页偏移计算:
pgoff = vm_pgoff + (offset_in_vma / PAGE_SIZE)vm_pgoff:VMA 映射的文件起始页偏移(以页为单位,已记录在vm_area_struct中)。offset_in_vma / PAGE_SIZE:虚拟地址在 VMA 内的页偏移(例如,4 字节偏移在 4KB 页中对应页偏移 0)。
- 通过
address_space的xarray查找页缓存
使用文件的address_space结构,以pgoff为键,从xarray(file->f_mapping->i_pages)中直接获取页缓存:
page = xa_load(&file->f_mapping->i_pages, pgoff)。
(二)示例验证
假设场景:
- 文件:大小 12KB,分为 3 页(
pgoff=0,1,2)。 - 进程映射:
- 创建两个
vm_area_struct(VMA1 和 VMA2):- VMA1:
vm_start = 0x10000,vm_end = 0x11000(映射 4KB)。vm_pgoff = 1(映射文件的第二页,即pgoff=1)。
- VMA2:
vm_start = 0x20000,vm_end = 0x22000(映射 8KB,两页)。vm_pgoff = 0(映射文件的前两页,即pgoff=0和pgoff=1)。
- VMA1:
- 创建两个
案例 1:访问 VMA1 的虚拟地址 0x10004
- 查找 VMA:定位到 VMA1(
0x10000-0x11000)。 - 计算偏移:
offset_in_vma = 0x10004 - 0x10000 = 4 字节。 - 转换为
pgoff:
pgoff = vm_pgoff(1) + (4 / 4096) = 1 + 0 = 1。 - 查找页缓存:通过
pgoff=1找到文件的第二页。
案例 2:访问 VMA2 的虚拟地址 0x21000
- 查找 VMA:定位到 VMA2(
0x20000-0x22000)。 - 计算偏移:
offset_in_vma = 0x21000 - 0x20000 = 0x1000(4KB)。 - 转换为
pgoff:
pgoff = vm_pgoff(0) + (0x1000 / 0x1000) = 0 + 1 = 1。 - 查找页缓存:通过
pgoff=1找到文件的第二页。
(三)关键点验证
vm_pgoff的作用vm_pgoff表示该 VMA 映射的文件起始页偏移。例如,若 VMA 映射文件的 4KB-8KB(即第二页到第三页),则vm_pgoff = 1。
- 虚拟地址到
pgoff的转换- 内核通过
offset_in_vma / PAGE_SIZE将字节偏移转换为页偏移。例如,offset_in_vma=4096对应页偏移 1。 - 最终
pgoff是全局唯一的:pgoff = vm_pgoff + 页偏移,唯一标识文件中的逻辑页,与进程的虚拟地址无关。
- 内核通过
- xarray 的全局管理
- 无论进程如何映射文件,同一
pgoff始终对应同一物理页。例如:进程 A 的 VMA1(pgoff=1)和进程 B 的 VMA(pgoff=1)共享同一物理页。 - 物理页的分配、释放、回写均由
address_space统一管理。
- 无论进程如何映射文件,同一
(四)对比匿名映射
- 匿名映射无全局
pgoff:匿名页的物理地址由进程的虚拟地址空间独立管理,无法通过类似pgoff的键全局索引。例如:进程 A 的0x10000和进程 B 的0x10000映射匿名页,物理页不同。 - 匿名页的共享管理:匿名页的共享需通过
MAP_SHARED或fork()等方式声明,依赖anon_vma的红黑树管理。
