Linux内核内存管理:内存回收水位线机制深度解析

一、内存水位线体系的设计逻辑

物理内存从来都是一项稀缺资源。
内核必须在分配与回收之间建立精密平衡。
Linux内核使用"_水位线"(watermark)_机制实现这一目标。

每条内存zone维护三条水位线:min、low、high。
它们是内存回收子系统的核心阈值。
当空闲页数量低于某条水位线时触发相应动作。

stateDiagram-v2
    [*] --> Normal : 空闲页 > high
    Normal --> kswapd_Active : 空闲页 < low
    kswapd_Active --> Normal : 空闲页 ≥ high
    Normal --> Direct_Reclaim : 空闲页 < min
    kswapd_Active --> Direct_Reclaim : 分配速度 > 回收速度
    Direct_Reclaim --> kswapd_Active : 回收成功(低于high)
    Direct_Reclaim --> OOM_Killer : 回收失败
    OOM_Killer --> [*]

    note right of Normal
        正常分配路径
        __alloc_pages_nodemask
    end note

    note right of kswapd_Active
        后台异步回收
        balance_pgdat()
    end note

    note right of Direct_Reclaim
        同步直接回收
        __perform_reclaim
    end note

水位线的核心设计思想是实现回收与分配的速率匹配。
不追求永不触发回收,而是让回收足够快。
这正是内核设计中的务实哲学。

二、min/low/high三条水位线的数学关系

2.1 初始化计算

水位线在启动时通过setup_per_zone_wmarks()计算。
核心公式建立在min_free_kbytes之上。

/*
 * 内核中水位线计算的核心逻辑
 * 来源: mm/page_alloc.c (简化版本)
 */
static void __setup_per_zone_wmarks(void)
{
    unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
    struct zone *zone;
    int i;

    for_each_zone(zone) {
        u64 tmp;

        /* min水位线由min_free_kbytes决定,按zone大小比例分配 */
        tmp = (pages_min * zone->managed_pages) / total_managed_pages;
        zone->watermark[WMARK_MIN] = tmp;

        /*
         * low水位线 = min * 1.25,提供回收缓冲区间
         * high水位线 = min * 1.5,kswapd回收目标
         * 比例因子确保系统有足够的异步回收余量
         */
        zone->watermark[WMARK_LOW] = tmp * 5 / 4;
        zone->watermark[WMARK_HIGH] = tmp * 3 / 2;

        /* watermark_boost用于内存碎片整理时的临时提升 */
        zone->watermark_boost = 0;
    }
}

2.2 水位线比例关系

默认配置下三条水位线构成固定比例。
min : low : high = 1 : 1.25 : 1.5。
min_free_kbytes通过/proc/sys/vm/min_free_kbytes可调。

2.3 生产级观测模块

以下是可直接运行的观测模块。

/*
 * watermark_monitor.c - 水位线实时观测内核模块
 *
 * 使用方式:
 *   insmod watermark_monitor.ko
 *   观察 dmesg | tail -50 输出
 *   rmmod watermark_monitor
 *
 * 许可: GPL v2
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/mmzone.h>
#include <linux/vmstat.h>
#include <linux/proc_fs.h>

#define WMM_PROC_NAME "watermark_monitor"

static int __init wm_monitor_init(void)
{
    struct zone *zone;
    pg_data_t *pgdat;
    int node_id, zid;

    pr_info("=== 水位线观测模块加载 ===\n");
    pr_info("%-8s %-10s %8s %8s %8s %10s\n",
            "Node", "Zone", "min_kb", "low_kb", "high_kb", "free_kb");

    for_each_online_node(node_id) {
        pgdat = NODE_DATA(node_id);
        for (zid = 0; zid < MAX_NR_ZONES; zid++) {
            zone = &pgdat->node_zones[zid];
            if (!populated_zone(zone))
                continue;

            unsigned long min  = zone->watermark[WMARK_MIN]  << (PAGE_SHIFT - 10);
            unsigned long low  = zone->watermark[WMARK_LOW]  << (PAGE_SHIFT - 10);
            unsigned long high = zone->watermark[WMARK_HIGH] << (PAGE_SHIFT - 10);
            unsigned long free = zone_page_state(zone, NR_FREE_PAGES)
                                          << (PAGE_SHIFT - 10);

            pr_info("%-8d %-10s %8lu %8lu %8lu %10lu\n",
                    node_id, zone->name, min, low, high, free);
        }
    }

    pr_info("=== 水位线比对完成 ===\n");
    return 0;
}

static void __exit wm_monitor_exit(void)
{
    pr_info("水位线观测模块卸载\n");
}

module_init(wm_monitor_init);
module_exit(wm_monitor_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhongyiren");
MODULE_DESCRIPTION("Watermark monitor for Linux memory zones");

三、kswapd守护进程的唤醒与回收策略

3.1 kswapd唤醒条件

每个NUMA节点拥有独立的kswapd线程。
唤醒条件在快速路径分配中判断。

/*
 * 快速路径分配的简化逻辑
 * 展示kswapd唤醒的判断流程
 */
