支持作者新书,点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》
Linux 内核物理内存管理核心机制与代码分析(附面试常问问题)
一、为什么要理解内核内存分配与回收机制?
问题引入:
- 内核内存分配失败时,应该怎么看日志、分析原因?
- 内存碎片化如何影响系统稳定性?
- 伙伴算法和回收、碎片整理各自的职责与联系是什么?
- 面试中常被问到的
__alloc_pages()
、zone_watermark_ok()
、rmqueue_buddy()
都是怎么协作的?
二、Linux 内核内存分配的总体架构
Linux 内核为了高效、安全地分配和管理物理内存,设计了层次化、模块化的内存分配与回收体系。其核心包括:
-
物理页分配主流程
- 入口函数:
__alloc_pages()
- 主要职责:响应内核、内核模块、用户空间分配请求
- 入口函数:
-
水位线判断机制
- 关键函数:
zone_watermark_ok()
- 主要职责:根据预先设定的阈值(min/low/high),判断当前 zone 是否可以安全分配内存
- 关键函数:
-
伙伴系统算法(Buddy System)
- 关键函数:
rmqueue_buddy()
、__rmqueue()
、expand()
- 主要职责:高效分配和合并物理页块,减少碎片
- 关键函数:
-
回收机制
- 关键函数:
kswapd()
、try_to_free_pages()
、shrink_node()
- 主要职责:回收不活跃/不再需要的内存页,增加可用空闲页
- 关键函数:
-
内存碎片整理(Compaction)
- 关键函数:
compact_zone()
- 主要职责:将分散的小块空闲页整理为更大连续块,提高高阶分配成功率
- 关键函数:
三、物理页分配主流程源码解读
1. 入口函数 __alloc_pages()
所有内存分配的主入口就是 mm/page_alloc.c
里的 __alloc_pages()
。核心代码结构如下:
struct page *__alloc_pages(gfp_t gfp, unsigned int order, int preferred_nid, nodemask_t *nodemask)
{
...
// Fast path
page = get_page_from_freelist(...);
if (likely(page))
goto out;
// Slow path
page = __alloc_pages_slowpath(...);
out:
...
return page;
}
面试常问:__alloc_pages()分配流程?
答:先快速分配,再慢速(回收等),失败则分配失败。
快速分配(Fast Path)
- 调用 get_page_from_freelist():
优先在本地/优选 zone 的空闲页列表中直接分配,流程极短,分配性能最高。
慢速分配(Slow Path)
- 调用 __alloc_pages_slowpath():
如果快速分配失败,进入慢速流程,可能会唤醒内存回收线程(kswapd)、直接回收页、内存整理等复杂操作,最终再次尝试分配。
2. get_page_from_freelist()
分配流程进入 get_page_from_freelist()
,在这里先进行 水位线判断:
if (!zone_watermark_ok(zone, order, mark, ac.classzone_idx, alloc_flags, pfmemalloc_allowed)) {
// 水位不够,不能分配
continue;
}
page = rmqueue(ac->preferred_zoneref->zone, zone, order, gfp_mask, alloc_flags, migratetype);
问题:什么情况下 get_page_from_freelist 能分配?
答:只有当 zone 空闲页大于水位线(通常是 low 或 min),才允许分配。
四、水位线判断机制源码解读
1. 为什么要有水位线?
水位线本质是内存分配的安全阈值,防止过度分配导致底层硬件、内核关键功能失去可用内存,从而保障系统稳定。
- min:最低保障,低于此线一般不允许分配,除非特殊分配(如 OOM killer)。
- low:回收触发线,低于此线分配允许,但异步唤醒回收线程。
- high:目标安全线,回收目标是达到此线。
2. 核心函数 zone_watermark_ok()
bool zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark,
int classzone_idx, unsigned long alloc_flags, long free_pages)
{
long min = mark;
// 计算可用空闲页,包括本地和高区
long free = zone_page_state(z, NR_FREE_PAGES) - (1 << order) + 1;
if (free < min)
return false;
return true;
}
问题:zone_watermark_ok 起什么作用?
答:判断分配前 zone 空闲页是否满足要求,不满足则不允许分配、需先回收。
五、伙伴算法分配核心逻辑与代码
1. 伙伴算法原理简介
- 所有空闲物理页以 2 的幂次方(阶)组织,每阶有空闲块链表。
- 分配时:从目标阶寻找空闲块,没有则拆分更高阶块。
- 释放时:检测伙伴块是否空闲,如果是则自动合并为更大块,递归进行。
2. 关键函数 rmqueue_buddy()
struct page *rmqueue_buddy(struct zone *preferred_zone, struct zone *zone,
unsigned int order, unsigned int alloc_flags, int migratetype)
{
...
if (!page) {
page = __rmqueue(zone, order, migratetype, alloc_flags);
if (!page) {
...
return NULL;
}
}
__mod_zone_freepage_state(zone, -(1 << order), get_pcppage_migratetype(page));
...
return page;
}
- rmqueue_buddy:伙伴系统分配主入口。
- __rmqueue:在指定 zone、指定阶、指定迁移类型下分配物理块,必要时递归拆分大块。
- expand(在 __rmqueue_smallest/expand 调用链):将大块按需求拆成小块,剩余部分回收进低阶链表。
3. 伙伴算法与分配路径关系
- get_page_from_freelist → rmqueue → rmqueue_buddy → __rmqueue → expand
六、回收机制与碎片整理机制源码解读
1. 回收机制
-
核心目标:释放不活跃/可回收的物理页,补充空闲页池。
-
后台回收线程(kswapd)
- 驻留内核线程,异步扫描并回收页。
-
分配路径直接回收(try_to_free_pages)
- 分配流程中若水位线低于 min,会阻塞直接回收。
关键代码(mm/vmscan.c):
int try_to_free_pages(struct zonelist *zonelist, int order, gfp_t gfp_mask, struct mem_cgroup *memcg)
{
...
shrink_node(...)
...
}
- shrink_node:分区扫描,LRU 链表优先回收 inactive、file cache、匿名页等。
- shrink_page_list:真正回收页,将释放页交给伙伴算法合并。
2. 碎片整理(Compaction)
常见问题:大块内存分配失败怎么办?
碎片严重时,即使总空闲内存充足,也可能分配不到高阶块。此时需要碎片整理:
- compact_zone()(mm/compaction.c):扫描 zone,将已用页搬向一端,将空闲页压向另一端,实现合并。
compact_zone(zone, ...);
调用时机会在分配大块失败(如 huge page、CMA 分配失败)时被触发。
七、内核调试常用接口与实际问题分析
1. /proc/zoneinfo 与 /proc/buddyinfo
/proc/zoneinfo
:各 zone 的空闲页分布、水位线参数、CMA 预留、活跃/非活跃页等。/proc/buddyinfo
:各阶空闲页块分布,碎片化情况一目了然。
2. 日志分析
-
sysrq
+m
打印内存信息,快速判断当前内存健康状况。 -
典型输出看点:
- 活跃/非活跃/缓存/脏页分布
- 各 zone free/managed/spanned
- buddyinfo 是否高阶页全为 0(碎片严重)
3. 代码调试技巧
- 源码中适当插入
pr_info()
或trace_mm_page_alloc
,动态观察分配和回收路径。 - 利用 cscope/ctags/VSCode 的“跳转定义/引用”梳理分配与回收调用链。
八、面试常见问题与标准解答总结
1. 伙伴算法的优势与不足?
- 优势:合并/拆分高效,分配和释放 O(logN),低碎片。
- 不足:仍可能产生外部碎片,极端场景下高阶分配失败。
2. 水位线在内存分配中的作用?
- 答:保护 zone 安全,防止关键区域耗尽,决定是否分配或触发回收。
3. 回收与伙伴算法关系?
- 答:回收释放可回收页,伙伴算法合并入空闲链表,为后续分配准备。
4. 为什么需要内存碎片整理机制?
- 答:碎片导致大块分配失败,整理能提升连续大块分配能力,特别对 hugepage、CMA 需求很大。
5. 内存分配失败常见原因及排查思路?
- 答:free 总量不足、高阶 buddyinfo 全为 0、CMA 被占满、swap 不足、内存泄漏、LRU 列表长时间未回收。
九、结语与夯实建议
- 学习内核内存管理,关键要能结合代码理解主线、跟踪调用关系,并用实际工具和日志印证理解。
- 建议画出分配-判断-回收-整理的流程图/思维导图,加深记忆。
- 推荐实操练习:尝试内核分配大块内存(如 hugepage)、制造碎片后观察系统回收与 compaction 行为。
系统掌握这套内存分配、回收、碎片整理机制及其源码实现,不仅能在面试中脱颖而出,也为日常内核开发与问题排查打下坚实基础。
支持作者新书,点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》