当前位置: 首页 > news >正文

Linux 内存映射机制:正向映射与反向映射深度解析

一、反向映射

        在 Linux 系统里,针对匿名映射的管理,需要一种机制来明确一个物理页 page 在不同进程的 vm_area_struct 中的具体虚拟地址位置。这主要依靠反向映射机制,结合 anon_vma 和 vm_area_struct 的信息达成。

(一)反向映射的核心数据结构
  1. page 结构中的反向映射信息
    每个物理页 page 会依据映射类型包含不同的指针。对于文件映射,包含一个 struct address_space 指针;对于匿名映射,则包含一个 struct anon_vma 指针。在匿名映射场景下,page->mapping 指向其所属的 anon_vma,而 anon_vma 维护着一个红黑树,该红黑树记录了所有映射该匿名区域的 vm_area_struct,这些 vm_area_struct 可能来自不同进程,代表着跨进程共享该匿名页的虚拟内存区域(VMA)。
  2. vm_area_struct 中的关键字段
    • vm_mm:指向所属进程的 mm_struct(地址空间),用于区分不同进程的 VMA。
    • vm_start:该 VMA 在所属进程虚拟地址空间中的起始地址。
    • vm_pgoff:表示文件或匿名映射在该 vm_area_struct 里的起始页偏移量(以页为单位)。对于文件映射,代表从文件起始处开始的页偏移;对于匿名映射,代表在匿名映射区域内的页偏移。
(二)从 page 定位到具体虚拟地址的步骤

若有一个匿名页 page,要找出它在所有共享进程中的虚拟地址位置,可按以下步骤操作:

  1. 通过 page->mapping 获取 anon_vma
    对于匿名页,page->mapping 指向其所属的 anon_vmaanon_vma 的红黑树(vma_list)存储了所有映射该匿名区域的 vm_area_struct,这些 vm_area_struct 可能来自不同进程。
  2. 遍历红黑树中的所有 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 = 0x10000vm_pgoff = 2,页大小 PAGE_SIZE = 4KB(即 0x1000)。现在有两个物理页 page1 和 page2 都映射到这个 vma 上,page1->index = 2page2->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 = 0x10000
    • vm_pgoff = 2
  • 进程 B 的 vm_area_struct(VMA2):
    • vm_start = 0x20000
    • vm_pgoff = 1

计算过程:

  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
  2. 计算该匿名页在进程 B 的 vm_area_struct 中的虚拟地址
    • 计算相对页偏移:
      同样根据相对页偏移公式,该匿名页在 VMA2 内的相对页偏移为:3 - 1 = 2
    • 计算虚拟地址:
      再根据虚拟地址计算公式,该匿名页在进程 B 中的虚拟地址为:0x20000 + (2 * 0x1000) = 0x22000

二、正向映射

(一)正向映射的核心流程

当进程访问一个文件映射的虚拟地址时,内核通过以下步骤找到对应的物理页(页缓存):

  1. 根据虚拟地址找到 vm_area_struct(VMA)
    通过进程的虚拟地址空间(mm_struct)的红黑树或链表,快速定位包含该地址的 vm_area_struct
  2. 计算虚拟地址相对于 VMA 的偏移
    虚拟地址偏移:offset_in_vma = 虚拟地址 - vm_start(单位为字节,例如 0x10004 - 0x10000 = 4 字节)。
  3. 转换为文件逻辑页偏移(pgoff
    页偏移计算:
    pgoff = vm_pgoff + (offset_in_vma / PAGE_SIZE)
    • vm_pgoff:VMA 映射的文件起始页偏移(以页为单位,已记录在 vm_area_struct 中)。
    • offset_in_vma / PAGE_SIZE:虚拟地址在 VMA 内的页偏移(例如,4 字节偏移在 4KB 页中对应页偏移 0)。
  4. 通过 address_space 的 xarray 查找页缓存
    使用文件的 address_space 结构,以 pgoff 为键,从 xarrayfile->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 = 0x10000vm_end = 0x11000(映射 4KB)。
        • vm_pgoff = 1(映射文件的第二页,即 pgoff=1)。
      • VMA2
        • vm_start = 0x20000vm_end = 0x22000(映射 8KB,两页)。
        • vm_pgoff = 0(映射文件的前两页,即 pgoff=0 和 pgoff=1)。

案例 1:访问 VMA1 的虚拟地址 0x10004

  1. 查找 VMA:定位到 VMA1(0x10000-0x11000)。
  2. 计算偏移offset_in_vma = 0x10004 - 0x10000 = 4 字节
  3. 转换为 pgoff
    pgoff = vm_pgoff(1) + (4 / 4096) = 1 + 0 = 1
  4. 查找页缓存:通过 pgoff=1 找到文件的第二页。

案例 2:访问 VMA2 的虚拟地址 0x21000

  1. 查找 VMA:定位到 VMA2(0x20000-0x22000)。
  2. 计算偏移offset_in_vma = 0x21000 - 0x20000 = 0x1000(4KB)
  3. 转换为 pgoff
    pgoff = vm_pgoff(0) + (0x1000 / 0x1000) = 0 + 1 = 1
  4. 查找页缓存:通过 pgoff=1 找到文件的第二页。
(三)关键点验证
  1. vm_pgoff 的作用
    • vm_pgoff 表示该 VMA 映射的文件起始页偏移。例如,若 VMA 映射文件的 4KB-8KB(即第二页到第三页),则 vm_pgoff = 1
  2. 虚拟地址到 pgoff 的转换
    • 内核通过 offset_in_vma / PAGE_SIZE 将字节偏移转换为页偏移。例如,offset_in_vma=4096 对应页偏移 1。
    • 最终 pgoff 是全局唯一的:pgoff = vm_pgoff + 页偏移,唯一标识文件中的逻辑页,与进程的虚拟地址无关。
  3. xarray 的全局管理
    • 无论进程如何映射文件,同一 pgoff 始终对应同一物理页。例如:进程 A 的 VMA1(pgoff=1)和进程 B 的 VMA(pgoff=1)共享同一物理页。
    • 物理页的分配、释放、回写均由 address_space 统一管理。
(四)对比匿名映射
  • 匿名映射无全局 pgoff:匿名页的物理地址由进程的虚拟地址空间独立管理,无法通过类似 pgoff 的键全局索引。例如:进程 A 的 0x10000 和进程 B 的 0x10000 映射匿名页,物理页不同。
  • 匿名页的共享管理:匿名页的共享需通过 MAP_SHARED 或 fork() 等方式声明,依赖 anon_vma 的红黑树管理。

相关文章:

  • LeetCode零钱兑换(动态规划)
  • MYSQL数据库语法补充2
  • Rancher 全面介绍
  • 《P2660 zzc 种田》
  • 创建一个简单的HTML游戏站
  • JS 数组相同的key 进行合并
  • 【强化学习】时间差分(Temporal Difference, TD)
  • OpenCv高阶(一)——图像金字塔(上采样、下采样)
  • 探秘AI(002)之“文心一言(文小言)”
  • Linux普通用户怎么切换为root用户
  • 如何避免论文内容被误认为是 AI 生成的?
  • 【第一天】 OSG初探——环境搭建与第一个3D窗口
  • 大模型的输出:温度对输出的影响
  • 开发效率提升200%——cursor
  • Windows Anaconda使用Sentence-BERT获取句子向量
  • 驱动-创建设备节点
  • Spring MVC与Spring Boot文件上传配置项对比
  • 什么是模型上下文协议(MCP)?
  • openEuler24.03 LTS下安装Flink
  • 搜索引擎是如何理解你的查询并提供精准结果的?