内核技术中文网»首页 论坛 圈点 查看内容

0 评论

0 收藏

分享

watermark与lowmem_reserve

本帖最后由 极致Linux内核 于 2023-2-3 20:53 编辑

概述:

当系统内存短缺的情况下仍去申请内存,可能会触发系统对内存的回收,那什么时候应该进行回收,回收到什么标准又可以停止回收,参考依据是什么?即本文将介绍的watermark(内存水位线),当检查watermark时又不单单是判断watermark,还会牵扯到lowmem_reserve[],关于lowmem_reserve本文会一并介绍。

说明:内核版本 5.9-rc2

watermark的概念:

系统中每个NUMA node的每个struct zone中都定义着一个_watermark[NRWMARK]数组,其中存放着该zone的min、low和high三种内存水位线。简单来说,它们是衡量当前系统剩余内存是否充足的一个标尺。当zone中的剩余内存高于high时说明剩余内存充足,低于low但高于min时说明内存短缺但是仍可分配内存,若低于min则说明剩余内存极度短缺将停止分配(GFP_ATOMIC类型的分配例外)并全力回收。

下图展示了min、low、high和内存回收的关系:

看图说话:

开始随着横轴time的增长,纵轴剩余内存total size由于内存分配急剧下降,当total size低于low的时候,会唤醒kswapd内核线程进行异步回收(所谓异步回收,就是此时仍可以分配内存,内存回收通过kswapd内核线程在后台同时进行);若回收的速度小于分配的速度,total size会降至min水位线以下,此时会触发同步回收,即在__alloc_pages_slowpath函数中阻塞分配内存并尝试直接内存回收(reclaim)、内存压缩(compact)、更甚者OOM Killer强制回收;直到tatol size大于high水位线,回收才会停止。整体呈现为一个"V"字形状。

简单总结:

high:高于high时,kswapd会睡眠。

low:低于low时,kswapd会被唤醒。

min:低于min时,说明在low和min之间kswapd的回收的速度低于分配,因此阻塞

分配,并通过各种形式回收内存。

补充:细心的话会发现,当内存低于min时还会下降一小段,说明仍然可以分配内存。但仅仅限于内核中的一些紧急的分配或是带有GFP_ATOMIC标志的分配请求,会放宽对watermark的检查,放宽多少,具体参看后面__zone_watermark_ok()函数的实现。

大小关系:

high:low:min == 6:5:4

(这是系统启动后默认的比例,用户可以通过procfs下的watermark_scale_factor进行调节)

追根溯源watermark:

procfs下面有个min_free_kbytes的接口,它对应kernel中的一个同名全局变量min_free_kbytes,表示上文提到的内存水位线min的大小。用户可以通过procfs下的/proc/sys/vm/min_free_kbytes节点去调节它。但是它的值的范围不得大于256MB,不得小于128KB。若用户不去调节它,在系统启动时会为它计算一个初值,并根据这个值扩大1.25倍和1.5倍分别作为low和high的值。

这些工作都是在init_per_zone_wmark_min()函数中实现,不贴代码了,直接看函数内容的提炼,如图:

(图中说明了全局变量min_free_kbytes的计算方法,以及如何根据min_free_kbytes为各个zone设置它们的min、low和high。)

检查watermark:

kernel中检查watermark的函数有两个:zone_watermark_fast和__zone_watermark_ok,它们会在每次rmqueue分配内存前被调用用来检查watermark。

其中zone_watermark_fast()是_zone_watermark_ok()的扩展版本,增加了对order 0分配的快速检查。我们详细看下基础版本__zone_watermark_ok :

bool __zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark,

                        int highest_zoneidx, unsigned int alloc_flags,

                        long free_pages)

{

        long min = mark;

        /*将当前zone中的free pages刨去不可用于分配的pages*/

        free_pages -= __zone_watermark_unusable_free(z, order, alloc_flags);

/*上文提到的,若是紧急分配,watermark的标准将被放宽*/

        if (alloc_flags & ALLOC_HIGH)

                min -= min / 2;

        if (unlikely(alloc_harder)) {

                if (alloc_flags & ALLOC_OOM)

                        min -= min / 2;

                else

                        min -= min / 4;

        }

        /*1.引入lowmem_reserve的概念;2.这里的min实际上可能是min low high

          注意:对水线的检查实际还会在watermark的基础上加上lowmem_reserve的

          值,若当前zone是preferred zone,那么lowmem_reserve[x]=0。

        */

        if (free_pages <= min + z->lowmem_reserve[highest_zoneidx])

                return false;

        /* 如果请求是order-0,那么一定可以得到order-0的pageblock,返回true */

        if (!order)

                return true;

        /*若不是order-0的请求,则检查free_area[]中是否有存在大于等于目标order的pageblock*/

        for (o = order; o < MAX_ORDER; o++) {

                struct free_area *area = &z->free_area[o];

                int mt;

                if (!area->nr_free)

                        continue;

                for (mt = 0; mt < MIGRATE_PCPTYPES; mt++) {

                        if (!free_area_empty(area, mt))

                                return true;

                }

        }

        return false;

}

