内存管理-40-watermark内存水位

基于msm-5.4

模块内调用路径:

postcore_initcall //page_alloc.c 【】内核初始化
    init_per_zone_wmark_min //page_alloc.c
/proc/sys/vm/extra_free_kbytes //【】sysctl节点配置。
/proc/sys/vm/min_free_kbytes //【】sysctl节点配置,会同时更新 user_min_free_kbytes。
    min_free_kbytes_sysctl_handler //page_alloc.c
/proc/sys/vm/watermark_scale_factor //【】sysctl节点配置。
    watermark_scale_factor_sysctl_handler //page_alloc.c
        setup_per_zone_wmarks //page_alloc.c
            __setup_per_zone_wmarks //page_alloc.c
                zone->_watermark[WMARK_MIN] = tmp;
                zone->watermark_boost = 0;
                zone->_watermark[WMARK_LOW]  = min_wmark_pages(zone) + low + tmp;
                zone->_watermark[WMARK_HIGH] = min_wmark_pages(zone) + low + tmp * 2;


一、概述

1. Linux内核内存水位(watermark)是用于管理内存分配和回收的关键机制。水位分为三个层次:min、low和high。用于判断系统当前的内存压力状态,并决定是否需要进行内存回收。

min水位:

当系统剩余的空闲内存(free memory)降到min水位以下时,表明内存非常紧张。在这种情况下,内存分配请求会被阻塞,直到内存回收完成,以防止发生OOM(Out of Memory)错误。min水位是一个临界值,低于这个值时,内存分配器会同步等待内存回收,即触发direct reclaim。

low水位:

当系统剩余的空闲内存降到low水位以下但仍在min水位以上时,表明内存面临一定的压力。在这种情况下,内核会唤醒kswapd线程进行异步内存回收,以逐步恢复空闲内存。low水位是一个警戒值,低于这个值时,kswapd会被唤醒,但内存分配请求不会被阻塞,也就是是异步回收。

high水位:

当系统剩余的空闲内存恢复到high水位以上时,表明内存压力已经缓解。kswapd线程会停止内存回收操作,系统恢复正常运行。high水位是一个安全值,内存回收的目标是将空闲内存恢复到这个水平。


2. 内存水位的实际应用

避免性能下降:

通过合理设置内存水位,可以避免因内存不足而导致的性能下降。
例如,在内存紧张时,提前唤醒 kswapd 进行内存回收,可以减少直接内存回收(direct reclaim)的频率,从而降低进程的内存分配延迟。

提高系统稳定性:

内存水位机制确保系统在内存不足时能够及时采取措施,避免OOM错误的发生。通过调整水位值,可以平衡内存使用和系统性能,提高系统的整体稳定性。

适应不同业务场景:

对于不同的业务场景,可以通过调整内存水位来优化内存管理。例如,对于需要大量缓存的业务,可以适当提高low水位,以更早地进行内存回收,保持更多的空闲内存。


二、相关数据结构

1. struct zone

struct zone {
    unsigned long _watermark[NR_WMARK]; //3
    unsigned long watermark_boost;
    ...
};

enum zone_watermarks { //include/linux/mmzone.h
    WMARK_MIN, //0
    WMARK_LOW, //1
    WMARK_HIGH, //2
    NR_WMARK //3
};

#define min_wmark_pages(z) (z->_watermark[WMARK_MIN] + z->watermark_boost)
#define low_wmark_pages(z) (z->_watermark[WMARK_LOW] + z->watermark_boost)
#define high_wmark_pages(z) (z->_watermark[WMARK_HIGH] + z->watermark_boost)
#define wmark_pages(z, i) (z->_watermark[i] + z->watermark_boost)

cat /proc/zoneinfo 中可以看到各个zone的水位值,打印的min/low/high值分别是 min_wmark_pages(z)/low_wmark_pages(z)/ high_wmark_pages(z) 的值,单位是pages.

成员介绍:

1.1 对 zone->watermark_boost 的赋值:

