美文网首页
保留内存lowmem_reserve浅析

保留内存lowmem_reserve浅析

作者: superme_ | 来源:发表于2021-05-13 14:09 被阅读0次

一、保留内存的用武之地

要了解内存区的保留内存,我们首先要知道这个保留内存用在何处。实际上它在内核水线计算时会用到,那内核是如何计算水线的呢?保留内存到底在水线计算起到什么作用呢?我们先来跟踪代码一探究竟。

为了方便说明,这里只针对UMA架构。UMA架构物理内存的分配最终由__alloc_pages_nodemask()函数实现。

structpage* __alloc_pages_nodemask(gfp_tgfp_mask,unsignedintorder,

structzonelist*zonelist,nodemask_t*nodemask)

{

structzoneref*preferred_zoneref;

......

structalloc_contextac= {

/* ac.high_zoneidx的值与入参gfp_mask有关系,即主要查看gftp_mask是否有特别指定__GFP_DMA|DMA32|HIGH|MOVEABLE*/

.high_zoneidx = gfp_zone(gfp_mask),/* 如果gfp_mask & __GFP_DMA , high_zoneidx == ZONE_DMA ,

gfp_mask & __GFP_DMA32 , high_zoneidx == ZONE_DMA32,

if ((flags & (__GFP_HIGHMEM | __GFP_MOVABLE)) == (__GFP_HIGHMEM | __GFP_MOVABLE)) high_zoneidx == ZONE_MOVABLE

if (flags & __GFP_HIGHMEM) return ZONE_HIGHMEM;

return ZONE_NORMAL */

                .nodemask = nodemask,

                .migratetype = gfpflags_to_migratetype(gfp_mask),

        };

......

ac.zonelist = zonelist;

/* Dirty zone balancing only done in the fast path */

ac.spread_dirty_pages = (gfp_mask & __GFP_WRITE);

......

/* 从HIGH往DMA方向选择第一个满足(不大于)ac.high_zoneidx的zoneref */

preferred_zoneref = first_zones_zonelist(ac.zonelist, ac.high_zoneidx,

                                ac.nodemask ? : &cpuset_current_mems_allowed,

                                &ac.preferred_zone);

......

ac.classzone_idx = zonelist_zone_idx(preferred_zoneref);/* zoneref->zone_idx */

......

/* ac.classzone_idx 和 ac.zonelist 可以基本确定我们优先从哪个zone分配内存 */

page = __alloc_pages_slowpath(alloc_mask, order, &ac);

}

__alloc_pages_slowpath()-->get_page_from_freelist() 这个函数中最终会进行水线检查。

zone_watermark_ok()--> __zone_watermark_ok(struct zone *z, unsigned int order,unsigned long mark, int classzone_idx, int alloc_flags,long free_pages)

这个是水线检查的主要实现函数,在检查前会对内存区的空闲内存free_pages和水线mark根据内存分配标志进行调整。

    [1] 根据alloc_flags调整水线和free_pages;

free_pages -= (1<< order) -1

/* 降低水线,使其可更容易分配到内存 */

if(alloc_flags & ALLOC_HIGH)

min -= min /2;

/* 若未设置ALLOC_HARDER,则需要保留z->nr_reserved_highatomic内存以不时之需 */

if(likely(!alloc_harder))

  free_pages -= z->nr_reserved_highatomic;

else

min -= min /4;/* 否则的话降低水线使其更容器分配到内存 */

#ifdefCONFIG_CMA

/* 如果配置了CMA,则没有明确指明从CMA分配时(即没有设置ALLOC_CMA)需要保留出CMA内存 */

/* If allocation can't use CMA areas don't use free CMA pages */

if(!(alloc_flags & ALLOC_CMA))

        free_pages -= zone_page_state(z, NR_FREE_CMA_PAGES);

#endif

[2] 调整好水线后开始计算是否到达水线

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

returnfalse;

在这里内存区的保留内存终于浮出水面。这里的水线实际等于 min + z->lowmem_reserve[classzone_idx]。min我们实际上了解了就是zone->watermark[NR_WMARK]中的一个值,它的取值跟实际情况关;而z->lowmem_reserve[classzone_idx]就是我们寻找已久的保留内存。

也就是说内核在进行水线检查时,不仅仅要检查内存区的真正的水线"zone->watermark[x]",还要考虑保留内存。

有时候我们会在某个zone还有比较客观的内存数量时(远超min水线)时,仍然会发生内存分配失败,这时可以考虑一下是否是保留区内存太大导致。