结论:watermark的检查不仅仅是对watermark的检查,还要在watermark的基础上加上lowmemreserve的值(下文将会介绍原因),后续再判断free_area中是否存在可以满足目标order分配需求的pageblock,全都都符合才算watermark ok~

lowmem_reserve的概念:

如果你仔细看上面的初始化watermark的函数init_per_zone_wmark_min()的框图了的话。你会发现在设置完每个zone的watermark后还有一步: 设置每个zone的lowmem_reserve[ ]

官方对于lowmem_reserve[]的说明如下:

For some specialised workloads on highmem machines it is dangerous for

the kernel to allow process memory to be allocated from the "lowmem"

zone. This is because that memory could then be pinned via the mlock()

system call, or by unavailability of swapspace.

And on large highmem machines this lack of reclaimable lowmem memory

can be fatal.

So the Linux page allocator has a mechanism which prevents allocations

which could use highmem from using too much lowmem. This means that

a certain amount of lowmem is defended from the possibility of being

captured into pinned user memory.

大意就是说:低地址内存是更珍贵的,当高地址内存被申请完了之后会fall back到低地址的内存,而低地址的内存申请却不能使用高地址的内存。举个例子:HIGH_ZONE的内存耗尽之后会使用NORMAL_ZONE的内存,若此时占用了大量NORMAL_ZONE内存,将NORMAL_ZONE也耗尽,且分配时用mlock锁了这些内存(无法被swap),一直未释放。后续若是HIGH_ZONE的内存得到释放,就导致一个尴尬的局面:HIGH_ZONE有大量空闲内存可用,但是NORMAL_ZONE为了满足HIGH_ZONE的使用致使自己无内存可用,这显然是不合理的。

于是struct zone就需要使用lowmem_reserve[]保留一部分内存不可以被高地址的zone使用,仅供自己使用。具体保留多少由高地址zone的managed pages的大小和一个比例值决定。这个比值位于全局数组sysctl_lowmem_reserve_ratio[MAX_NR_ZONES]中,它有个默认初值,用户还可以通过procfs下的lowmem_reserve_ratio接口来自行修改。如何使用比值计算lowmem_reserve[]大小的代码实现位于setup_per_zone_lowmem_reserve中(如下所示)。

/*

* setup_per_zone_lowmem_reserve - called whenever

*      sysctl_lowmem_reserve_ratio changes.  Ensures that each zone

*      has a correct pages reserved value, so an adequate number of

*      pages are left in the zone after a successful __alloc_pages().

*/

static void setup_per_zone_lowmem_reserve(void)

{

        struct pglist_data *pgdat;

        enum zone_type j, idx;

        for_each_online_pgdat(pgdat) {

                for (j = 0; j < MAX_NR_ZONES; j++) {

                        struct zone *zone = pgdat->node_zones + j;

                        unsigned long managed_pages = zone_managed_pages(zone);

                        zone->lowmem_reserve[j] = 0;

                        idx = j;

                        while (idx) {

                                struct zone *lower_zone;

                                idx--;

                                lower_zone = pgdat->node_zones + idx;

                                if (!sysctl_lowmem_reserve_ratio[idx] ||

                                    !zone_managed_pages(lower_zone)) {

                                        lower_zone->lowmem_reserve[j] = 0;

                                        continue;

                                } else {

                                        lower_zone->lowmem_reserve[j] =

                                                managed_pages / sysctl_lowmem_reserve_ratio[idx];

                                }

                                managed_pages += zone_managed_pages(lower_zone);

                        }

                }

        }

        /* update totalreserve_pages */

        calculate_totalreserve_pages();

}

简述就是high zone中的内存越大,low zone中为为自己reserve的内存就越多,如果high zone太大的话,high zone甚至完全不能使用low zone的内存。

原文作者:superme_

原文地址:https://www.jianshu.com/p/2c49310acbe1(版权归原文作者所有,侵权联系删除

回复

举报 使用道具

全部回复
暂无回帖,快来参与回复吧
主题 1545
回复 0
粉丝 2
扫码获取每晚技术直播链接
快速回复 返回顶部 返回列表