online_pages //memory_hotplug.c 【】内存热插路径更新
__offline_pages //memory_hotplug.c 【】内存热拔路径更新
postcore_initcall //【】内核启动后初始化
    init_per_zone_wmark_min //page_alloc.c
        setup_per_zone_wmarks //page_alloc.c
            __setup_per_zone_wmarks
                zone->watermark_boost = 0; //初始化为0

__rmqueue //page_alloc.c
    __rmqueue_fallback //page_alloc.c
        steal_suitable_fallback //page_alloc.c
            boost_watermark //page_alloc.c
                zone->watermark_boost = min(zone->watermark_boost + pageblock_nr_pages, max_boost);

module_init(kswapd_init)
    kswapd_init //vmscan.c
        kswapd_run //vmscan.c
            pgdat->kswapd = kthread_run(kswapd, pgdat, "kswapd%d:0", nid) //vmscan.c
                kswapd //vmscan.c
                    balance_pgdat //vmscan.c
                        zone->watermark_boost -= min(zone->watermark_boost, zone_boosts[i]);

zone->watermark_boost 初始化为0,看来其值应该是临时赋值的。推测其作用应该是临时或动态地增加某个zone的水位线值,以便在特定情况下更好地控制内存分配和回收。例如,当系统检测到内存压力较大时,可能会临时提高 WMARK_LOW 或 WMARK_HIGH 的值,以减少 kswapd 的唤醒频率或避免直接回收的发生。这种动态调整可以在系统负载变化时提供更灵活的内存管理策略,从而优化系统性能。

在某些特定的场景下,如高负载的应用启动或大量内存分配请求时,系统可能会使用 zone->watermark_boost 来临时增加水位线,防止内存不足导致的性能下降。这种机制可以帮助系统在短时间内更好地应对突发的内存需求,避免不必要的内存回收操作。

从对 zone->watermark_boost 的使用上看,主要是在判断内存水位和kswapd内存回收时使用。


三、水位更新逻辑

1. init_per_zone_wmark_min()

其调用路径见前文。

int __meminit init_per_zone_wmark_min(void) //page_alloc.c
{
    unsigned long lowmem_kbytes;
    int new_min_free_kbytes;

    /* 计算此ZONE_DMA和ZONE_NORMAL中超出high水线的页面数之和,由于此时high水位线还没初始化,因此等于各zone的页面数之和 */
    lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);
    /* 只是对数值开平方,不对单位KB开平方,结果还取KB为单位 */
    new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16);
    /* user_min_free_kbytes 默认是-1,但是通过sysctl更新 min_free_kbytes 时也会更新它 */
    if (new_min_free_kbytes > user_min_free_kbytes) {
        /* 系统让 min_free_kbytes 的初始化值介于128k~64M之间,但是之后通过sysctl接口设置就没这个限制 */
        min_free_kbytes = new_min_free_kbytes;
        if (min_free_kbytes < 128)
            min_free_kbytes = 128;
        if (min_free_kbytes > 65536)
            min_free_kbytes = 65536;
    } else {
        pr_warn("min_free_kbytes is not updated to %d because user defined value %d is preferred\n",
                new_min_free_kbytes, user_min_free_kbytes);
    }
    /* 计算每个zone的min、low、high水位值,其中min是按比例分配给各个zone的,low和high是在min的基础上加delta值得到。*/
    setup_per_zone_wmarks();
    /* 根据处理器数量和每个zone的内存量来计算阈值 */
    refresh_zone_stat_thresholds();
    /* 初始化 lowmem_reserve, 见前面博客 */
    setup_per_zone_lowmem_reserve();

    return 0;
}


1.1 setup_per_zone_wmarks()

void setup_per_zone_wmarks(void)
{
    /* 静态spinlock进行保护,确保串行设置 */
    static DEFINE_SPINLOCK(lock);

    spin_lock(&lock);
    __setup_per_zone_wmarks();-
    spin_unlock(&lock);
}


1.1.1 __setup_per_zone_wmarks()

