2. Linux 内存管理概述:页表、TLB、内存区域与物理内存分配器

在编写 mmap 驱动之前,必须理解 Linux 内核如何管理物理内存和虚拟内存。本章将梳理四个核心概念:页表(Page Table)、TLB(Translation Lookaside Buffer)、内存区域(ZONE_DMA / ZONE_NORMAL / ZONE_HIGHMEM)以及物理内存分配器(Buddy System)。这些知识是理解 mmap 如何建立用户空间到物理内存直接映射的基础。

2.1 虚拟地址与物理地址的桥梁:页表

页表是 CPU 内存管理单元(MMU)用于将虚拟地址(VA)转换为物理地址(PA)的数据结构。每个进程拥有独立的页表,实现进程隔离。

  • 页框(Page Frame):物理内存被划分为固定大小的块,通常为 4KB。
  • 页(Page):虚拟地址空间也被划分为同样大小的块。
  • 页表项(PTE):页表中的每一行,记录虚拟页到物理页框的映射关系、权限位(读/写/执行)、存在位等。
关键点: 在 ARM64 或 x86_64 架构中,页表通常采用多级结构(如 4 级页表:PGD → PUD → PMD → PTE),以节省内存。驱动开发中,我们常通过 remap_pfn_range() 或 io_remap_pfn_range() 直接操作页表,建立用户虚拟地址到物理页框的映射。
驱动提示: 在 mmap 回调中,内核提供 struct vm_area_struct(VMA)描述用户请求的虚拟地址区间。驱动通过修改该 VMA 对应的页表,实现物理内存的直接映射。
页表:虚拟地址到物理地址的转换 虚拟地址 (VA) MMU + 页表 物理地址 (PA) 多级页表示例(x86_64 4级页表) PGD PUD PMD PTE 物理页框

2.2 TLB:地址转换的加速缓存

TLB(Translation Lookaside Buffer)是 CPU 内部的一个硬件缓存,用于存储最近使用的虚拟地址到物理地址的转换结果。每次地址转换时,MMU 优先查询 TLB,若命中则无需遍历多级页表,大幅提升性能。

特性 TLB 命中 TLB 未命中
访问延迟 1-2 个 CPU 周期 数十到数百个周期(需遍历页表)
对驱动的影响 mmap 映射后访问速度快 首次访问或上下文切换后可能触发
刷新时机 进程切换、修改页表后需 TLB 刷新 —
驱动注意事项: 在 mmap 回调中建立新映射后,内核会自动处理 TLB 刷新。但如果驱动在运行时动态修改了已映射物理页的权限或缓存属性(例如通过 set_memory_uc()),必须显式调用 flush_tlb_kernel_range() 或类似接口,否则可能导致缓存一致性问题。

2.3 物理内存区域:ZONE_DMA / ZONE_NORMAL / ZONE_HIGHMEM

Linux 内核将物理内存划分为不同的区域(Zone),以适应不同硬件设备的 DMA 能力和 CPU 寻址范围。

  • ZONE_DMA:低端物理内存区域(通常 < 16MB),用于需要 DMA 访问的旧设备。在 x86 上,ISA 设备只能访问此区域。
  • ZONE_NORMAL:内核直接映射区域(线性映射),物理地址与虚拟地址存在固定偏移。大多数内核内存分配来自此区域。
  • ZONE_HIGHMEM:高端内存区域(仅 32 位系统),物理地址高于内核线性映射范围,需要临时映射才能访问。64 位系统通常不需要此区域。
物理内存区域布局(32 位系统示例) ZONE_DMA (0 - 16MB) ZONE_NORMAL (16MB - 896MB) ZONE_HIGHMEM (896MB - 4GB) 64 位系统:线性地址空间足够大,通常只有 ZONE_DMA 和 ZONE_NORMAL
mmap 映射区域选择: 驱动在分配用于 mmap 的物理内存时,通常使用 kmalloc()(来自 ZONE_NORMAL)或 __get_free_pages()。如果需要 DMA 缓冲区,应使用 dma_alloc_coherent(),它会自动从合适的区域分配。在 32 位系统上,mmap 高端内存需要额外处理。

2.4 物理内存分配器:Buddy System

Buddy System(伙伴系统)是 Linux 内核管理物理页框的核心算法。它以 2 的幂次方(order)为单位管理连续物理页,解决外部碎片问题。

  • 基本单位:一个页框(通常 4KB),order 0 表示 1 页,order 1 表示 2 页,order 2 表示 4 页,依此类推。
  • 分配:从满足需求的最小 order 链表中分配。若该链表为空,则从更高 order 链表分裂出两个“伙伴”。
  • 释放:释放时检查其伙伴是否空闲,若是则合并回更高 order,减少碎片。
Order 连续页数 大小 典型用途
0 1 4 KB 小块内存、进程页表
1 2 8 KB 小数据结构
3 8 32 KB DMA 缓冲区
5 32 128 KB 帧缓冲、网络包
8 256 1 MB 大块连续内存
Buddy System:分配与合并示例 order 3 (8 页, 32 KB) order 2 (4 页) — 分配 order 2 (4 页) — 空闲 合并回 order 3 (8 页, 32 KB) 伙伴合并条件:两个块互为伙伴且都空闲,且 order 相同
驱动编程接口: 驱动中常用 alloc_pages(gfp_mask, order) 或 __get_free_pages(gfp_mask, order) 从 Buddy System 分配连续物理页。例如,分配 4 页(16KB)连续内存:unsigned long addr = __get_free_pages(GFP_KERNEL, 2);。释放时使用 free_pages(addr, 2)。

2.5 本章小结