Linux 内核物理内存管理核心机制与代码分析(附面试常问问题)


支持作者新书,点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》


B站讲解视频



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系统》


B站讲解视频


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值