static void __setup_per_zone_wmarks(void) //page_alloc.c
{
    /* 将KB转换为page单位,min_free_kbytes 初始化时系统计算出来的值,也可以通过sysctl文件进行设置 */
    unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
    /* extra_free_kbytes 默认是0,可通过sysctl文件进行配置 */
    unsigned long pages_low = extra_free_kbytes >> (PAGE_SHIFT - 10);
    unsigned long lowmem_pages = 0;
    struct zone *zone;
    unsigned long flags;

    /* 统计除了ZONE_HIGHMEM这个zone外,buddy管理的总内存页数 */
    for_each_zone(zone) {
        if (!is_highmem(zone))
            lowmem_pages += zone_managed_pages(zone); //zone->managed_pages
    }

    /* 针对每个zone设置min low high水位线 */
    for_each_zone(zone) {
        u64 tmp, low;
        spin_lock_irqsave(&zone->lock, flags);
        /* 每个zone按其managed_pages的页数等比例划分pages_min #### */
        tmp = (u64)pages_min * zone_managed_pages(zone);
        do_div(tmp, lowmem_pages);
        /* vm_total_pages 表示所有zones中高于high水位的页面数之和,与min相比,相当于分母变小了 */
        low = (u64)pages_low * zone_managed_pages(zone);
        do_div(low, vm_total_pages); //
        /* 64位机器上不会有highmem区域,因此不考虑该情况 */
        if (is_highmem(zone)) {
            ...
        } else {
            /* 对于低端内存,将每个zone等比例划分的值直接赋值给 _watermark[MIN] */
            zone->_watermark[WMARK_MIN] = tmp;
        }

        /*
         * mult_frac(x, y, z) = x * y / z
         *
         * watermark_scale_factor默认是10, 可通过sysctl设置, 其主要控制high/low/min之间的delta值,其值越大delta就越大。
         */
        tmp = max_t(u64, tmp >> 2, mult_frac(zone_managed_pages(zone), watermark_scale_factor, 10000));

        /*
         * 每次设置之前都现将 watermark_boost 清0了,tmp是high/low/min之间的delta值, 
         * 若 extra_free_kbytes 没有指定则low=0, 此时若 watermark_scale_factor 的值较少,则low=5/4*min, high=6/4*min.
         */
        zone->watermark_boost = 0;
        zone->_watermark[WMARK_LOW]  = min_wmark_pages(zone) + low + tmp;
        zone->_watermark[WMARK_HIGH] = min_wmark_pages(zone) + low + tmp * 2;

        spin_unlock_irqrestore(&zone->lock, flags);
    }

    /*
     * 设置完内存水位线后,会更新 totalreserve_pages 的值,这个值用于评估系统正常运行时需要使
     * 用的内存,该值会作用于overcommit时,判断当前是否允许此次内存分配。见《内存管理-39-lowmem_reserve低端预留内存》
     */
    calculate_totalreserve_pages();
}


1.2 refresh_zone_stat_thresholds()

void refresh_zone_stat_thresholds(void)
{
    struct pglist_data *pgdat;
    struct zone *zone;
    int cpu;
    int threshold;

    /* 先将每cpu变量 pgdat->per_cpu_nodestats 清0 */
    for_each_online_pgdat(pgdat) {
        for_each_online_cpu(cpu) {
            per_cpu_ptr(pgdat->per_cpu_nodestats, cpu)->stat_threshold = 0;
        }
    }

    /* 对每一个有实际物理页面的zone,  */
    for_each_populated_zone(zone) {
        struct pglist_data *pgdat = zone->zone_pgdat;
        unsigned long max_drift, tolerate_drift;

        /* 根据处理器数量和每个区域的内存量来计算阈值。内存多意味着可以将更新推迟更长时间,处理器越多则会导致更多争用。*/
        threshold = calculate_normal_threshold(zone);

        for_each_online_cpu(cpu) {
            int pgdat_threshold;

            per_cpu_ptr(zone->pageset, cpu)->stat_threshold = threshold;

            /* Base nodestat threshold on the largest populated zone. */
            pgdat_threshold = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu)->stat_threshold;
            per_cpu_ptr(pgdat->per_cpu_nodestats, cpu)->stat_threshold = max(threshold, pgdat_threshold);
        }

        /*
         * 翻译: 仅当存在 NR_FREE_PAGES 报告低水位线正常但实际上分配可能突破最低水位线的危险时,才设置 percpu_drift_mark
         */
        tolerate_drift = low_wmark_pages(zone) - min_wmark_pages(zone);
        max_drift = num_online_cpus() * threshold;
        if (max_drift > tolerate_drift)
            zone->percpu_drift_mark = high_wmark_pages(zone) + max_drift;
    }
}


