2. 物理内存管理基础:页框、内存节点与内存域
物理内存是 Linux 内核最核心的资源之一。内核必须高效地管理物理内存的分配与回收,同时要兼顾不同硬件架构(如 NUMA、UMA)和不同用途(如 DMA、普通映射、内核映射)的差异。本章深入剖析物理内存管理的三个基本抽象:页框(Page Frame)、内存节点(Node) 与 内存域(Zone),并介绍它们在内核中的数据结构与组织关系。
2.1 页框(Page Frame)——物理内存的最小单位
物理内存被划分为固定大小的连续块,称为页框(Page Frame)。每个页框对应一个物理页,其大小通常为 4KB(在 ARM64 或 x86_64 上可配置为 16KB 或 64KB)。内核使用 struct page 结构体来描述每一个页框。
struct page 核心字段
| 字段 | 类型 | 说明 |
|---|---|---|
flags |
unsigned long | 页框状态标志(如 PG_locked、PG_dirty、PG_swapbacked) |
_refcount |
atomic_t | 引用计数,决定页框是否可被回收 |
mapping |
struct address_space * | 指向该页框所属的地址空间(文件页或匿名页) |
lru |
struct list_head | 用于 LRU 链表(活跃/不活跃页链表) |
private |
unsigned long | 私有数据,用于 slab 或文件系统元数据 |
slab_cache |
struct kmem_cache * | 若页框被 slab 分配器管理,指向所属的 slab 缓存 |
struct page 实例,所有实例存储在全局数组 mem_map 中。通过 virt_to_page() 或 pfn_to_page() 宏可以从虚拟地址或页框号(PFN)快速获取对应的 struct page。
2.2 内存节点(Node)——NUMA 架构下的顶层划分
在 NUMA(非统一内存访问)系统中,CPU 访问本地内存的速度远快于访问远程内存。内核将物理内存划分为多个内存节点(Node),每个节点对应一个 CPU 或一组 CPU 的本地内存。在 UMA(统一内存访问)系统中,只有一个内存节点。
每个内存节点由 struct pglist_data(简称 pg_data_t)描述,节点之间通过链表串联。
struct pglist_data 核心字段
| 字段 | 说明 |
|---|---|
node_zones[] |
该节点包含的内存域数组(ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM 等) |
node_zonelists[] |
备用内存域列表,用于跨域/跨节点分配时的回退顺序 |
nr_zones |
该节点实际使用的内存域数量 |
node_start_pfn |
该节点起始物理页框号 |
node_spanned_pages |
该节点覆盖的物理页框总数(含空洞) |
node_present_pages |
该节点实际可用的物理页框数 |
pgdat_list |
链表指针,将所有节点串联起来 |
2.3 内存域(Zone)——按用途划分的内存区域
每个内存节点内部,物理内存根据用途和地址范围进一步划分为多个内存域(Zone)。常见的内存域包括:
- ZONE_DMA / ZONE_DMA32:用于 DMA 操作的物理内存,地址范围受限(如 x86 上 ZONE_DMA 为 0~16MB)。
- ZONE_NORMAL:内核直接映射的物理内存,线性映射到内核虚拟地址空间。
- ZONE_HIGHMEM:仅存在于 32 位系统,物理地址高于内核线性映射范围的区域,需要临时映射才能访问。
- ZONE_MOVABLE:可迁移页的集合,用于内存热插拔和防碎片。
- ZONE_DEVICE:用于持久内存或设备内存。
每个内存域由 struct zone 描述,其中包含 Buddy 分配器的核心数据结构——空闲链表数组(free_area[])。
struct zone 核心字段
| 字段 | 说明 |
|---|---|
free_area[MAX_ORDER] |
Buddy 分配器的空闲链表数组,每个元素管理 2^order 个连续页框 |
pageset[] |
每 CPU 页框缓存,用于减少锁竞争 |
zone_start_pfn |
该内存域起始页框号 |
spanned_pages |
该内存域覆盖的页框总数 |
present_pages |
该内存域实际可用的页框数 |
watermark[] |
水位线(WMARK_MIN、WMARK_LOW、WMARK_HIGH),用于控制内存回收触发时机 |
zone_pgdat |
指向所属的内存节点 |
2.4 三层结构关系图
下图展示了物理内存管理的三层抽象:Node → Zone → Page Frame,以及 Buddy 分配器在 Zone 层如何管理空闲页框。
2.5 内存域水位线与分配回退路径
每个内存域维护三个水位线(Watermark),用于控制内存分配与回收的触发时机:
- WMARK_MIN:最低水位。空闲内存低于此值时,分配器必须阻塞等待内存回收。
- WMARK_LOW:低水位。空闲内存低于此值时,内核唤醒 kswapd 进行异步回收。
- WMARK_HIGH:高水位。空闲内存高于此值时,kswapd 可以休眠。
当在指定内存域分配失败时,内核会按照 node_zonelists 中定义的备用顺序依次尝试其他内存域,甚至跨节点回退。回退策略分为两种:
| 策略 | 说明 |
|---|---|
| ZONELIST_FALLBACK | 优先在同一节点内回退到其他 Zone(如 ZONE_NORMAL → ZONE_DMA),再跨节点 |
| NODE_FALLBACK | 优先跨节点回退到相同类型的 Zone,再考虑同一节点内的其他 Zone |
__GFP_THISNODE 标志强制分配器只在当前节点分配,避免性能下降。
2.6 页框分配与释放的完整路径
下图展示了从分配请求到最终获取物理页框的完整路径,涉及 Node、Zone、Buddy 三层协作。
2.7 关键数据结构关系总结
| 抽象层 | 内核数据结构 | 核心职责 | 关联分配器 |
|---|---|---|---|
| 页框 | struct page |
描述单个物理页框的状态、引用计数、映射关系 | Buddy、Slab |
| 内存域 | struct zone |
管理特定用途的内存区域,维护空闲链表和水位线 | Buddy |
| 内存节点 | struct pglist_data |
管理 NUMA 节点内的所有内存域,提供备用分配路径 | Buddy |
/proc/zoneinfo 查看每个 Zone 的空闲页框数和水位线;通过 /proc/buddyinfo 查看 Buddy 分配器的空闲链表分布;通过 numactl --hardware 查看 NUMA 节点拓扑。