3. 物理内存与DMA区域:ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM的概念与用途
在Linux内核中,物理内存被划分为不同的管理区域(Zone),以适应不同硬件设备(尤其是DMA控制器)对物理地址范围的限制。理解这些区域是进行物理内存映射和缓存控制的基础。
3.1 内存区域划分的根源
并非所有物理内存地址都能被所有硬件设备直接访问。主要限制来自:
- ISA DMA限制: 传统的ISA总线DMA控制器只能访问前16MB的物理内存。
- 32位地址空间限制: 在32位系统中,内核虚拟地址空间有限(通常1GB),无法直接映射所有物理内存。
- 外设寻址能力: 某些PCIe设备可能只能访问低于4GB的内存。
为了解决这些限制,内核将物理内存划分为三个主要区域:
| 区域名称 | 物理地址范围 | 主要用途 | 特点 |
|---|---|---|---|
| ZONE_DMA | 0 - 16MB | ISA DMA设备、低端外设 | 地址受限,用于无法访问高位内存的旧设备 |
| ZONE_NORMAL | 16MB - 896MB (32位) | 内核直接映射、常规分配 | 线性映射到内核空间,访问速度最快 |
| ZONE_HIGHMEM | > 896MB (32位) | 用户进程、大块缓存 | 需要临时映射才能访问,64位系统通常不需要 |
关键理解: 在64位系统上,由于内核拥有巨大的虚拟地址空间,ZONE_HIGHMEM通常不存在或大小为零。所有物理内存都可以被直接映射,简化了内存管理。
3.2 各区域详细解析
3.2.1 ZONE_DMA
ZONE_DMA是物理内存中最低的16MB区域。它的存在主要是为了兼容旧的ISA总线设备,这些设备的DMA控制器只能访问0-16MB的物理地址。现代设备(如PCIe)通常支持64位寻址,但内核仍然保留ZONE_DMA以确保兼容性。
- 分配标志:
GFP_DMA - 典型用户: 老式声卡、软盘控制器、某些嵌入式DMA引擎。
- 大小: 通常为16MB,但可以通过内核配置调整。
3.2.2 ZONE_NORMAL
ZONE_NORMAL是内核最常用的内存区域。在32位系统上,它覆盖从16MB到896MB的物理内存。内核通过线性映射(即物理地址与虚拟地址仅差一个固定偏移)直接访问这片区域,因此访问速度最快。
- 分配标志:
GFP_NORMAL(默认) - 典型用户: 内核代码、内核数据结构、驱动分配的缓冲区。
- 特点: 直接映射,TLB命中率高,性能最佳。
3.2.3 ZONE_HIGHMEM
ZONE_HIGHMEM是物理内存中高于896MB的部分(在32位系统上)。由于内核虚拟地址空间只有1GB,无法直接映射所有物理内存,因此高内存区域需要通过临时映射(kmap)来访问。这种映射会消耗TLB条目,且映射数量有限。
- 分配标志:
GFP_HIGHMEM - 典型用户: 用户进程的页面、文件系统缓存。
- 特点: 不能直接访问,需要先建立临时映射,性能略低。
驱动开发注意: 如果你的设备驱动需要分配DMA缓冲区,务必使用
GFP_DMA 或 GFP_DMA32 标志,否则分配的内存可能无法被设备访问,导致数据传输失败。
3.3 内存区域与DMA的关系
下图展示了物理内存区域、内核虚拟地址空间以及DMA设备之间的映射关系:
3.4 区域选择策略
在驱动开发中,选择正确的内存区域至关重要。以下是一些指导原则:
- 优先使用ZONE_NORMAL: 对于大多数内核分配,使用
GFP_KERNEL(默认从ZONE_NORMAL分配)即可。 - DMA缓冲区使用ZONE_DMA: 如果设备只能访问低16MB内存,使用
GFP_DMA。 - 大块分配考虑ZONE_HIGHMEM: 对于大块缓存(如文件系统缓存),使用
GFP_HIGHMEM可以节省ZONE_NORMAL空间。 - 64位系统简化: 在64位系统上,通常只需要关注ZONE_DMA和ZONE_NORMAL,因为ZONE_HIGHMEM不存在。
实战要点: 在编写mmap驱动的物理内存映射时,如果映射的是ZONE_DMA区域的内存,需要确保用户空间映射的虚拟地址与设备DMA引擎使用的物理地址一致。这通常通过
remap_pfn_range() 函数实现,并配合 pgprot_noncached() 设置缓存属性。
3.5 区域与缓存控制的关系
不同内存区域对缓存控制的影响:
- ZONE_DMA: 通常用于DMA操作,需要保证缓存一致性,因此常设置为非缓存(non-cached)或写合并(write-combine)。
- ZONE_NORMAL: 默认启用缓存,用于常规数据访问。但在DMA场景下,需要手动刷新缓存或使用一致性DMA API。
- ZONE_HIGHMEM: 由于通过临时映射访问,缓存行为取决于映射时设置的属性。
理解这些区域是掌握Linux内存管理的第一步,也是后续实现高效mmap映射和DMA传输的基础。