1.2.1 refresh_zone_stat_thresholds()的作用?

此函数用于更新zone的状态阈值。这些阈值主要用于内存管理中的各种决策,如内存分配、回收等。


1.2.2 zone->percpu_drift_mark 的作用?

zone->percpu_drift_mark 用于管理 zone 中的 per-CPU 页分配器(per-CPU pageset)。具体作用如下:

(1) 防止 per-CPU 页分配器的偏差过大:

在多处理器系统中,每个 CPU 都有一个自己的 per-CPU pageset,用于快速分配和回收内存页。这些 per-CPU pageset 会在内存紧张时与全局的 zone 页池进行同步。

percpu_drift_mark 用于监控 per-CPU pageset 中的页数量与全局 zone 页池中的页数量之间的偏差。当 per-CPU pageset 中的页数量超过 percpu_drift_mark 时,内核会采取措施减少这种偏差,例如将多余的页归还给全局 zone 页池。

(2) 确保内存分配的公平性和一致性:

通过设置 percpu_drift_mark,内核可以确保每个 CPU 的 per-CPU pageset 不会占用过多的内存资源,从而避免某些 CPU 占用大量内存而其他 CPU 无页可用的情况。

这有助于维持系统整体的内存分配公平性和一致性,特别是在高并发和多任务环境中。

(3) 优化内存分配性能:

per-CPU pageset 的设计目的是提高内存分配的性能,因为 CPU 可以快速访问本地的内存页,而不需要跨 CPU 访问全局 zone 页池。

percpu_drift_mark 通过限制 per-CPU pageset 中的页数量,可以防止因过度积累而导致的性能下降。当 per-CPU pageset 中的页数量接近 percpu_drift_mark 时,内核会及时调整,确保内存分配的高效性。

(4) 动态调整:

percpu_drift_mark 的值可以根据系统的内存状况动态调整。例如,当系统内存紧张时,可以减小 percpu_drift_mark 的值,迫使 per-CPU pageset 更快地归还页给全局 zone 页池。

这种动态调整机制有助于内核更好地应对不同负载下的内存需求,提高系统的整体稳定性。


四、使用逻辑

1. zone_watermark_fast()

