2. Linux 内存管理基础:虚拟地址、物理地址、页表、MMU 工作原理

在深入 mmap 与寄存器直接访问之前,必须理解 Linux 内存管理的核心抽象:虚拟地址(Virtual Address, VA)与物理地址(Physical Address, PA)的映射关系。用户态程序无法直接操作物理地址,所有内存访问都经过 MMU(Memory Management Unit)和页表(Page Table)的转换。本章将拆解这一过程,为后续的 mmap 实现用户态直接寄存器访问打下理论基础。

2.1 虚拟地址空间与物理地址空间

每个用户态进程拥有独立的 虚拟地址空间,在 32 位系统上通常为 4 GiB(0x00000000 ~ 0xFFFFFFFF),在 64 位系统上则更大。物理地址空间是系统实际可用的 RAM 与 MMIO(Memory-Mapped I/O)区域的集合。

概念 描述 典型范围(32位)
虚拟地址 (VA) CPU 发出的地址,由 MMU 转换 0x00000000 ~ 0xFFFFFFFF
物理地址 (PA) 实际访问内存/外设寄存器的地址 0x00000000 ~ 物理 RAM 上限
用户空间 进程可访问的虚拟地址范围 0x00000000 ~ 0xBFFFFFFF (3 GiB)
内核空间 内核与驱动使用的虚拟地址范围 0xC0000000 ~ 0xFFFFFFFF (1 GiB)
关键点: 外设寄存器通常映射在物理地址的高端区域(如 0xF0000000 以上),用户态程序无法直接访问。通过 mmap 可以将这些物理地址映射到用户空间的虚拟地址上,从而绕过内核态的限制。

2.2 MMU 与地址转换流程

MMU 是 CPU 内部的一个硬件单元,负责将虚拟地址转换为物理地址。转换过程依赖页表,页表由操作系统维护,存放在内存中。MMU 通过页表基址寄存器(如 x86 的 CR3)找到当前进程的页表。

地址转换三步走:
  1. 分段:虚拟地址被拆分为页号(Page Number)和页内偏移(Offset)。
  2. 查表:MMU 以页号为索引,遍历多级页表,找到对应的物理页框号(Page Frame Number, PFN)。
  3. 拼接:物理地址 = 物理页框号 + 页内偏移。
MMU 地址转换流程 虚拟地址 (VA) 0x00A0_1000 页号 + 偏移 0x00A0 + 0x1000 多级页表查询 PFN = 0x3F00 物理地址 (PA) 0x3F00_1000 MMU 硬件自动完成 无需软件干预

2.3 页表结构与多级映射

现代操作系统使用多级页表(如 x86_64 的 4 级页表),以节省内存。页表项(PTE)不仅包含物理页框号,还包含权限位(读/写/执行)、存在位、脏位等控制信息。

页表级别 名称(x86_64) 覆盖范围 典型大小
L1 PML4 (Page Map Level 4) 512 GiB 512 项 × 8 字节 = 4 KiB
L2 PDPT (Page Directory Pointer Table) 1 GiB 512 项 × 8 字节 = 4 KiB
L3 PD (Page Directory) 2 MiB 512 项 × 8 字节 = 4 KiB
L4 PT (Page Table) 4 KiB 512 项 × 8 字节 = 4 KiB
注意: 页表由内核管理,用户态程序无法直接修改。但通过 mmap 系统调用,内核会在当前进程的页表中插入新的映射条目,从而允许用户态访问特定的物理内存或寄存器。
x86_64 四级页表结构 虚拟地址 (64位): [PML4索引] [PDPT索引] [PD索引] [PT索引] [页内偏移] PML4 512 项 PDPT 512 项 PD 512 项 PT 512 项 物理页框 (4 KiB) PFN = 0x3F00 PTE 包含权限位 R/W/X, Present, Dirty

2.4 TLB 与缓存一致性

每次地址转换都查询多级页表会带来巨大的性能开销。MMU 内部集成了 TLB(Translation Lookaside Buffer),即页表缓存,用于缓存最近使用的 VA→PA 映射。当进程切换或页表更新时,TLB 需要被刷新(Flush)。

TLB 刷新场景:
  • 进程上下文切换:切换到不同进程时,TLB 中的旧映射失效。
  • mmap / munmap:建立或解除内存映射后,需要刷新相关 TLB 条目。
  • 修改页表权限:如通过 mprotect 改变内存保护属性。

2.5 用户态直接寄存器访问的桥梁:mmap

理解了虚拟地址与物理地址的映射机制后,mmap 的角色就清晰了:

  1. 用户态调用:mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset)。
  2. 内核处理:驱动中的 mmap 操作函数将物理地址(寄存器基址)映射到用户空间的虚拟地址。
  3. 页表插入:内核在当前进程的页表中添加新的 PTE,将用户虚拟页指向物理寄存器页。
  4. 用户态直接访问:应用程序通过返回的虚拟地址指针直接读写寄存器,无需系统调用。
重要限制: 映射粒度受限于页面大小(通常为 4 KiB)。即使只需要访问一个 32 位寄存器,也必须映射整个物理页。此外,映射区域必须按页对齐。
mmap 建立用户态到寄存器的映射 用户空间 虚拟地址 VA 0x7F00_0000 内核空间 页表操作 插入 PTE 物理寄存器 物理地址 PA 0xF000_4000 用户态通过 VA 直接读写寄存器,无需系统调用 mmap 建立映射后,MMU 自动完成 VA → PA 转换

2.6 本章小结

  • 虚拟地址是进程看到的地址空间,物理地址是硬件实际使用的地址。
  • MMU 负责将虚拟地址转换为物理地址,转换依据是页表。
  • 页表采用多级结构(如 x86_64 的四级页表),以平衡内存占用与查询效率。
  • TLB 缓存最近使用的地址映射,减少页表查询开销。
  • mmap 的本质是在进程页表中插入新的映射条目,使用户态虚拟地址直接指向物理寄存器地址。