Linux 虚拟内存访问流程:缺页异常处理与页表映射机制详解

当用户访问一个虚拟地址时,MMU(内存管理单元)会首先查找对应的页表项是否存在。若页表项存在,这意味着该虚拟地址对应的物理页帧及其 page 结构体已存在于内存中,且物理地址有效,此时可直接通过页表获取物理地址并完成内存访问。页表项不存在但 page 和物理页存在的情况若页表项不存在,但 page 结构体和物理页已存在于内存中,会触发缺页异常。以下分文件映射和匿名页两种情况进行处理:
文件映射情况

首先,通过虚拟地址在当前进程的虚拟地址空间中查找对应的 vm_area_struct。该结构体包含 vm_start 和 vm_end,可用于判断虚拟地址是否处于此范围内。计算虚拟地址相对于 vm_start 的偏移量 page_offset,再根据此偏移量计算页索引 pgoff = page_offset >> PAGE_SHIFT。

对于文件映射,可通过 vm_area_struct 的内部成员 vm_file 查找该区域所属的文件 file,进而找到该文件的元数据 inode。在 inode 内部,借助 address_space 结构体的成员 xarray 可获取所有的 page 结构体。利用之前计算得到的页索引 pgoff,调用 struct page *page = xarray_lookup(&mapping->pages, pgoff) 就能定位到具体的那一页。

获取到实际的物理页帧后,通过虚拟地址逐层解析页目录结构,找到最后一级页表项(PTE)的存储位置。将计算得到的物理页帧号(pfn)与标志位(如存在位、读写权限等)组合后填充到该 PTE 中。标志位的设置需要依据 vm_area_struct 的权限(vm_flags)来确定,例如若 vm_flags 包含 VM_WRITE,则需设置 PTE 的写权限标志。
匿名页情况

匿名页通常用于进程的堆、栈等区域,不与具体文件关联。当触发缺页异常且判断为匿名页情况时,首先检查该匿名页是否已被换出到交换空间。

若匿名页已被换出,会根据 vm_area_struct 中记录的交换项信息,从交换空间中将其换入内存。这涉及到分配一个新的物理页,将交换空间中的数据读取到该物理页,并更新 page 结构体的相关信息,如设置 page 的映射信息和引用计数。

若匿名页未被换出,只是页表项缺失,可通过 vm_area_struct 中的 vm_pgoff(页偏移量)和虚拟地址计算出对应的 page。这通常是通过 anon_vma 结构来管理的,它维护了匿名页的反向映射关系。找到 page 后,同样获取其物理页帧号(pfn),并根据 vm_area_struct 的权限设置标志位,将物理页帧号和标志位组合后填充到对应的 PTE 中。根据虚拟地址找不到 page 或者 vm_area_struct 的情况

若根据虚拟地址既找不到 page 结构体,也找不到对应的 vm_area_struct,这通常意味着该虚拟地址对应的内存区域尚未分配或初始化。对于文件映射场景,需要通过 inode 找到文件在磁盘中的位置。从磁盘读取相应的数据,为其分配一个新的 vm_area_struct 来描述该虚拟内存区域,同时更新 address_space 结构体,将新读取的数据对应的 page 结构体添加到 address_space 的 xarray 中。对于匿名页场景,会直接分配一个新的物理页,创建对应的 page 结构体,并将其与当前的虚拟地址关联起来。同时,创建一个新的 vm_area_struct 来管理该虚拟内存区域,更新相关的内存管理数据结构。

最后,无论是文件映射还是匿名页场景,都需要更新页表。通过逐层解析虚拟地址对应的页目录结构,找到最后一级页表项(PTE)的位置,将新分配或读取的物理页帧号(pfn)与相应的标志位组合后填充到该 PTE 中,从而建立起虚拟地址到物理地址的映射关系。这样,下次访问该虚拟地址时,MMU 就能通过页表找到对应的物理地址。

1. 页表项存在时的处理

步骤

  1. MMU 查找页表:用户访问虚拟地址时,MMU 通过多级页表(PGD→P4D→PUD→PMD→PTE)查找对应的页表项(PTE)。

具体流程见:四级页表与五级页表_level 5 page table-CSDN博客

  1. 检查 PTE 存在位 :若 PTE 存在且有效(_PAGE_PRESENT 标志置位),直接获取物理页帧号(PFN),转换为物理地址完成访问。

  2. 无需内核介入:整个过程由硬件完成,无缺页异常触发。

示例

进程 A 多次读取同一文件映射区域,首次访问触发缺页异常并加载页到内存,后续访问直接通过有效 PTE 获取物理地址。


2. 页表项不存在但物理页已存在(触发缺页异常)

2.1 文件映射场景

