2. Linux 内存管理基础:虚拟地址空间、页表、TLB、物理内存与虚拟内存的映射关系

在深入 mmap 驱动实现之前,必须理解 Linux 内存管理的核心抽象层。现代操作系统通过虚拟内存机制,为每个进程提供独立的、连续的地址空间,而底层物理内存则可能是不连续的、甚至被换出到磁盘。本章将拆解这一机制的核心组件:虚拟地址空间、页表、TLB 以及物理内存与虚拟内存的映射关系。

核心观点: 虚拟内存是进程的“幻觉”,页表是“翻译官”,TLB 是“加速缓存”。驱动开发者操作物理内存时,必须通过内核提供的接口(如 mmap、remap_pfn_range)来建立或修改这些映射,而不是直接访问物理地址。

2.1 虚拟地址空间布局

在 32 位系统上,虚拟地址空间为 4GB(0x00000000 ~ 0xFFFFFFFF)。Linux 将其划分为用户空间和内核空间两部分,比例通常为 3:1(用户空间 3GB,内核空间 1GB)。在 64 位系统上,地址空间巨大,但划分原则类似。

区域 地址范围(32位典型值) 权限 内容
用户空间 0x00000000 ~ 0xBFFFFFFF 用户态可读写 代码、数据、堆、栈、共享库
内核空间 0xC0000000 ~ 0xFFFFFFFF 内核态可读写 内核代码、驱动、页表、物理内存直接映射区
驱动开发提示: 用户空间程序无法直接访问内核空间地址。驱动通过 mmap 将内核空间的物理内存或 DMA 缓冲区映射到用户空间,本质是在用户空间的页表中插入新的映射条目,使进程能安全地访问这些物理页面。
虚拟地址空间布局(32位 Linux) 用户空间 (0x00000000 ~ 0xBFFFFFFF) 3GB 代码段 | 数据段 | 堆 | 栈 | 共享库 | mmap 映射区 进程 A 的虚拟地址空间(独立、隔离) 通过页表映射到物理内存 驱动 mmap 将物理内存映射至此区域 分界线:用户态/内核态切换 内核空间 (0xC0000000 ~ 0xFFFFFFFF) 1GB 内核代码 | 驱动 | 页表 | 物理内存直接映射区 (ZONE_NORMAL) 所有进程共享同一内核空间映射

2.2 页表:虚拟地址到物理地址的转换

页表是操作系统维护的一种数据结构,用于将虚拟地址转换为物理地址。Linux 采用多级页表结构(如 4 级页表:PGD、PUD、PMD、PTE),以节省内存并支持大地址空间。

  • 页框(Page Frame): 物理内存被划分为固定大小的块,通常为 4KB。
  • 页(Page): 虚拟地址空间也被划分为同样大小的块。
  • 页表项(PTE): 每个虚拟页对应一个页表项,记录其映射的物理页框号及权限属性。
页表层级 名称 作用
PGD Page Global Directory 顶级目录,每个进程一个
PUD Page Upper Directory 第二级目录(64位系统使用)
PMD Page Middle Directory 第三级目录
PTE Page Table Entry 最终页表项,包含物理页框号和标志位
性能陷阱: 每次内存访问都需要经过多级页表查询,这会导致多次内存访问开销。TLB(Translation Lookaside Buffer)正是为了解决这一性能瓶颈而设计的硬件缓存。
多级页表地址转换流程 虚拟地址 PGD PUD PMD PTE 物理页 地址转换过程 CPU 发出虚拟地址 → 硬件遍历 PGD → PUD → PMD → PTE → 获取物理页框号 每级页表都从内存中读取,TLB 缓存最近使用的转换结果以加速

2.3 TLB:地址转换的硬件缓存

