Linux 内存区域Zone划分详解内存区域Zone是 Linux 内核在物理内存管理中对不同地址范围的逻辑划分。这种划分并非出于性能或功能上的喜好而是由硬件限制如 DMA 寻址能力和内核虚拟地址空间布局决定的。一、为什么要划分 Zone硬件 DMA 的限制一些老旧的 DMA 设备如 ISA 总线设备只能访问物理内存的低端区域例如 0~16MB无法寻址高于某个地址的物理内存。内核必须从特定地址范围内为这些设备分配 DMA 缓冲区。内核虚拟地址映射的局限在 32 位系统中内核的虚拟地址空间通常 1GB 或 2GB有限无法直接永久映射所有物理内存。因此需要将部分物理内存区域如高端内存采用动态映射的方式访问。内存热插拔与 NUMA 的差异在 NUMA 架构中每个节点有自己的本地内存不同节点间的访问延迟不同。Zone 作为 Node 的下级可以进一步细分同一节点内的不同用途内存。二、常见的 Zone 类型不同架构的 Zone 定义略有差异以 x86 为例内核定义了以下几种 Zone按地址从低到高Zone 名称典型地址范围主要用途存在的必要性ZONE_DMA0 ~ 16 MB传统 ISA DMA 设备的缓冲区某些硬件只能访问低于 16MB 的物理内存ZONE_DMA3216 MB ~ 4 GB支持 32 位 DMA 的设备如 PCI在 64 位系统上为只能寻址 32 位的设备准备ZONE_NORMAL16 MB ~ 896 MB (32位) 或 4GB 以上64位内核永久直接映射的物理内存内核代码、核心数据结构、普通内存分配ZONE_HIGHMEM896 MB ~ 物理内存结束仅 32 位用户进程、动态映射的高端内存32 位内核无法直接映射全部物理内存注在64 位系统如 x86_64上内核虚拟地址空间巨大通常为 128TB足以直接映射所有物理内存因此ZONE_HIGHMEM 不存在。同时为了兼容 32 位设备仍然保留 ZONE_DMA 和 ZONE_DMA32。1. ZONE_DMA定义物理内存最低的 16MB 区域旧架构可能是 4MBx86 为 16MB。用途为无法访问全部内存的老式 ISA 设备分配 DMA 缓冲区。这些设备只能使用低端地址做 DMA。分配标志GFP_DMA。2. ZONE_DMA32定义在 64 位系统上低端 4GB 物理地址范围16MB~4GB中除 ZONE_DMA 之外的部分。用途为支持 32 位 DMA 的 PCI 设备分配缓冲区。这些设备可以访问完整的 32 位地址空间0~4GB。分配标志GFP_DMA32。3. ZONE_NORMAL定义内核虚拟地址空间中直接、永久映射的物理内存区域。32 位系统通常为 16MB ~ 896MB数值取决于内核配置如CONFIG_HIGHMEM。64 位系统几乎全部物理内存因为直接映射范围远大于物理内存容量。用途内核自身代码、堆、栈、kmalloc 分配的内存、页表、网络缓冲区等。这是内核最常用的分配区域。分配标志默认无特殊标志或GFP_KERNEL等。4. ZONE_HIGHMEM 仅 32 位定义高于 896MB 的所有物理内存。原因32 位内核虚拟地址空间只有 1GB用户/内核 3:1 分割其中内核部分0xC0000000 开始大小为 1GB。这 1GB 中前 896MB0xC0000000 ~ 0xF7FFFFFF用于直接映射物理地址 - 0xC0000000 即得到虚拟地址。剩余的 128MB 用于映射 HIGHMEM 区域。访问方式不能直接通过指针访问必须使用kmap()/kunmap()建立临时映射。用途主要分配给用户进程因为用户地址空间已经通过页表映射内核需要访问时临时建立映射。分配标志GFP_HIGHMEM。注意在 64 位系统上ZONE_HIGHMEM不存在。所有物理内存都处于ZONE_NORMAL或ZONE_DMA32。三、Zone 与伙伴系统的关系每个 Zone 都拥有自己独立的伙伴系统Buddy System。当内核请求分配物理页框时内存分配器会根据分配标志gfp_mask确定优先从哪个 Zone 开始分配。分配流程简化若指定GFP_DMA优先从ZONE_DMA分配若失败可尝试低 Zone 回退取决于配置。若指定GFP_HIGHMEM优先从ZONE_HIGHMEM分配若失败则回退到ZONE_NORMAL。默认GFP_KERNEL从ZONE_NORMAL分配若失败则尝试ZONE_DMA。回退Fallback顺序内核为每个 Zone 维护一个 fallback 列表当当前 Zone 空闲内存不足时会尝试从更高或更低 Zone 借页。例如在 32 位系统上ZONE_HIGHMEM→ZONE_NORMAL→ZONE_DMAZONE_NORMAL→ZONE_DMAZONE_DMA→ 无回退除非配置了ZONE_DMA32等。四、不同架构的 Zone 差异架构Zone 特点x86_32有 ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM。ZONE_NORMAL 上限通常为 896MB可调。x86_64无 ZONE_HIGHMEM。有 ZONE_DMA (0-16MB), ZONE_DMA32 (16MB-4GB), ZONE_NORMAL (4GB)。ARM32类似 x86_32但 ZONE_NORMAL 上限依赖于内核虚拟地址空间划分如低端内存限制。ARM64类似 x86_64没有 HIGHMEM但有 ZONE_DMA 和 ZONE_DMA32 用于特定设备。RISC-V 64通常只有 ZONE_DMA32 和 ZONE_NORMAL取决于硬件 DMA 能力。五、查看系统 Zone 信息可以通过以下文件查看当前系统的 Zone 划分及使用情况# 查看每个 Zone 的统计信息空闲页、活跃页、水位等cat/proc/zoneinfo# 查看伙伴系统在每个 Zone 中的空闲块分布阶数 0~10cat/proc/buddyinfo# 查看整体内存信息包括 Zone 名称及大小cat/proc/meminfo|grep-idma\|normal\|high/proc/zoneinfo输出示例节选x86_64Node 0, zone DMA pages free 3971 min 128 low 160 high 192 ... Node 0, zone DMA32 pages free 14782 ... Node 0, zone Normal pages free 105624 ...六、Zone 水线与内存回收每个 Zone 定义了三个水位watermarkmin、low、high。min系统保留的“急救”内存低于此值意味着该 Zone 极度紧张。low当空闲页低于此值时内核会唤醒kswapd线程进行异步回收。high异步回收完成后空闲页应达到此水位kswapd休眠。内存分配时如果 Zone 的空闲页低于low且分配标志允许等待则会触发直接回收direct reclaim。七、编程接口GFP 标志与 Zone 选择内核开发者可以通过gfp_mask控制从哪个 Zone 分配内存/* 常用 GFP 标志 */#defineGFP_KERNEL(__GFP_IO|__GFP_FS|__GFP_RECLAIM)// 从 ZONE_NORMAL 分配#defineGFP_USER(__GFP_IO|__GFP_FS|__GFP_HARDWALL)// 用户空间分配#defineGFP_DMA__GFP_DMA// 从 ZONE_DMA 分配#defineGFP_DMA32__GFP_DMA32// 从 ZONE_DMA32 分配#defineGFP_HIGHUSER(__GFP_HIGHMEM|__GFP_USER)// 优先 ZONE_HIGHMEM (32位)实际分配函数如alloc_pages(gfp_mask, order)或kmalloc(size, gfp_mask)会根据标志选择最合适的 Zone。八、常见误区澄清ZONE_NORMAL 并不代表“普通”物理内存而是指能被内核直接映射的部分。在 32 位系统上它可能只是内存的低 896MB而 64 位系统上几乎全部物理内存都是 NORMAL。ZONE_HIGHMEM 并非性能更高或更低只是访问方式不同。因为需要临时映射访问 HIGHMEM 比 NORMAL 多一次页表操作略有开销。在 64 位系统上为什么还有 ZONE_DMA 和 ZONE_DMA32这是为了兼容那些 DMA 寻址能力有限的硬件如某些 PCIe 设备只支持 32 位地址或仅低 16MB 地址。Zone 的地址范围是物理地址不是虚拟地址。九、总结Zone 是物理地址的逻辑划分为了解决 DMA 寻址限制和 32 位内核虚拟地址空间不足的问题。常见 ZoneZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGHMEM。每个 Zone 有独立的伙伴系统和水位线内存分配请求会按照 fallback 顺序尝试不同的 Zone。64 位系统没有 ZONE_HIGHMEM且 ZONE_NORMAL 覆盖绝大部分物理内存。通过/proc/zoneinfo可以查看各 Zone 的详细状态。理解 Zone 划分是深入 Linux 内存管理的关键一步也是分析系统内存压力、调试 DMA 分配失败问题的基础。