3. 内存映射基础:物理地址与虚拟地址、MMU与页表、内存映射区域划分
在嵌入式系统与外设驱动开发中,理解内存映射是进行寄存器配置与性能调优的基石。本章将深入剖析物理地址与虚拟地址的差异、内存管理单元(MMU)与页表的工作机制,以及内存映射区域的划分原则,帮助开发者建立清晰的内存视图。
3.1 物理地址与虚拟地址
物理地址(Physical Address, PA)是硬件系统总线上实际传输的地址信号,直接对应内存芯片或外设寄存器的物理位置。虚拟地址(Virtual Address, VA)则是CPU核心在执行指令时使用的地址,经过MMU转换后才成为物理地址。
| 特性 | 物理地址 (PA) | 虚拟地址 (VA) |
|---|---|---|
| 定义 | 硬件总线上的真实地址 | CPU编程视角的逻辑地址 |
| 空间大小 | 由硬件地址线宽度决定(如32位→4GB) | 由操作系统/架构决定(如32位→4GB,64位→16EB) |
| 连续性 | 物理上可能不连续(存在空洞) | 对进程呈现连续的线性空间 |
| 访问权限 | 无保护机制,任何CPU模式均可访问 | 通过页表实现读/写/执行权限控制 |
| 驱动开发视角 | 外设寄存器必须使用物理地址访问 | 驱动代码通常操作虚拟地址,通过ioremap()映射 |
关键认知: 外设寄存器位于固定的物理地址空间,驱动开发的核心任务之一就是将外设的物理地址区间正确映射到内核虚拟地址空间,才能通过指针或readl/writel等接口进行访问。
3.2 MMU与页表
内存管理单元(Memory Management Unit, MMU)是CPU内部负责地址转换的硬件模块。它接收CPU发出的虚拟地址,通过查询页表(Page Table)将其转换为物理地址。页表是存储在内存中的多级数据结构,每一级表项(Page Table Entry, PTE)包含物理页框号、权限位、缓存属性等关键信息。
MMU核心功能:
- 地址转换: VA → PA 的快速查找与映射。
- 权限检查: 根据页表项中的权限位(R/W/X)阻止非法访问。
- 缓存控制: 通过页表属性控制该页是否可缓存(Cacheable)、是否写透(Write-Through)等。
- 进程隔离: 每个进程拥有独立的页表,实现虚拟地址空间隔离。
3.3 内存映射区域划分
在典型的嵌入式系统(如ARM Cortex-A系列)中,物理地址空间被划分为多个区域,每个区域具有不同的访问属性和用途。驱动开发者必须清楚外设寄存器位于哪个区域,以及如何正确配置MMU页表属性。
3.3.1 主要区域划分
| 区域名称 | 典型地址范围 | 访问属性 | 典型用途 |
|---|---|---|---|
| 外设空间 | 0x4000_0000 – 0x5FFF_FFFF | 非缓存(Device-nGnRnE) | UART、GPIO、I2C、SPI、DMA等外设寄存器 |
| 内存空间 | 0x8000_0000 – 0xBFFF_FFFF | 可缓存(Normal, Write-Back) | DDR SDRAM、SRAM、堆栈、数据段 |
| 系统控制空间 | 0xE000_0000 – 0xEFFF_FFFF | 非缓存(Device-nGRE) | 中断控制器(GIC)、定时器、系统寄存器 |
| ROM/Flash空间 | 0x0000_0000 – 0x0FFF_FFFF | 可缓存(Normal, Read-Only) | Boot ROM、Flash、固件存储 |
驱动开发陷阱: 将外设寄存器区域错误地映射为可缓存(Cacheable)会导致数据一致性问题。例如,CPU读取UART状态寄存器时,如果命中缓存,可能读到过时的状态值,而无法感知新到达的数据。外设空间必须使用非缓存(Device或Strongly-Ordered)属性映射。
3.3.2 外设空间与内存空间的MMU属性差异
MMU页表项中的属性位决定了该地址区域的访问行为。外设空间与内存空间在缓存策略、访问顺序、合并行为上存在本质区别:
- 外设空间(Device Memory):
- 禁止缓存(Non-Cacheable)。
- 严格保序(访问顺序与程序顺序一致)。
- 禁止写合并(Write Combine)和预取。
- 典型属性:Device-nGnRnE(非聚集、非重排、提前写确认)。
- 内存空间(Normal Memory):
- 允许缓存(Cacheable),支持Write-Back或Write-Through策略。
- 允许CPU重排和预取以提高性能。
- 支持写合并和读分配。
- 典型属性:Normal, Inner/Outer Write-Back, Read/Write Allocate。
3.4 驱动开发中的映射实践
在Linux内核中,驱动开发者通过以下API完成物理地址到虚拟地址的映射:
/* 外设寄存器映射:使用ioremap() */
void __iomem *regs;
regs = ioremap(0x4000_0000, 0x1000); // 映射外设空间4KB
if (!regs) {
pr_err("ioremap failed\n");
return -ENOMEM;
}
/* 通过虚拟地址访问寄存器 */
u32 val = readl(regs + 0x10); // 读取偏移0x10的寄存器
writel(0x01, regs + 0x00); // 写入偏移0x00的寄存器
/* 内存空间映射:使用remap_pfn_range()或dma_alloc_coherent() */
/* 用于DMA缓冲区等需要物理连续的内存区域 */
性能调优提示: 对于频繁访问的外设寄存器,可以考虑使用
ioremap_wc()(Write-Combine映射)替代 ioremap(),允许CPU对写操作进行合并,减少总线事务次数。但需确保外设支持写合并语义,且不会因合并导致功能错误。
3.5 本章小结
- 物理地址是硬件真实地址,虚拟地址是CPU编程视角的逻辑地址,MMU负责两者之间的转换。
- 页表是多级数据结构,存储地址映射关系和访问权限,是MMU工作的核心依据。
- 物理地址空间必须按区域划分:外设空间使用非缓存Device属性,内存空间使用可缓存Normal属性。
- 驱动开发中,使用ioremap()系列函数将外设物理地址映射到内核虚拟地址空间,并确保页表属性正确。
- 错误的映射属性(如将外设映射为Cacheable)会导致数据一致性问题,是驱动调试中的常见陷阱。