static inline bool zone_watermark_fast(struct zone *z, unsigned int order,
        unsigned long mark, int classzone_idx, unsigned int alloc_flags, gfp_t gfp_mask) //page_alloc.c
{
    /* 本zone的空闲页面个数 */
    long free_pages = zone_page_state(z, NR_FREE_PAGES); //zone->vm_stat[NR_FREE_PAGES]
    long cma_pages = 0;

#ifdef CONFIG_CMA
    /* 如果分配不能使用 CMA 区域,则不要使用空闲的 CMA 页面 */
    if (!(alloc_flags & ALLOC_CMA))
        cma_pages = zone_page_state(z, NR_FREE_CMA_PAGES); //zone->vm_stat[NR_FREE_CMA_PAGES]
#endif

    /*
     * 翻译: 仅针对 0 阶(分配单个页面)进行快速检查。如果失败,则需要考虑预留内存。有一种极端情况,
     * 即检查通过但只有高order原子预留是空闲的。如果调用者是 !atomic 的,那么它将毫无意义地搜索空
     * 闲列表。这种极端情况会比较慢,但无害。
     *
     * 这是针对0阶分配的快速检查。若不允许分配CMA内存还要从空闲页中剔除cma page,然后和水位+预留进
     * 行相比。这说明CMA内存也是算在free_pages里面的。
     */
    if (!order && (free_pages - cma_pages) > mark + z->lowmem_reserve[classzone_idx])
        return true;

    /* 下面就是非0阶分配或空闲页面数量低于测试水位的情况了 */

    if (__zone_watermark_ok(z, order, mark, classzone_idx, alloc_flags, free_pages))
        return true;
    /*
     * Ignore watermark boosting for GFP_ATOMIC order-0 allocations
     * when checking the min watermark. The min watermark is the
     * point where boosting is ignored so that kswapd is woken up
     * when below the low watermark.
     * 翻译:检查min水位时,忽略 GFP_ATOMIC order-0 分配的水位boost。min水位是忽略boost的点,
     * 因此当低于low水位时,kswapd 会被唤醒。
     *
     * 分配最初传入的是 ALLOC_WMARK_LOW。
     *
     * 对于0阶原子分配,且有水位boost,且分配标志是WMARK_MIN,将水位调整到min水位,然后重新测试.
     */
    if (unlikely(!order && (gfp_mask & __GFP_ATOMIC) && z->watermark_boost && ((alloc_flags & ALLOC_WMARK_MASK) == WMARK_MIN))) {
        mark = z->_watermark[WMARK_MIN];
        return __zone_watermark_ok(z, order, mark, classzone_idx, alloc_flags, free_pages);
    }

    return false;
}


1.1 __zone_watermark_ok()

/* zone_watermark_fast: (z, order, mark, classzone_idx, alloc_flags, free_pages) */
bool __zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark,
             int classzone_idx, unsigned int alloc_flags, long free_pages) //page_alloc.c
{
    long min = mark;
    int o;
    /* 若有这两个标志,则表示需要尽力分配 */
    const bool alloc_harder = (alloc_flags & (ALLOC_HARDER|ALLOC_OOM));

    /* 检查分配出去2^order个page之后的free pages是否满足水位mark的要求(free_pages 可能会变成负数 - 没关系) */
    free_pages -= (1 << order) - 1;

    /* ALLOC_HIGH==__GFP_HIGH,针对高优先级的分配,则击穿水位,将水线值降低一半 */
    if (alloc_flags & ALLOC_HIGH)
        min -= min / 2; //降低至原来的1/2

    /*
     * 翻译: 如果调用者没有 ALLOC_HARDER 权限,则从空闲页面中减去高原子预留。这将高估原子预留的大小,但可以
     * 避免搜索。
     *
     * 只有设置了ALLOC_HARDER,才能从 free_list[MIGRATE_HIGHATOMIC]的链表中进行页面分配,否则减去。
     *
     * 若此次分配请求没有alloc_harder权限,则空闲页的数量减去高端预留内存; 若有alloc_harder权限,则继续往下击穿水位:
     * ALLOC_HARDER: min是原来的3/8
     * ALLOC_OOM: min是原来的2/8
     */
    if (likely(!alloc_harder)) {
        /* 这里是没有设置alloc_harder的情况 */
        free_pages -= z->nr_reserved_highatomic;
    } else {
        /*
         * 翻译: OOM 受害者可以比普通的 ALLOC_HARDER 用户更加努力,因为它肯定会很快进入退出路径并释放内存。
         * 它在释放路径期间进行的任何分配都将很小且短暂。
         */
        if (alloc_flags & ALLOC_OOM)
            min -= min / 2; //降低至原来的1/4
        else
            min -= min / 4; //降低至原来的3/8
    }

#ifdef CONFIG_CMA
    /* 若此次分配不能使用CMA内存,还要从空闲页面中减去CMA页面 */
    if (!(alloc_flags & ALLOC_CMA))
        free_pages -= zone_page_state(z, NR_FREE_CMA_PAGES);
#endif

    /*
     * 翻译: 检查 0 阶分配请求的水位。如果不满足这些条件,那么高阶请求也无法继续,即使恰好有合适的页面可用。
     *
     * 如果空闲页面free pages已经小于等于预留内存和限制水位之和了,说明此次分配请求不满足wartmark要求,返回false
     */
    if (free_pages <= min + z->lowmem_reserve[classzone_idx])
        return false;

    /* 若没有触及水位限制,且是0阶分配,返回true. 0阶的分配请求就不用担心没有大块内存供使用了 */
    if (!order)
        return true;

    /*
     * 翻译: 对于高阶请求,从其order开始,检查至少一个合适的页面块是可用的。
     *
     * 对于高阶分配请求,虽然上面水位检查已经过了,但是不一定有大块内存供其使用,还需要继续判断一下。
     */
    for (o = order; o < MAX_ORDER; o++) {
        struct free_area *area = &z->free_area[o];
        int mt;

        /* 若此order的空闲列表上没有页面了,则继续下一轮循环,去查看更高阶的空闲链表 */
        if (!area->nr_free)
            continue;

        /* 此阶上有空闲内存块,在 UNMOVABLE, MOVABLE, RECLAIMABLE 链表上只要有一个链表不为空,就返回true */
        for (mt = 0; mt < MIGRATE_PCPTYPES; mt++) {
#ifdef CONFIG_CMA
            /* 这里主要是想先判断前三个迁移类型,在循环外延后判断CMA类型 */
            if (mt == MIGRATE_CMA)
                continue;
#endif
            /* !list_empty(&area->free_list[mt]); */
            if (!free_area_empty(area, mt))
                return true;
        }

#ifdef CONFIG_CMA
        /* 此阶上有空闲内存块,但是不是上面三种迁移类型的。如果要分配的是CMA内存,且CMA内存链表不为空,也返回true */
        if ((alloc_flags & ALLOC_CMA) && !free_area_empty(area, MIGRATE_CMA)) {
            return true;
        }
#endif
        /* 若是harder分配,且高优先级原子分配的HIGHATOMIC内存链表不为空,也返回true。否则继续遍历更高阶内存块了 */
        if (alloc_harder && !list_empty(&area->free_list[MIGRATE_HIGHATOMIC]))
            return true;
    }

    /* 在当前zone的free_area[]中没有找到可以满足当前order分配的内存块,返回false */
    return false;
}

