2. 页与区(Zone):页框、页描述符与内存区
在 Linux 内核中,物理内存管理的最小单位是页框(Page Frame),而内核通过页描述符(struct page)来管理每一个页框。为了应对不同硬件(如 DMA 设备)和不同地址空间的限制,内核又将物理内存划分为若干个内存区(Zone)。理解这三者的概念与用途,是掌握内核内存管理的基础。
2.1 页框(Page Frame)
页框是物理内存的固定大小分块,通常为 4KB(在 ARM64 或 x86_64 上也可配置为 16KB 或 64KB)。内核将整个物理内存划分为连续的页框,每个页框都有一个唯一的编号(PFN,Page Frame Number)。
- 大小固定:标准大小为 4KB,与 MMU 的页表粒度一致。
- 物理连续:页框在物理地址上是连续的,但映射到虚拟地址时可以非连续。
- 分配单位:伙伴系统(Buddy System)以页框为单位进行分配和释放。
提示: 在嵌入式系统中,如果内存小于 4GB,通常使用 4KB 页框;对于大内存系统(如服务器),可以使用“大页(Huge Page)”来减少 TLB 缺失,但内核底层管理仍以标准页框为基础。
2.2 页描述符(struct page)
内核为每一个物理页框维护一个 struct page 结构体,该结构体记录了页框的状态、引用计数、映射信息等。所有 struct page 构成一个全局数组 mem_map,通过 PFN 可以快速索引到对应的页描述符。
// include/linux/mm_types.h (简化)
struct page {
unsigned long flags; // 页状态标志(PG_locked, PG_dirty 等)
atomic_t _refcount; // 引用计数
atomic_t _mapcount; // 映射计数(页表映射次数)
unsigned int private; // 私有数据(用于文件页或 slab)
struct address_space *mapping; // 映射的地址空间
pgoff_t index; // 在映射中的偏移
struct list_head lru; // LRU 链表节点(用于页面回收)
void *virtual; // 内核虚拟地址(若为低端内存则直接映射)
};
| 字段 | 用途 |
|---|---|
flags |
描述页框状态,如是否锁定、是否脏、是否正在回写等。 |
_refcount |
引用计数,当为 0 时页框可被回收。 |
_mapcount |
页表映射次数,用于判断是否被多个进程共享。 |
mapping |
指向所属文件或匿名页的 address_space。 |
lru |
用于页面回收算法的 LRU 链表。 |
注意:
struct page 的大小在不同架构上略有差异,但通常为 64 字节。对于 4GB 物理内存(约 1,048,576 个页框),mem_map 数组占用约 64MB 内核内存,这部分开销是固定的。
2.3 内存区(Zone)
由于硬件限制(如 DMA 设备只能访问低 16MB 物理内存)和虚拟地址空间布局(如 32 位系统无法直接映射全部物理内存),内核将物理内存划分为三个主要的内存区:
- ZONE_DMA:用于 DMA 操作的物理内存区,通常位于物理地址低端(如 0~16MB)。
- ZONE_NORMAL:内核直接映射的物理内存区,位于 ZONE_DMA 之上,在 32 位系统中通常为 16MB~896MB。
- ZONE_HIGHMEM:高端内存区,位于物理地址高于 896MB 的区域,需要临时映射才能被内核访问。
| 内存区 | 物理地址范围 | 内核映射方式 | 主要用途 |
|---|---|---|---|
| ZONE_DMA | 0 ~ 16MB | 直接映射(线性映射) | ISA DMA 设备、部分嵌入式 DMA 控制器 |
| ZONE_NORMAL | 16MB ~ 896MB | 直接映射(线性映射) | 内核代码、内核数据结构、普通驱动分配 |
| ZONE_HIGHMEM | > 896MB | 临时映射(kmap/kmap_atomic) | 用户进程页面、大块缓存 |
64 位系统差异: 在 64 位架构(如 x86_64、ARM64)上,由于虚拟地址空间极大(通常 > 128TB),内核可以直接映射所有物理内存,因此 ZONE_HIGHMEM 不存在。此时只有 ZONE_DMA 和 ZONE_NORMAL(或 ZONE_DMA32)。
2.4 内存区与页框的关系
每个页框通过其 struct page 中的 flags 字段或所属的 zone 链表来标识它属于哪个内存区。内核在分配内存时,会根据请求的 gfp_t 标志(如 GFP_DMA、GFP_HIGHMEM)从对应的内存区中分配页框。
2.5 内存区在驱动开发中的实际意义
在编写设备驱动时,正确选择内存区至关重要:
- DMA 缓冲区:必须从
ZONE_DMA分配,使用GFP_DMA标志。例如:kmalloc(size, GFP_KERNEL | GFP_DMA)。 - 普通内核缓冲区:从
ZONE_NORMAL分配,使用GFP_KERNEL或GFP_ATOMIC。 - 大块用户空间缓冲区:在 32 位系统上,如果物理内存超过 896MB,用户空间页面通常来自
ZONE_HIGHMEM,内核需要通过kmap()临时映射才能访问。
最佳实践: 在 64 位系统上开发驱动时,无需关心 ZONE_HIGHMEM,但依然需要为 DMA 缓冲区指定
GFP_DMA 或 GFP_DMA32,以确保分配的物理地址在 DMA 设备的寻址范围内。
2.6 总结
| 概念 | 本质 | 驱动开发关注点 |
|---|---|---|
| 页框(Page Frame) | 物理内存的最小分配单元(4KB) | 分配连续物理页框时使用 alloc_pages() |
| 页描述符(struct page) | 每个页框的元数据结构体 | 通过 virt_to_page() 或 pfn_to_page() 获取 |
| 内存区(Zone) | 按物理地址和用途划分的内存区域 | 使用 GFP_DMA、GFP_HIGHMEM 等标志指定分配来源 |