2. Linux 内存管理基础:虚拟地址与物理地址、页表与 MMU、用户空间与内核空间划分

在深入 mmap 与 DMA 缓冲区共享之前,必须透彻理解 Linux 内存管理的三大基石:虚拟地址与物理地址的映射机制、页表与 MMU 的协同工作、以及用户空间与内核空间的严格划分。这些概念是驱动开发者实现零拷贝、高效数据共享的理论基础。

2.1 虚拟地址与物理地址:为什么需要两层抽象?

物理地址是硬件总线上的真实地址,而虚拟地址是每个进程看到的独立地址空间。引入虚拟地址的核心目的有三:

  • 进程隔离:每个进程拥有独立的 32/64 位虚拟地址空间,防止恶意或错误的进程访问其他进程的内存。
  • 简化内存管理:物理内存可以是非连续的,但虚拟地址空间对进程表现为连续的线性区域。
  • 按需分配:只有真正访问的虚拟页才分配物理页框,提高内存利用率。
驱动开发关键点:DMA 控制器只能操作物理地址,而用户态程序只能看到虚拟地址。因此,mmap 的本质就是在虚拟地址与物理地址之间建立映射,使得用户态可以直接访问 DMA 缓冲区。

下表总结了两种地址的核心差异:

特性 虚拟地址 (VA) 物理地址 (PA)
可见性 每个进程独立 全局唯一
连续性 对进程连续 可能不连续
大小 受 CPU 位数限制 (如 48 位) 受硬件总线宽度限制
DMA 使用 不可直接使用 必须使用
权限控制 通过页表项控制 无直接权限控制

2.2 页表与 MMU:地址转换的核心引擎

MMU (Memory Management Unit) 是 CPU 内部的硬件单元,负责将虚拟地址实时转换为物理地址。页表 (Page Table) 是 MMU 查询的转换规则表,存储在内存中。每个进程拥有独立的页表。

现代 Linux 使用多级页表(如 x86_64 的 4 级页表)来节省内存。转换过程如下:

  1. CPU 发出虚拟地址。
  2. MMU 将虚拟地址拆分为多级索引和偏移量。
  3. MMU 逐级查询页表,最终找到物理页框号 (PFN)。
  4. 将 PFN 与页内偏移组合,得到物理地址。
  5. 如果页表项缺失(缺页异常),触发内核的缺页中断处理程序。
性能优化:TLB (Translation Lookaside Buffer) 是 MMU 内部的页表缓存,用于加速地址转换。驱动开发中,频繁的 mmap 和 munmap 操作可能导致 TLB 刷新,影响性能。

下图展示了虚拟地址到物理地址的转换流程:

虚拟地址到物理地址转换流程 (MMU + 页表) 虚拟地址 (VA) MMU 解析 拆分索引 + 偏移 多级页表查询 PML4 → PDPT → PD → PT PA 缺页异常 → 内核分配物理页框 TLB (页表缓存) 快速命中 / 未命中 说明: • 虚拟地址被拆分为 4 级索引 (PML4, PDPT, PD, PT) 和 12 位页内偏移。 • 每级页表占用 4KB,包含 512 个 8 字节的页表项 (PTE)。

2.3 用户空间与内核空间划分

Linux 将虚拟地址空间划分为用户空间和内核空间两部分。在 32 位系统中,典型的划分是 3:1(3GB 用户空间 + 1GB 内核空间);在 64 位系统中,划分比例不同,但原理一致。

驱动开发陷阱:用户态程序不能直接访问内核空间地址。如果驱动需要将内核缓冲区共享给用户态,必须通过 mmap 建立映射,或者使用 copy_to_user/copy_from_user 进行数据拷贝。直接传递内核指针给用户态会导致 oops 或安全漏洞。

下表对比了用户空间与内核空间的关键差异:

特性 用户空间 内核空间
访问权限 受限 (Ring 3) 完全 (Ring 0)
地址范围 (32位) 0x00000000 - 0xBFFFFFFF 0xC0000000 - 0xFFFFFFFF
进程隔离 每个进程独立 所有进程共享
直接访问硬件 不允许 允许
缺页处理 可缺页,按需分配 通常不可缺页 (原子上下文)
mmap 映射 可映射文件或设备 提供物理内存页框

下图展示了典型的 Linux 虚拟地址空间布局:

Linux 虚拟地址空间布局 (32位 3:1 划分) 用户空间 (0x00000000 - 0xBFFFFFFF) 3GB 代码段 / 数据段 / BSS 堆 (Heap) / 栈 (Stack) mmap 映射区域 (共享库、设备映射、匿名映射) 分界线 内核空间 (0xC0000000 - 0xFFFFFFFF) 1GB 直接映射区 (ZONE_NORMAL) vmalloc 动态映射区 固定映射区 DMA 缓冲区 / 设备内存映射 (ioremap) 关键点:用户态通过 mmap 将内核空间的 DMA 缓冲区映射到用户空间的 mmap 区域,实现零拷贝共享。

2.4 页表项 (PTE) 与权限控制

每个页表项 (PTE) 不仅包含物理页框号,还包含关键的权限位和状态位,这些位直接决定了 mmap 的行为:

  • Present (P) 位:页面是否在物理内存中。如果为 0,访问将触发缺页异常。
  • Read/Write (R/W) 位:页面是否可写。驱动可以通过 mmap 的 prot 参数控制。
  • User/Supervisor (U/S) 位:用户态是否可以访问。内核空间页面的 U/S 位为 0。
  • Dirty (D) 位:页面是否被写入。DMA 缓冲区通常需要关注此位。
  • Access (A) 位:页面是否被访问过,用于页面回收算法。
mmap 映射的 PTE 设置:当驱动实现 mmap 操作时,内核会调用 remap_pfn_range() 或 io_remap_pfn_range() 来建立页表项。这些函数会设置 PTE 的物理页框号、缓存属性(如 pgprot_noncached)以及用户态访问权限。

2.5 内存区域 (VMA) 与 mmap 的关系

在 Linux 内核中,每个进程的用户空间由多个 VMA (Virtual Memory Area) 描述。VMA 是内核管理虚拟地址空间的基本单位,记录了地址范围、权限、以及对应的文件或设备操作函数。

当用户态调用 mmap() 时,内核会:

  1. 在进程的地址空间中查找一个空闲的虚拟地址区间。
  2. 创建一个新的 VMA 结构,填充起始地址、长度、权限等信息。
  3. 调用文件或设备的 mmap 操作函数(驱动开发者实现)。
  4. 驱动通过 remap_pfn_range() 将物理页框映射到 VMA 的虚拟地址范围。

VMA 与页表的关系如下图所示:

VMA 与页表映射关系 进程 VMA 链表 VMA1: 代码段 (r-x) VMA2: 堆 (rw-) VMA3: mmap 设备 (rw-) ... 更多 VMA 进程页表 PTE 0: 代码页框 PTE 1: 堆页框 PTE 2: 设备 DMA 页框 PTE 3: ... 物理内存 页框 0x1000 页框 0x2000 页框 0x3000 (DMA) 页框 0x4000

2.6 本章小结

理解虚拟地址与物理地址的映射、页表与 MMU 的转换机制、以及用户空间与内核空间的划分,是掌握 mmap 接口和 DMA 缓冲区共享的前提。驱动开发者必须牢记:

  • 用户态只能操作虚拟地址,DMA 只能操作物理地址。
  • mmap 的本质是在用户空间 VMA 与内核物理页框之间建立页表映射。
  • 页表项的权限位(R/W、U/S)决定了共享缓冲区的访问控制。
  • 内核空间与用户空间的严格隔离是系统安全的基础,驱动不能绕过这一层。