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 或交换空间。

相关推荐
云计算练习生19 小时前
linux shell编程实战 10 Git工具详解与运维场景实战
linux·运维·git
虚伪的空想家21 小时前
KVM的ubuntu虚机如何关闭安全启动
linux·安全·ubuntu
t198751281 天前
在Ubuntu 22.04系统上安装libimobiledevice
linux·运维·ubuntu
skywalk81631 天前
linux安装Code Server 以便Comate IDE和CodeBuddy等都可以远程连上来
linux·运维·服务器·vscode·comate
晚风吹人醒.1 天前
缓存中间件Redis安装及功能演示、企业案例
linux·数据库·redis·ubuntu·缓存·中间件
Hard but lovely1 天前
linux: pthread库的使用和理解
linux
这儿有一堆花1 天前
Kali Linux:探测存活到挖掘漏洞
linux·运维·服务器
松涛和鸣1 天前
从零开始理解 C 语言函数指针与回调机制
linux·c语言·开发语言·嵌入式硬件·排序算法
皮小白1 天前
ubuntu开机检查磁盘失败进入应急模式如何修复
linux·运维·ubuntu
邂逅星河浪漫1 天前
【CentOS】虚拟机网卡IP地址修改步骤
linux·运维·centos