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

在嵌入式Linux驱动开发中,理解内存管理是掌握mmap与DMA缓冲区映射的前提。本章从硬件视角出发,系统梳理虚拟地址与物理地址的映射关系、MMU与页表的工作机制,以及内核空间与用户空间的划分原则。

核心认知: 驱动开发者操作的是虚拟地址,而硬件设备(如DMA控制器)直接访问物理地址。理解两者之间的转换桥梁——MMU与页表,是正确实现缓冲区映射的关键。

2.1 虚拟地址与物理地址

在启用MMU的系统中,CPU发出的地址并非直接送往内存总线,而是先经过MMU转换为物理地址。这种间接寻址机制带来了隔离、保护和灵活映射三大优势。

地址类型 定义 访问者 特点
虚拟地址 (VA) CPU程序看到的地址空间 用户进程、内核线程 连续、独立、可隔离
物理地址 (PA) 内存硬件单元的真实地址 MMU、DMA控制器、总线 离散、全局、受硬件限制
总线地址 (BA) 外设总线视角的地址 DMA引擎、PCIe设备 可能与PA相同或经过IOMMU转换
驱动开发提示: 在DMA操作中,驱动程序必须使用物理地址或总线地址来配置DMA控制器。使用virt_to_phys()和dma_map_single()等API完成地址转换。
虚拟地址 → 物理地址映射关系 虚拟地址空间 进程A: 0x0000-0xFFFF 进程B: 0x0000-0xFFFF 内核: 0xC000-0xFFFF 每个进程拥有独立VA空间 MMU 页表查询 TLB加速 物理地址空间 页框0: 0x00000 页框1: 0x01000 页框N: 0xNNNNN 全局共享、离散分布 VA PA 关键点:同一物理页框可映射到多个进程的虚拟地址空间(共享内存),不同进程的相同VA可指向不同PA(隔离)

2.2 页表与MMU

MMU(Memory Management Unit)是CPU内部负责地址转换的硬件单元,其核心数据结构是页表(Page Table)。页表将虚拟地址划分为固定大小的页(Page),映射到物理内存的页框(Page Frame)。

2.2.1 页表层级结构

现代ARM/Linux系统采用多级页表(通常为2~4级),以节省页表占用的物理内存。以ARM64的4级页表为例:

层级 名称 粒度 覆盖范围
L0 PGD (Page Global Directory) 512GB 全局目录
L1 P4D (Page 4th Directory) 1GB 四级目录
L2 PUD (Page Upper Directory) 2MB 上级目录
L3 PMD (Page Middle Directory) 2MB 中间目录
L4 PTE (Page Table Entry) 4KB 页表项
性能注意: 多级页表查询会引入多次内存访问开销。TLB(Translation Lookaside Buffer)作为页表的硬件缓存,可缓存最近使用的VA→PA映射,命中率通常超过99%。驱动开发中频繁切换地址空间(如context switch)会导致TLB刷新,影响实时性。
ARM64 4级页表地址转换流程 虚拟地址 (64位) [未使用位] | [L0索引:9位] | [L1索引:9位] | [L2索引:9位] | [L3索引:9位] | [页内偏移:12位] L0 PGD 全局目录 L1 P4D 四级目录 L2 PUD 上级目录 L3 PMD 中间目录 L4 PTE 页表项 物理页框 TLB (快表) 缓存最近VA→PA映射 TLB命中时跳过页表遍历 每级页表占用一个物理页框(4KB),仅存储下一级页表的物理地址

2.2.2 页表项属性

每个PTE不仅包含物理页框号(PFN),还包含关键的权限和缓存属性位:

  • Present (P):页是否在物理内存中(缺页异常时清0)
  • Read/Write (R/W):读写权限控制
  • User/Supervisor (U/S):用户态/内核态访问权限
  • Cache Disable (CD):禁止缓存(DMA缓冲区常用)
  • Write-Through (WT):写透缓存策略
  • Access/Dirty (A/D):由硬件维护的访问和脏页标志
