mm/page_alloc.c是 Linux 内核内存管理子系统中最核心、最庞大的文件之一,主要负责物理内存页的分配与释放,也就是常说的伙伴系统(Buddy System)的实现。它是所有物理内存分配(如alloc_pages、__get_free_pages、kmalloc的底层)的最终落脚点。
下面按功能模块梳理它的主要职责:
1. 伙伴系统(Buddy System)的核心实现
这是该文件最根本的功能:管理物理内存的页框(page frame),以 2 的幂次为单位进行分配和释放。
__free_one_page():释放一个页块时,检查其"伙伴"页块是否空闲,若空闲则合并成更大的块,并递归向上合并。__rmqueue()/__rmqueue_smallest()/__rmqueue_fallback():从空闲链表中摘取合适大小的页块;当指定迁移类型(migrate type)没有空闲块时,会从其他迁移类型"偷取"(fallback)。expand():当只需要小块但只有大块空闲时,把大块逐级分裂成小块。__find_buddy_pfn():通过 PFN 异或运算快速找到伙伴页框。
空闲页块按order(阶)组织在free_area[order]中,每个 order 下又按迁移类型(MIGRATE_UNMOVABLE / MOVABLE / RECLAIMABLE 等)分成多个链表,以尽量保持内存的连续性、减少碎片。
2. 每 CPU 页框缓存(Per-CPU Pages, PCP)
为了减少对 zone 锁的争用、提高分配速度:
per_cpu_pages/per_cpu_pageset:每个 CPU 每个 zone 维护一个本地页框缓存。rmqueue_pcplist()/rmqueue_bulk():优先从本地缓存分配,缓存空了才批量从伙伴系统补充。free_unref_page()/free_pcppages_bulk():释放时先放入本地缓存,达到高水位再批量归还伙伴系统。相关水位:
pcp->high、pcp->batch、pcp->low。
这是现代内核分配热路径的关键优化。
3. 分配接口(对外 API)
向上层提供多种粒度的物理内存分配函数:
| 函数 | 说明 |
|---|---|
alloc_pages()/alloc_pages_node() | 分配 2^order 个连续物理页,返回struct page * |
__get_free_pages()/__get_free_page() | 返回虚拟地址的分配 |
get_zeroed_page() | 分配并清零一页 |
__alloc_pages()/__alloc_pages_noprof() | 核心分配函数,含完整的慢路径 |
__alloc_pages_slowpath() | 慢路径:唤醒 kswapd、直接回收、内存规整、OOM 等 |
__alloc_pages_direct_compact() | 内存规整(compaction) |
__alloc_pages_direct_reclaim() | 直接回收 |
__alloc_pages_may_oom() | 触发 OOM killer |
GFP 标志(gfp_t)的处理也在这里,例如GFP_KERNEL、GFP_ATOMIC、__GFP_ZERO、__GFP_HIGHMEM等,决定从哪个 zone 分配、能否睡眠/回收。
4. 释放接口
free_pages()/__free_pages():释放页块,最终调用__free_one_page()。free_unref_page()/free_pages_prepare():释放前的检查与准备(如检查 page 引用计数、处理PageAnon、PageDirty等)。__put_page()/put_page():减少引用计数,归零时释放。
5. 内存水位与回收触发
zone_watermark_ok()/zone_watermark_fast():检查 zone 是否达到水位(WMARK_MIN/LOW/HIGH)。__zone_watermark_ok():考虑保留页、迁移类型后的水位判断。wake_all_kswapds():水位不足时唤醒 kswapd 后台回收。should_reclaim_retry()/should_compact_retry():慢路径中的重试策略。
6. 内存热插拔与初始化
free_area_init()/free_area_init_nodes():初始化每个 node/zone 的free_area、zone结构。build_all_zonelists():构建 zone 的分配顺序列表(zonelist)。memmap_init()/memmap_init_range():初始化struct page数组。__init_single_page():初始化单个 page 结构。与
CONFIG_MEMORY_HOTPLUG相关的online_pages()、offline_pages()也在部分版本中涉及。memblock交互:早期内存从 memblock 移交给伙伴系统。
7. 内存碎片化与迁移类型
MIGRATE_UNMOVABLE / MOVABLE / RECLAIMABLE / RESERVE / ISOLATE / CMA等迁移类型的维护。set_pageblock_migratetype()/get_pageblock_migratetype():设置/获取页块迁移类型。__rmqueue_fallback():防止碎片化的关键逻辑,避免破坏大块连续内存。find_suitable_fallback():寻找可回退的迁移类型。与 CMA(连续内存分配器)、
__GFP_CMA的交互。
8. 页块隔离与内存规整配合
alloc_contig_range()/__alloc_contig_migrate_range():分配连续物理内存(用于 CMA、hugetlb 等)。isolate_freepages()/isolate_migratepages()(部分在compaction.c,但入口在 page_alloc 中协调)。has_unmovable_pages()/is_pageblock_removable_nolock():判断页块是否可移动/可移除。
9. 统计与调试
vm_stat//proc/pagetypeinfo//proc/buddyinfo:统计各 order、各迁移类型的空闲页数。__count_zone_vm_events():更新 zone 事件计数。bad_page():检测到损坏的 page 时的报错。dump_page():打印 page 信息用于调试。check_new_page()/free_pages_check():调试模式下检查 page 一致性。setup_per_cpu_pageset():初始化每 CPU 页集。
10. 与其他模块的接口
向SLUB/SLAB(
mm/slub.c)提供底层页块。向vmalloc(
mm/vmalloc.c)提供页表页和物理页。向页表管理(
mm/memory.c)提供pte_alloc所需的页。与kswapd(
mm/vmscan.c)配合做页面回收。与compaction(
mm/compaction.c)配合减少碎片。与CMA(
mm/cma.c)配合预留连续内存。与memory cgroup(
mm/memcontrol.c)配合做内存统计和限制。
总结一句话
mm/page_alloc.c是 Linux物理内存页分配器的实现核心,负责伙伴系统的分配/释放、每 CPU 缓存、内存水位判断、慢路径回收与规整、迁移类型与反碎片、内存热插拔初始化,以及向上层提供alloc_pages/free_pages等基础接口。它是整个内核内存管理的"地基"。