TLB(Translation Lookaside Buffer)是 CPU 内部的一个小型、高速的硬件缓存,用于存储最近使用的虚拟地址到物理地址的映射关系。当 CPU 访问内存时,首先在 TLB 中查找;如果命中,则直接获得物理地址,无需访问内存中的页表。

  • TLB 命中: 转换速度极快(通常 1 个 CPU 周期),性能最优。
  • TLB 未命中: 硬件需要遍历多级页表(页表遍历),导致数十甚至数百个周期的延迟。
  • TLB 刷新: 当页表内容被修改(如建立新的 mmap 映射、解除映射、页面换入换出)时,必须使对应的 TLB 条目失效,否则 CPU 会使用过时的缓存映射。
驱动关键操作: 在 mmap 驱动中,当通过 remap_pfn_range 或 io_remap_pfn_range 建立新的物理内存映射后,内核会自动处理 TLB 的刷新。但在某些高级场景(如直接修改页表、使用 kmap 等),驱动开发者可能需要手动调用 flush_tlb_page 或 flush_tlb_mm 来确保 TLB 一致性。
TLB 地址转换流程 CPU 虚拟地址 TLB 缓存 {VA:PA} 映射表 命中? 命中 直接获取物理地址 未命中 遍历内存页表 TLB 命中:1 周期延迟;TLB 未命中:数十到数百周期延迟 驱动建立新映射后,内核自动刷新相关 TLB 条目

2.4 物理内存与虚拟内存的映射关系

物理内存是硬件实际提供的 RAM,而虚拟内存是操作系统为每个进程提供的抽象。两者通过页表建立映射。一个物理页框可以被多个进程的虚拟页同时映射(共享内存),也可以被一个进程独占。

概念 说明 驱动中的体现
物理页框 物理内存的最小管理单元(4KB) 通过 __pa() 将内核虚拟地址转为物理地址
虚拟页 进程地址空间中的连续区域 通过 mmap 系统调用创建
页表项 记录虚拟页到物理页框的映射关系 通过 remap_pfn_range 建立
缺页异常 访问未映射的虚拟页时触发 驱动可通过 vm_fault 处理自定义缺页
mmap 的本质: 驱动中的 mmap 实现,就是在进程的虚拟地址空间中分配一段虚拟地址区域(VMA),然后通过 remap_pfn_range 将这段虚拟区域与指定的物理页框建立页表映射。此后,进程访问该虚拟地址时,CPU 通过页表直接找到物理内存,无需经过内核的 read/write 系统调用。
物理内存与虚拟内存映射关系 进程 A 虚拟空间 虚拟页 VA1 虚拟页 VA2 虚拟页 VA3 虚拟页 VA4 进程 B 虚拟空间 虚拟页 VB1 虚拟页 VB2 虚拟页 VB3 物理内存 物理页框 PFN1 物理页框 PFN2 物理页框 PFN3 物理页框 PFN4 红色虚线:进程 A 独占映射;橙色虚线:进程 B 与进程 A 共享物理页框(共享内存 / mmap)

2.5 驱动开发中的关键数据结构

在编写 mmap 驱动时,以下内核数据结构是必须掌握的:

  • struct vm_area_struct (VMA): 描述进程虚拟地址空间中一个连续区域的属性,包括起始地址、结束地址、权限标志、以及关联的 vm_ops 操作函数集。驱动通过 mmap 回调函数接收并操作 VMA。
  • struct page: 描述一个物理页框的内核结构体。驱动通过 virt_to_page() 或 pfn_to_page() 获取物理页框对应的 struct page 指针。
  • pgprot_t: 页表项的保护标志位,用于控制缓存策略(如是否启用 Cache、Write-Combine 等)。驱动通过 pgprot_noncached()、pgprot_writecombine() 等宏修改这些标志。
mmap 驱动核心流程:
  1. 用户进程调用 mmap() 系统调用。
  2. 内核创建新的 VMA,并调用驱动的 mmap 文件操作回调。
  3. 驱动调用 remap_pfn_range(),将 VMA 的虚拟地址范围映射到指定的物理页框。
  4. 驱动通过 vma->vm_page_prot 设置缓存策略(如禁用 Cache 以支持 DMA)。
  5. 用户进程直接读写映射后的虚拟地址,CPU 通过页表直接访问物理内存。