步骤

  1. 查找 VMA :通过虚拟地址在进程的 mm_struct 红黑树中找到对应的 vm_area_struct(VMA)。

  2. 计算页索引

    cpp 复制代码
    page_offset = vaddr - vma->vm_start;  
    pgoff = page_offset >> PAGE_SHIFT;  // 计算文件内的页索引
  3. 定位文件与页缓存

    • 通过 vma->vm_file 获取文件对象 struct file

    • 通过 file->f_inode->i_mapping 获取文件的 address_space

  4. 查找缓存页

    cpp 复制代码
    struct page *page = xa_load(&mapping->i_pages, pgoff);  // 从 xarray 中查找页
  5. 填充 PTE

    • 若页存在,获取其 PFN,根据 VMA 权限(vma->vm_flags)设置 PTE 标志(如 VM_READPTE_RDONLY)。

    • 更新 PTE,建立虚拟地址到物理页的映射。

示例

进程 B 映射同一文件的不同区域,首次访问某偏移时,内核通过 address_space 的 xarray 查找缓存页,若存在则直接映射。

2.2 匿名页场景

步骤

  1. 检查交换状态

    • 若页被换出(PageSwapCache(page) 为真),根据 swap_info_struct 从交换分区读取数据到新物理页。

    • 更新页的 mapping 指向 swapper_space(交换空间的 address_space)。

  2. 未换出页的处理

    • 通过反向映射(anon_vma)找到所有共享该页的 VMA。

    • 计算虚拟地址对应的页偏移,定位物理页。

  3. 填充 PTE

    • 设置 PFN 和权限(如 VM_WRITEPTE_WRITE),更新 PTE。

示例

进程 C 调用 fork(),子进程写时复制(COW)父进程的堆内存:

  • 父进程匿名页的 PTE 变为只读。

  • 子进程写入时触发缺页异常,内核复制新页,更新子进程 PTE 为可写。


3. 找不到 page 或 VMA 的情况

3.1 文件映射场景(新区域访问)

步骤

  1. 扩展 VMA:若虚拟地址超出现有 VMA 范围,内核可能扩展或创建新 VMA。

  2. 读取磁盘数据

    • 通过 inode 定位磁盘数据,分配物理页并读取数据。

    • 将新页加入 address_space->i_pages

  3. 更新页表:填充 PTE,建立映射。

示例

进程 D 通过 mmap 扩展文件映射区域,访问新范围时触发缺页,内核从磁盘读取数据并创建新 VMA。

3.2 匿名页场景(新内存分配)

步骤

  1. 分配物理页 :调用 alloc_page() 分配新物理页,初始化 page 结构体。

  2. 关联 VMA

    • 创建新 VMA(如堆扩展),设置 anon_vma 管理反向映射。

    • 将页的 mapping 指向 anon_vma

  3. 填充 PTE:设置 PFN 和权限,更新页表。

示例

进程 E 调用 malloc() 分配内存,首次访问时触发缺页,内核分配匿名页并关联到堆 VMA。


4. 页表更新与后续访问

步骤

  1. 逐级填充页表:从 PGD 到 PTE,确保各级页目录项存在。

  2. 设置 PTE 标志 :根据 VMA 权限(如 VM_EXECPTE_X)设置访问控制。

  3. TLB 刷新 :通过 flush_tlb_page() 刷新 TLB 缓存,确保新映射生效。

示例

进程 F 修改文件映射页的权限(如 mprotect(PROT_WRITE)),内核更新 PTE 的写标志并刷新 TLB。


关键数据结构关系

  • 文件映射
    vma→vm_file→f_inode→i_mapping→i_pages(xarray 管理页缓存)。

  • 匿名映射
    vma→anon_vma 管理反向映射,page→mapping 指向 anon_vma 或交换空间。

相关推荐
MobiCetus13 分钟前
Linux Kernel 7
linux·运维·服务器·windows·ubuntu·centos·gnu
西洼工作室27 分钟前
centos时间不正确解决
linux·运维·centos
再学一丢丢37 分钟前
用户管理和权限管理
linux·运维·服务器
碧寒1 小时前
Ubuntu系统18.04更新驱动解决方法
linux·运维·ubuntu
charlie1145141911 小时前
IMX6ULL2025年最新部署方案2在Ubuntu24.04上编译通过Qt5.12.9且部署到IMX6ULL正点原子开发板上
linux·嵌入式硬件·qt·系统架构·嵌入式软件·移植教程
Bardb1 小时前
05--MQTT物联网协议
linux·服务器·阿里云·json
红虾程序员2 小时前
Linux进阶命令
linux·服务器·前端
.R^O^2 小时前
VLAN的知识
linux·服务器·网络·mysql
长流小哥3 小时前
Linux 深入浅出信号量:从线程到进程的同步与互斥实战指南
linux·c语言·开发语言·bash
派阿喵搞电子3 小时前
Ubuntu 常用命令行指令
linux·ubuntu