一、 引言从虚拟到物理的桥梁在前一部分中我们探讨了虚拟内存如何为进程构建独立的地址空间。然而无论虚拟地址多么庞大最终都必须落实到真实的物理内存条DRAM上。物理内存管理Physical Memory Management是Linux内核中最基础、最底层的子系统之一它负责回答一个核心问题如何高效、公平且无碎片地分配和回收宝贵的物理页帧Page Frames物理内存管理与虚拟内存管理形成鲜明的分工虚拟内存管理关心“看到什么”地址空间布局、权限、映射关系。物理内存管理关心“有什么可用”物理页的分配、释放、统计、碎片整理。本部分将深入剖析Linux内核如何组织TB级的物理内存如何通过精巧的算法满足微秒级的分配请求以及如何适应从嵌入式设备到超级计算机的多样化硬件架构。二、 物理内存的层级组织节点、区域与页Linux并不将物理内存视为一块平坦的连续空间。为了适配现代硬件特性如多路处理器、NUMA、DMA限制它采用了分层的组织结构。2.1 NUMA节点Node非统一内存访问的抽象现代服务器普遍采用NUMANon-Uniform Memory Access架构。在一个多路系统中每个CPU插槽Socket拥有本地连接的物理内存访问本地内存速度快访问远端其他CPU插槽内存速度慢。数据结构pg_data_t内核使用struct pglist_data定义于include/linux/mmzone.h来描述一个NUMA节点。每个节点包含node_zones[]节点内包含的各个内存区域Zones。node_mem_map指向该节点所有物理页描述符struct page的数组。node_size节点的总页数。kswapd该节点的内存回收守护进程。策略内核遵循“本地分配优先”原则尽量从当前运行CPU所在的节点分配内存以减少跨节点访问的延迟。2.2 内存区域Zone划分用途的桶即使在一个节点内内存也不能随意使用。由于历史遗留问题和硬件限制内核将每个节点的内存划分为不同的区域Zones。主要区域类型x86_64为例ZONE_DMA0-16MB。保留给旧式ISA设备进行DMA传输这些设备只能寻址低16MB。ZONE_DMA3216MB-4GB。供只能寻址32位地址的设备使用。ZONE_NORMAL4GB以上。常规内核直接映射区大部分内核和用户内存从此分配。(注32位系统有ZONE_HIGHMEM用于映射高端物理内存64位系统通常不再需要)为什么需要分区如果不分区一个偶然的DMA分配请求可能会耗尽低端内存导致内核无法分配用于关键数据结构如页表本身的低端内存引发系统崩溃。分区确保了每种用途都有专用的储备。2.3 物理页描述符struct page物理内存的最小管理单位是页帧Page Frame通常是4KB。内核使用struct page定义于include/linux/mm_types.h来追踪每一个物理页的状态。关键字段flags页的状态标志如脏页、活跃页、锁定的页。_refcount引用计数。为0时表示空闲。mapping指向该页所属的地址空间如页缓存或匿名映射。private用于文件系统或驱动程序的私有数据。lru链表头。用于将该页链接到LRU链表或伙伴系统的空闲链表中。内存开销struct page本身也占用内存。对于TB级内存系统页描述符可能占用数百MB这也是为什么需要精简page结构的原因。三、 伙伴系统Buddy System抗碎片的基石伙伴系统是Linux物理内存管理的核心算法由Knowlton于1965年提出。它的核心使命是在频繁的分配与释放后尽可能地减少外部碎片External Fragmentation并保证分配速度。3.1 算法原理幂次分配与伙伴合并伙伴系统将空闲内存划分为11个MAX_ORDER不同大小的组Order。每个组包含大小为2order个连续页的块。Order 01页4KBOrder 12页8KB...Order 101024页4MB核心操作分配当请求N页时寻找满足2kN的最小阶数k。如果k阶链表为空则拆分k1阶的一个块一分为二一半分配另一半放入k阶链表。释放当释放一个块时检查它的“伙伴”相邻且大小相同的块是否空闲。如果空闲则合并成一个大一级的块并递归检查上一级伙伴。“伙伴”的定义两个块物理地址连续且大小相同。它们的地址二进制只有第order12位不同。3.2 数据结构free_area每个Zone都有自己的伙伴系统状态存储在struct zone的free_area[MAX_ORDER]数组中。struct free_area { struct list_head free_list[MIGRATE_TYPES]; // 空闲链表数组 unsigned long nr_free; // 空闲块总数 };(注MIGRATE_TYPES是反碎片技术下文详述)3.3 分配接口__alloc_pages内核最核心的分配函数是__alloc_pages()定义于mm/page_alloc.c。它接受两个关键参数gfp_mask分配标志Get Free Pages mask。例如GFP_KERNEL可休眠、GFP_ATOMIC原子上下文不可休眠、GFP_HIGHUSER用户空间。order申请的阶数。分配路径首选Zone根据gfp_mask确定从哪个Zone分配如DMA请求必须去ZONE_DMA。水印检查检查Zone的空闲页是否高于最低水位线min。低于min则不分配触发直接回收。伙伴查找在对应order的链表中查找空闲块。四、 反碎片技术迁移类型与每CPU缓存原始的伙伴系统容易产生碎片尤其是长期运行的系统。Linux引入了两项关键技术来解决这一问题。4.1 迁移类型Migrate Types内核将页块分为不同的“迁移类型”目的是将性质相似的页聚集在一起防止不可移动的页如内核数据结构夹在两个可移动页如用户数据中间阻碍大块连续内存的形成。主要类型MIGRATE_UNMOVABLE内核核心数据结构不可移动。MIGRATE_MOVABLE用户空间页、页缓存可以移动用于压缩或碎片整理。MIGRATE_RECLAIMABLE可回收页如文件系统元数据。MIGRATE_CMA连续内存分配器用于嵌入式媒体驱动。效果伙伴系统的每个Order现在都有多条链表按迁移类型分。分配时优先匹配类型找不到时可“偷”其他类型的块但尽量避免破坏不可移动块的连续性。4.2 每CPU页框缓存Per-CPU Page Frame Cache, PCP伙伴系统的操作需要加锁在高并发场景下会成为瓶颈。为了优化单页Order-0的分配速度内核引入了PCP。机制每个CPU维护一个私有缓存pcplist预存了一些单页。分配单页时直接从本地CPU缓存获取无需加锁。释放单页时先放回本地缓存。只有当本地缓存空了或满了才去访问全局的伙伴系统。热页与冷页Hot Cold PagesPCP缓存区分“热页”数据可能在Cache中和“冷页”数据不在Cache中。分配用于DMA输出的页通常希望是冷页以避免冲刷CPU缓存。五、 水位线与内存回收压力内核不能等到内存完全耗尽才行动。它通过一套水位线机制来监控内存压力。5.1 水位线等级每个Zone维护三个水位线WatermarksWMARK_MIN (Low)保留给紧急情况的页池。低于此线分配将触发直接回收Direct Reclaim。WMARK_LOW (High)正常回收的警戒线。内核唤醒kswapd开始后台回收。WMARK_HIGH (Max)理想状态的上限。kswapd努力维持在此线之上。5.2 分配器行为逻辑if (free_pages WMARK_MIN) { // 紧急立即同步回收内存可能阻塞 page __alloc_pages_slowpath(...); } else if (free_pages WMARK_LOW) { // 紧张唤醒kswapd异步回收 wakeup_kswapd(); page __alloc_pages_fast(...); } else { // 充足直接分配 page get_page_from_freelist(...); }六、 架构图物理内存管理全景下图描绘了从NUMA节点到底层物理页的完整组织架构┌─────────────────────────────────────────────────────────────────┐ │ 系统物理内存 (System Physical RAM) │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ NUMA Node 0 │ │ NUMA Node 1 │ ... (More Nodes) │ │ │ (pglist_data)│ │ │ │ │ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Zone DMA │ │ Zone DMA │ │ │ │ (pages) │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Zone Normal │ │ Zone Normal │ │ │ │ (pages) │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 伙伴系统 (Buddy Allocator) │ │ │ │ Order: 0 1 2 ... 10 (MAX_ORDER) │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ │ │List │ │List │ │List │ │List │ │ │ │ │ │(4K) │ │(8K) │ │(16K)│ │(4M) │ │ │ │ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ │ │Page │ │Page │ │Page │ │Page │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ │ │ └───────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ 每CPU缓存 (Per-CPU Cache) │ │ │ CPU0: [PCP List (Hot/Cold)] │ │ │ CPU1: [PCP List (Hot/Cold)] │ │ │ ... │ │ └─────────────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ 物理页元数据 (struct page array) │ │ │ [page0][page1][page2] ... [pageN] (One per physical page) │ │ └─────────────────────────────────────────────────────────────┘图解说明顶层系统由多个NUMA节点组成。中层每个节点划分为多个Zone。Zone内部的核心是伙伴系统按Order组织空闲链表。底层每个物理页对应一个struct page。同时为了加速单页分配每个CPU维护着私有的PCP缓存。七、 高级话题内存热插拔与气球驱动7.1 内存热插拔Memory Hotplug现代服务器支持在不关机的情况下添加或移除物理内存板DIMM。上线Online新内存被添加到ZONE_MOVABLE区域为了避免碎片新内存通常标记为可移动并合并到伙伴系统中。下线Offline内核必须将目标范围内的所有页迁移到其他区域然后将其从伙伴系统中移除。这在云环境中用于替换故障内存条。7.2 虚拟化气球驱动Balloon Driver在虚拟机VM中Hypervisor无法直接知道Guest OS哪些页是空闲的。气球驱动通过以下方式“回收”内存Hypervisor想回收内存通知气球驱动。气球驱动在Guest OS内申请大量内存“充气”迫使Guest OS进行内存回收。这些被占用的页实际上被传递给Hypervisor另作他用。当Guest内存富余时气球驱动释放内存“放气”。八、 性能优化与考量/proc/buddyinfo查看伙伴系统各Order的空闲情况。如果高阶大块长期为0说明内存碎片化严重。/proc/pagetypeinfo查看迁移类型的分布诊断反碎片机制的效果。大页预分配对于数据库等需要大块连续内存的应用建议预先配置HugeTLB避免运行时伙伴系统因碎片无法分配大页。NUMA策略使用numactl绑定进程避免跨节点访问带来的高延迟。九、 小结第三部分揭示了Linux物理内存管理的精密架构分层治理通过Node、Zone、Page三级结构适配复杂硬件。算法核心伙伴系统通过幂次拆分合并对抗碎片。性能加速PCP缓存化解锁竞争迁移类型提升连续性。动态调控水位线触发回收维持系统平衡。物理内存管理为上层提供了alloc_pages这一基石接口。在第四部分中我们将目光投向更细粒度的内存管理——Slab分配器与内核对象缓存看看内核如何高效管理比4KB小得多的数据结构。