static inline bool need_kswapd_wakeup(struct zone *zone, int order)
{
    unsigned long watermark;
    long free_pages;

    /* 获取对应阶的水位线,高阶分配需要更高的水位 */
    watermark = zone->watermark[WMARK_LOW];
    if (order > 0)
        watermark += (1 << order);

    free_pages = zone_page_state(zone, NR_FREE_PAGES);

    /* 空闲页低于low水位线时唤醒kswapd */
    if (free_pages <= watermark)
        return true;

    return false;
}

kswapd唤醒逻辑已高度适应不同分配阶。
order为0的分配仅需超过low水位线。
高阶分配需要额外空闲页余地。

3.2 kswapd的回收循环

kswapd被唤醒后进入balance_pgdat()。
目标是使所有zone的空闲页都恢复到high水位线以上。
回收过程中持续检查是否达到目标。

kswapd的平衡逻辑非常务实。
它不追求精确的high水位线。
只要所有zone都超过high就停止回收。
这是性能与效果之间的最优平衡点。

四、直接回收的触发时机与工程实践

4.1 直接回收路径

当快速路径分配失败时进入慢速路径。
慢速路径中包含直接回收(direct reclaim)。

/*
 * 慢速路径分配的核心逻辑示意
 * 展示direct reclaim的触发条件
 */
static struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
                       struct alloc_context *ac)
{
    struct page *page = NULL;
    unsigned int alloc_flags;
    bool can_direct_reclaim = gfp_mask & __GFP_DIRECT_RECLAIM;

    if (!can_direct_reclaim)
        goto nopage;

    /* 尝试直接回收内存 */
    page = __alloc_pages_direct_reclaim(gfp_mask, order,
                                        alloc_flags, ac,
                                        &did_some_progress);
    if (page)
        goto got_pg;

    /* 回收未能满足需求时进行二次尝试 */
    page = __alloc_pages_direct_compact(gfp_mask, order,
                                        alloc_flags, ac,
                                        compact_priority, &compact_result);
    if (page)
        goto got_pg;

nopage:
    /* 所有努力失败,尝试OOM killer */
    page = __alloc_pages_may_oom(gfp_mask, order, ac,
                                  &did_some_progress);
got_pg:
    return page;
}

4.2 直接回收的性能影响

直接回收在调用进程的上下文中同步执行。
这意味着应用程序会被阻塞等待回收完成。
这正是水位线设计要极力避免的场景。

生产环境中应密切关注直接回收频率。
可使用以下命令监控:

grep -E "pgsteal_direct|pgscan_direct" /proc/vmstat

pgscan_direct数值持续增长意味着直接回收活跃。
此时应考虑增加min_free_kbytes或添加物理内存。

4.3 调优实践

内存压力下的调优遵循以下原则:
优先增加物理内存,这是根本解决方案。
其次调整min_free_kbytes提升水位线基准。
最后考虑调整vm.swappiness影响回收倾向。

五、总结

水位线机制是Linux内存管理的核心调度器。
三条水位线构成递进式内存压力响应体系。
min水位线是最后防线,触发同步直接回收。
low水位线是预警线,唤醒kswapd异步回收。
high水位线是kswapd的回收停止目标。
水位线比例关系基于min_free_kbytes建立。
内核模块可实时观测各zone的水位线状态。
生产环境中通过vmstat监控直接回收频率。
调优应优先从物理内存和min_free_kbytes入手。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