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;                // 内核虚拟地址(若为低端内存则直接映射)
};
struct page 关键字段说明
字段 用途
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 的区域,需要临时映射才能被内核访问。
三种内存区对比(32 位 x86 典型布局)
内存区 物理地址范围 内核映射方式 主要用途
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)从对应的内存区中分配页框。

物理内存布局与内存区划分 物理内存(4GB 示例) ZONE_DMA 0 ~ 16MB DMA 设备专用 ZONE_NORMAL 16MB ~ 896MB 内核直接映射区 ZONE_HIGHMEM > 896MB 需临时映射 mem_map[] 全局页描述符数组 page[0] page[1] page[2] ...... page[N] 每个 struct page 对应一个页框 通过 PFN 索引访问

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 等标志指定分配来源