二、认识保留内存

2.1 基本概念

好了,是时候了解一下什么是lowmem_reserve了。

kernel在分配内存时,可能会涉及到多个zone,分配会尝试从zonelist第一个zone分配,如果失败就会尝试下一个低级的zone(这里的低级仅仅指zone内存的位置,实际上低地址zone是更稀缺的资源)。考虑这样一种场景应用进程通过内存映射申请Highmem 并且加mlock分配,如果此时HIGH zone无法满足分配,则会尝试从Normal进行分配。问题来了,应用进程在从HIHG“降”到Normal区的分配请求有可能会耗尽Normal区的内存,而且由于mlock又无法回收,最终的结果就是Normal区无内存--在i386这样的架构上内核能够正常访问的线性区正是Normal区,这就导致kernel可能无法正常工作,然而HIGH zone却可能有足量的可回收内存。

针对这个情形,当Normal zone在碰到来自HIGH的分配请求时,可以通过lowmem_reserve声明:可以使用我的内存,但是必须要保留lowmem_reserve[NORMAL]给我自己使用。

同样当从Normal失败后,会尝试从zonelist中的DMA申请分配,通过lowmem_reserve[DMA],限制来自HIGHMEM和Normal的分配请求。

2.2 保留内存的初始化

     有了上面的铺垫我们看一下各个区的lowmem_reseve[]是如何配置的,各个区究竟保留了多少内存。

1】内核定义了一个long lowmem_reserve[MAX_NR_ZONES]数组来表示各个区的保留内存。这个数组的大小MAX_NR_ZONES与内核配置有关系,例如内核使能了CONFIG_ZONE_DMA、CONFIG_ZONE_DMA32、则MAX_NR_ZONES值为4,他们是:

enumzone_type {

ZONE_DMA,/* 0 */

        ZONE_DMA32, 

        ZONE_NORMAL,             

        ZONE_MOVABLE,

__MAX_NR_ZONES/* 3 */

};

【2】那各个lowmem_reserve[MAX_NR_ZONES]是如何计算的呢?

我们来看看这个数组的初始化流程,它是由setup_per_zone_lowmem_reserve(void)函数来完成的。

四、总结

    这篇文章还是从实际遇到的问题引出的。之前在某个环境出现过Normal区内存量远高于水线时出现OOM的情况,最后查明原因就是因为保留内存太多导致。最终通过调整sysctl_lowmem_reserve_ratio参数进行规避。

相关文章

  • 保留内存lowmem_reserve浅析

    一、保留内存的用武之地 要了解内存区的保留内存,我们首先要知道这个保留内存用在何处。实际上它在内核水线计算时会用到...

  • 浅析内存

    之前在学c++的时候,只知道new是动态申请内存,需要手动释放,而直接 类 对象;的方式则是由编辑器管理内存,即在...

  • 大规模NodeJS项目架构与优化

    聊聊大规模NodeJS项目架构 NodeJS异步IO原理浅析及优化方案 NodeJS内存管理机制及内存优化 大规模...

  • LeakCanary 工作原理浅析

    参考链接Java内存问题 及 LeakCanary 原理分析LeakCanary 工作原理浅析 通过 appli...

  • java数组

    1.java数组(先声明再分配内存):在栈内存中保留数组名,用来保存指向数组实体的地址的名称,在堆内存中保留所需内...

  • 内存分析和处理

    1. 程序内存浅析 一个软件要运行,需要将数据加载到内存中,通过cpu进行内存数据的读写,完成数据的运算。 1....

  • 内存简析(上)

    程序在运行需要将数据加载到内存中,然后通过CPU进行内存数据的读写完成数据的运算。 内存浅析: 软件程序在程序中的...

  • 大规模Node项目架构和优化

    这节课我们要讨论什么 1、NodeJs异步IO原理浅析及优化方案 2、nodejs内存管理机制以及内存优化 3、大...

  • 内存分配浅析

    程序的内存存储的内容如图所示 静态存储区主要分配全局变量和静态变量 从栈上分配: 栈上存放函数参数值 局部变量值等...

  • 浅析JS中的堆内存与栈内存

    浅析JS中的堆内存与栈内存 最近跟着组里的大佬面试碰到这么一个问题, Q:说说var、let、const的区别A:...

网友评论

      本文标题:保留内存lowmem_reserve浅析

      本文链接:https://www.haomeiwen.com/subject/tqxjpktx.html