若此函数返回false,就表示空闲页面不满足水位要求,或没有满足order的大块连续物理内存。

若 alloc_flags 中包含:
ALLOC_HIGH: 则水位要求降低至原来的4/8。
ALLOC_HARDER: 则水位要求降低至原来的3/8。
ALLOC_OOM: 则水位要求降低至原来的2/8。
ALLOC_HARDER|ALLOC_OOM: 包含这两个标志位中的任意一个,就可以访问 zone->nr_reserved_highatomic。
ALLOC_CMA: 包含这个标志可以从CMA类型的空闲链表中分配,否则空闲页面中还要减去CMA类型的内存。


五、zone->watermark_boost 作用

TODO

1. zone->watermark_boost 的作用?

zone->watermark_boost 是一个用于临时提升内存水位线的机制,主要目的是为了应对某些特定情况下的内存压力。具体作用如下:

(1) 临时提升水位线:

zone->watermark_boost 会在系统面临高内存压力时,临时提高各个内存区域(zone)的水位线(watermark),从而减少内存分配失败的风险。

这种提升是动态的,通常在系统检测到内存紧张时自动进行。
提升后的水位线可以使得内核更早地启动内存回收操作,如 kswapd 或 direct reclaim,以防止内存耗尽导致系统崩溃或其他严重问题。

(2) 应用场景:

突发性内存需求:当系统突然需要大量内存时,watermark_boost 可以快速提升水位线,确保有足够的空闲内存供分配。
内存泄漏检测:在某些情况下,watermark_boost 可以用来检测内存泄漏。通过提升水位线,系统可以更容易地识别出哪些进程占用了过多内存。
负载均衡:在多核或多节点系统中,watermark_boost 可以帮助平衡不同内存区域的使用情况,避免某个区域因内存不足而影响整体性能。

(3) 实现细节:

watermark_boost 的值通常是一个相对较小的增量,用于微调水位线。
在内存分配过程中,内核会检查当前的水位线加上 watermark_boost 后是否满足要求,如果满足则继续分配,否则启动内存回收操作。
提升后的水位线会在内存压力缓解后逐渐恢复到正常水平。

(4) 与 extra_free_kbytes 和 watermark_scale_factor 的区别:

extra_free_kbytes 是一种静态的方式,通过在 /proc/sys/vm/extra_free_kbytes 中设置一个固定的值来增加 low 与 min 之间的差值。
watermark_scale_factor 也是一种静态的方式,通过设置一个比例因子来动态调整 low 和 high 水位线,但它是基于内存总量的比例来计算的。
watermark_boost 则是一种动态的方式,它在特定情况下临时提升水位线,而不是固定增加某个值或比例。
通过这些机制,Linux 内核能够更好地管理内存,确保系统的稳定性和性能。

 

六、内存水线的作用

TODO

1. 内存水位线的作用:
当内存使用量低于 WMARK_MIN 时,系统会触发直接回收(direct reclaim)。
当内存使用量在 WMARK_MIN 和 WMARK_LOW 之间时,kswapd 进程会被唤醒,进行后台内存回收。
当内存使用量低于 WMARK_HIGH 时,系统会尽量避免触发直接回收,以减少对系统性能的影响。

 

七、调试汇总

1. 文件接口

1.1 /proc/sys/vm/min_free_kbytes

通过此文件可以直接配置水位相关的 min_free_kbytes 全局变量的值,默认取值范围大于0, 对应的handler是 min_free_kbytes_sysctl_handler() 它里面会更新 user_min_free_kbytes 的值,并调用 setup_per_zone_wmarks() 来更新每个zone的水位。

min_free_kbytes 主要用于调整 zone->_watermark[WMARK_MIN] 水位的值。


1.2 /proc/sys/vm/extra_free_kbytes

通过此文件修改水位相关的 extra_free_kbytes 全局变量的值,默认取值范围大于0, 对应的handler也是 min_free_kbytes_sysctl_handler(),也会触发水位的更新。

extra_free_kbytes 的值只会更改low与min之间的delta值,不会更新high与low之间的delta值,详见 __setup_per_zone_wmarks()。


1.3 /proc/sys/vm/watermark_boost_factor

通过此文件修改水位相关的 watermark_boost_factor 全局变量的值,默认取值范围大于0, 默认值是1500, 对应的handler是 watermark_boost_factor_sysctl_handler(), 其只是对这个全局变量进行赋值。

在 boost_eligible()/boost_watermark() 两个函数中会用到,推测应该会影响到 zone->watermark_boost。


1.4 /proc/sys/vm/watermark_scale_factor

通过此文件修改水位相关的 watermark_scale_factor 全局变量的值,默认取值范围是1--1000, 即占万分之一 -- 10%的 zone->managed_pages 的页面数量。对应的handler是 watermark_scale_factor_sysctl_handler(), 这个函数中也会调用 setup_per_zone_wmarks() 触发每个zone的水位的更新。

watermark_scale_factor 只在 __setup_per_zone_wmarks() 中使用,用于控制high/low/min三者之间的delta值,其值越大三者间的delta只也就越大,详见 __setup_per_zone_wmarks()。


2. trace接口


八、总结

1. 根据 alloc_flags 中指定的不同分配优先级(ALLOC_HIGH/ALLOC_HARDER/ALLOC_OOM),水位是可以击穿的(水位限制要求分别是原来的4/8、3/8、2/8),里面若指定了 ALLOC_CMA 才能从CMA空闲链表中分配内存,若指定了 ALLOC_HARDER 或 ALLOC_OOM 才可以从高优先级原子预留空闲链表中分配内存。

2. systcl接口可以调整min水线的值,和min/low/high三者之间的delta值。

 

 


参考:
linux内存回收(二)--直接内存回收机制:https://zhuanlan.zhihu.com/p/454418249

 

posted on 2024-11-09 11:48  Hello-World3  阅读(151)  评论(0编辑  收藏  举报

导航