驱动应用: 在实现mmap时,驱动程序通过remap_pfn_range()设置PTE属性,将内核物理页框映射到用户空间。对于DMA缓冲区,通常设置Cache Disable或Write-Combine属性,避免缓存一致性问题。

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

Linux将4GB(32位)或更大的虚拟地址空间划分为两个部分:用户空间和内核空间。划分比例由CONFIG_PAGE_OFFSET决定,典型配置如下:

架构 用户空间范围 内核空间范围 划分比例
ARM32 (3G/1G) 0x00000000 - 0xBFFFFFFF 0xC0000000 - 0xFFFFFFFF 3GB / 1GB
ARM32 (2G/2G) 0x00000000 - 0x7FFFFFFF 0x80000000 - 0xFFFFFFFF 2GB / 2GB
ARM64 0x0000000000000000 - 0x0000FFFFFFFFFFFF 0xFFFF000000000000 - 0xFFFFFFFFFFFFFFFF 48位 / 48位

2.3.1 内核空间布局

内核空间进一步划分为多个逻辑区域,每个区域具有不同的映射属性和用途:

  • 直接映射区(ZONE_NORMAL/DMA):物理内存线性映射到内核虚拟地址空间,virt_to_phys()可直接转换
  • vmalloc区:用于非连续内存分配,虚拟地址连续但物理地址不连续
  • 固定映射区(Fixmap):用于特殊用途(如APIC、IO空间)的固定虚拟地址
  • 模块区:用于加载内核模块
  • 高端内存区(HighMem):32位系统中访问超过1GB物理内存的临时映射区
ARM32 虚拟地址空间划分 (3G/1G) 用户空间 (0x00000000 - 0xBFFFFFFF) 每个进程独立,通过MMU隔离 代码段 | 数据段 | 堆 | 栈 | 共享库 | mmap区域 用户态不可访问内核空间 PAGE_OFFSET = 0xC0000000 内核空间 (0xC0000000 - 0xFFFFFFFF) 直接映射区 (ZONE_NORMAL/DMA) 线性映射,virt_to_phys直接转换 vmalloc区 非连续映射 模块区 内核模块加载 固定映射区 IO空间、APIC等 高端内存区 (HighMem) 临时映射 >1GB物理内存 内核空间所有进程共享,用户态通过系统调用进入内核态访问

2.4 驱动开发中的内存操作要点

在驱动开发中,理解上述内存管理机制有助于正确选择内存分配和映射API:

场景 推荐API 内存区域 物理连续性
小尺寸DMA缓冲区 (< 4KB) dma_alloc_coherent() ZONE_DMA 连续
大尺寸DMA缓冲区 dma_alloc_coherent() 或 __get_free_pages() ZONE_NORMAL 连续
非连续DMA缓冲区 dma_map_sg() + sg_table 任意区域 可离散
用户态mmap映射 remap_pfn_range() 内核物理页→用户VA 连续物理页框
内核虚拟地址转换 virt_to_phys() / phys_to_virt() 直接映射区 线性关系
常见陷阱:
  • 不要对vmalloc返回的地址使用virt_to_phys(),vmalloc区没有线性映射关系
  • DMA缓冲区必须保证物理地址连续(除非使用SG模式),否则DMA控制器无法正确传输
  • 用户态mmap映射时,必须确保映射的物理页框在映射期间不会被释放或重用
  • ARM架构下DMA缓冲区需要设置PTE的Cache Disable位,否则CPU缓存与DMA数据不一致

2.5 本章小结

本章建立了嵌入式Linux驱动开发所需的内存管理知识框架:

  • 虚拟地址与物理地址通过MMU和页表建立映射,驱动开发者必须清楚两者之间的转换关系
  • 多级页表在节省内存的同时引入查询开销,TLB是关键的硬件加速器
  • 内核空间与用户空间的划分决定了驱动代码的访问权限和地址范围
  • 驱动中的内存分配和映射API选择取决于缓冲区大小、连续性要求和DMA操作需求