zoukankan      html  css  js  c++  java
  • Linux内存调节之zone watermark【转】

    转自:https://zhuanlan.zhihu.com/p/73539328

    前面的文章提到“什么情况下触发direct reclaim,什么情况下又会触发kswapd,是由内存的watermark决定的”,那这个"watermark"到底是如何发挥作用的呢?

    Kswapd与Watermark

    Linux中物理内存的每个zone都有自己独立的min, low和high三个档位的watermark值,在代码中以struct zone中的_watermark[NR_WMARK]来表示。

    在进行内存分配的时候,如果分配器(比如buddy allocator)发现当前空余内存的值低于"low"但高于"min",说明现在内存面临一定的压力,那么在此次内存分配完成后,kswapd将被唤醒,以执行内存回收操作。在这种情况下,内存分配虽然会触发内存回收,但不存在被内存回收所阻塞的问题,两者的执行关系是异步的。

    这里所说的"空余内存"其实是一个zone总的空余内存减去其lowmem_reserve的值。对于kswapd来说,要回收多少内存才算完成任务呢?只要把空余内存的大小恢复到"high"对应的watermark值就可以了,当然,这取决于当前空余内存和"high"值之间的差距,差距越大,需要回收的内存也就越多。"low"可以被认为是一个警戒水位线,而"high"则是一个安全的水位线。

    如果内存分配器发现空余内存的值低于了"min",说明现在内存严重不足。这里要分两种情况来讨论,一种是默认的操作,此时分配器将同步等待内存回收完成,再进行内存分配,也就是direct reclaim。还有一种特殊情况,如果内存分配的请求是带了PF_MEMALLOC标志位的,并且现在空余内存的大小可以满足本次内存分配的需求,那么也将是先分配,再回收。

    使用PF_MEMALLOC("PF"表示per-process flag)相当于是忽略了watermark,因此它对应的内存分配的标志是ALLOC_NO_WATERMARK。能够获取"min"值以下的内存,也意味着该process有动用几乎所有内存的权利,因此它也对应GFP的标志__GFP_MEMALLOC。

    if (gfp_mask & __GFP_MEMALLOC)
    	return ALLOC_NO_WATERMARKS;
    
    if (alloc_flags & ALLOC_NO_WATERMARKS)
            set_page_pfmemalloc(page);
    

    那谁有这样的权利,可以在内存严重短缺的时候,不等待回收而强行分配内存呢?其中的一个人物就是kswapd啦,因为kswapd本身就是负责回收内存的,它只需要占用一小部分内存支撑其正常运行(就像启动资金一样),就可以去回收更多的内存(赚更多的钱回来)。

    虽然kswapd是在"low"到"min"的这段区间被唤醒加入调度队列的,但当它真正执行的时候,空余内存的值可能已经掉到"min"以下了。可见,"min"值存在的一个意义是保证像kswapd这样的特殊任务能够在需要的时候立刻获得所需内存。

    Watermark的取值

    那么这三个watermark值的大小又是如何确定的呢?ZONE_HIGHMEM的watermark值比较特殊,但因为现在64位系统已经不再使用ZONE_HIGHMEM了,为了简化讨论,以下将以不含ZONE_HIGHMEM,且只有一个node的64位系统为例进行讲解。

    在这种系统中,总的"min"值约等于所有zones可用内存的总和乘以16再开平方的大小,可通过"/proc/sys/vm/min_free_kbytes"查看和修改。假设可用内存的大小是4GiB,那么其对应的"min"值就是8MiB ( [公式] )。

    int __meminit init_per_zone_wmark_min(void)
    {
    	min_free_kbytes = int_sqrt(lowmem_kbytes * 16);
    
    	if (min_free_kbytes < 128)
    	    min_free_kbytes = 128;
    	if (min_free_kbytes > 65536)
    	    min_free_kbytes = 65536;
           ...
    }
    

    这里的"min"值有个下限和上限,就是最小不能低于128KiB,最大不能超过65536KiB。在实际应用中,通常建议为不低于1024KiB。

    得到总的"min"值后,我们就可以根据各个zone在总内存中的占比,通过do_div()计算出它们各自的"min"值。假设总的"min"值是8MiB,有ZONE_DMA和ZONE_NORMAL两个zones,大小分别是128MiB和896MiB,那么ZONE_DMA和ZONE_NORMAL的"min"值就分别是1MiB和7MiB。

    void __setup_per_zone_wmarks(void)
    {
        unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
    
        for_each_zone(zone) {
            tmp = (u64)pages_min * zone->managed_pages;
    	do_div(tmp, lowmem_pages);
    
            zone->watermark[WMARK_MIN] = tmp;
            zone->watermark[WMARK_LOW]  = min_wmark_pages(zone) + (tmp >> 2);
            zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);
        ...
    }
    

    一个zone的"low"和"high"的值都是根据它的"min"值算出来的,"low"比"min"的值大1/4左右,"high"比"min"的值大1/2左右,三者的比例关系大致是4:5:6。

    使用"cat /proc/zoneinfo"可以查看这三个值的大小(注意这里是以page为单位的):

    你可以把"/proc/zoneinfo"中所有zones的"min"值加起来乘以4(如果page size是4KiB的话),看一下是不是基本等于"/proc/sys/vm"中的"min_free_kbytes"的值。

    Watermark的调节

    为了尽量避免出现direct reclaim,我们需要空余内存的大小一直保持在"min"值之上。在网络收发的时候,数据量可能突然增大,需要临时申请大量的内存,这种场景被称为"burst allocation"。此时kswapd回收内存的速度可能赶不上内存分配的速度,造成direct reclaim被触发,影响系统性能。

    在内存分配时,只有"low"与"min"之间之间的这段区域才是kswapd的活动空间,低于了"min"会触发direct reclaim,高于了"low"又不会唤醒kswapd,而Linux中默认的"low"与"min"之间的差值确实显得小了点。

    为此,Android的设计者在Linux的内存watermark的基础上,增加了一个"extra_free_kbytes"的变量,这个"extra"是额外加在"low"与"min"之间的,它在保持"min"值不变的情况下,让"low"值有所增大。假设你的"burst allocation"需要100MiB(100*1024KiB)的空间,那么你就可以把"extra_free_kbytes"的值设为102400。

    于是,设置各个zone的watermark的代码变成了这样:

    void __setup_per_zone_wmarks(void)
    {
    	unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
    	unsigned long pages_low = extra_free_kbytes >> (PAGE_SHIFT - 10);
    
    	for_each_zone(zone) {
    		min = (u64)pages_min * zone->managed_pages;
    		do_div(min, lowmem_pages);
    		low = (u64)pages_low * zone->managed_pages;
    		do_div(low, vm_total_pages);
    
                    zone->watermark[WMARK_MIN] = min;
    		zone->watermark[WMARK_LOW]  = min + low + (min >> 2);					
    		zone->watermark[WMARK_HIGH] = min + low + (min >> 1)
    	...
    }
    

    和Linux中对应的代码相比,主要就是多了这样一个"extra_free_kbytes",该参数可通过设置"/proc/sys/vm/extra_free_kbytes"来调节。关于Andoird这个patch的详细信息,请参考这个提交记录

    在Android的机器(基于4.4的Linux内核)上用"cat /proc/zoneinfo"查看一下:

    可见,这里"low"和"high"已经不再是"min"值的5/4和6/4了,而是多出了一大截。想要知道调节有没有取得预期的效果,可以通过"/proc/vmstat"中的"pageoutrun"和"allocstall"来查看,两者分别代表了kswapd和direct reclaim启动的次数。

    在Linux内核4.6版本中,诞生了一种新的调节watermark的方式。具体做法是引入一个叫做"watermark_scale_factor"的系数,其默认值为10,对应内存占比0.1%(10/10000),可通过"/proc/ sys/vm/watermark_scale_factor"设置,最大为1000。当它的值被设定为1000时,意味着"low"与"min"之间的差值,以及"high"与"low"之间的差值都将是内存大小的10%(1000/10000)。

    tmp = max_t(u64, tmp >> 2, mult_frac(zone_managed_pages(zone),
    		           watermark_scale_factor, 10000));
    
    zone->_watermark[WMARK_LOW]  = min_wmark_pages(zone) + tmp;
    zone->_watermark[WMARK_HIGH] = min_wmark_pages(zone) + tmp * 2;
    

    关于这个patch的详细信息,请参考这个提交记录。前面讲到的"extra_free_kbytes"的方式只增大了"low"与"min"之间的差值,而"watermark_scale_factor"的方式同时增大了"low"与"min"之间,以及"high"与"low"之间的差值。现在的Android代码已经合并了4.6内核的这个改动,不再单独提供通过"extra_free_kbytes"来调节watermark的方式了。

     

    参考:

    Zoned Allocator -3- (Watermark)

     

    原创文章,转载请注明出处。

    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    Android 应用程序集成FaceBook 登录及二次封装
    Android MVP 设计模式
    java 接口的作用和好处
    Android版本和API Level对应关系
    Android 开源库和项目 2
    高效开发iOS系列 -- 那些不为人知的KVC
    HDU 1019 Least Common Multiple 数学题解
    程序猿喜欢如何的职位描写叙述?
    从零開始搭建微信硬件开发环境全过程——1小时掌握微信硬件开发流程
    Spring ORM数据訪问——Hibernate
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/13747568.html
Copyright © 2011-2022 走看看