内存回收详解见linux内存源码分析 - 内存回收(整体流程),在直接内存回收过程中,有可能会造成当前需要分配内存的进程被加入一个等待队列,当整个node的空闲页数量满足要求时,由kswapd唤醒它重新获取内存。这个等待队列头就是node结点描述符pgdat中的pfmemalloc_wait。如果当前进程加入到了pgdat->pfmemalloc_wait这个等待队列中,那么进程就不会进行直接内存回收,而是由kswapd唤醒后直接进行内存分配。

  直接内存回收执行路径是:

  __alloc_pages_slowpath() -> __alloc_pages_direct_reclaim() -> __perform_reclaim() -> try_to_free_pages() -> do_try_to_free_pages() -> shrink_zones() -> shrink_zone()

  在__alloc_pages_slowpath()中可能唤醒了所有node的kswapd内核线程,也可能没有唤醒,每个node的kswapd是否在__alloc_pages_slowpath()中被唤醒有两个条件:

  1. 分配标志中没有__GFP_NO_KSWAPD,只有在透明大页的分配过程中会有这个标志。
  2. node中有至少一个zone的空闲页框没有达到 空闲页框数量 >= high阀值 + 1 << order + 保留内存,或者有至少一个zone需要进行内存压缩,这两种情况node的kswapd都会被唤醒。

  而在kswapd中会对node中每一个不平衡的zone进行内存回收,直到所有zone都满足 zone分配页框后剩余的页框数量 > 此zone的high阀值 + 此zone保留的页框数量。kswapd就会停止内存回收,然后唤醒在等待队列的进程。

  之后进程由于内存不足,对zonelist进行直接回收时,会调用到try_to_free_pages(),在这个函数内,决定了进程是否加入到node结点的pgdat->pfmemalloc_wait这个等待队列中,如下:

unsigned long try_to_free_pages(struct zonelist *zonelist, int order,gfp_t gfp_mask, nodemask_t *nodemask)
{unsigned long nr_reclaimed;struct scan_control sc = {/* 打算回收32个页框 */.nr_to_reclaim = SWAP_CLUSTER_MAX,.gfp_mask = (gfp_mask = memalloc_noio_flags(gfp_mask)),/* 本次内存分配的order值 */.order = order,/* 允许进行回收的node掩码 */.nodemask = nodemask,/* 优先级为默认的12 */.priority = DEF_PRIORITY,/* 与/proc/sys/vm/laptop_mode文件有关* laptop_mode为0,则允许进行回写操作,即使允许回写,直接内存回收也不能对脏文件页进行回写* 不过允许回写时,可以对非文件页进行回写*/.may_writepage = !laptop_mode,/* 允许进行unmap操作 */.may_unmap = 1,/* 允许进行非文件页的操作 */.may_swap = 1,};/** Do not enter reclaim if fatal signal was delivered while throttled.* 1 is returned so that the page allocator does not OOM kill at this* point.*//* 当zonelist中获取到的第一个node平衡,则返回,如果获取到的第一个node不平衡,则将当前进程加入到pgdat->pfmemalloc_wait这个等待队列中 * 这个等待队列会在kswapd进行内存回收时,如果让node平衡了,则会唤醒这个等待队列中的进程* 判断node平衡的标准:* 此node的ZONE_DMA和ZONE_NORMAL的总共空闲页框数量 是否大于 此node的ZONE_DMA和ZONE_NORMAL的平均min阀值数量,大于则说明node平衡* 加入pgdat->pfmemalloc_wait的情况* 1.如果分配标志禁止了文件系统操作,则将要进行内存回收的进程设置为TASK_INTERRUPTIBLE状态,然后加入到node的pgdat->pfmemalloc_wait,并且会设置超时时间为1s * 2.如果分配标志没有禁止了文件系统操作,则将要进行内存回收的进程加入到node的pgdat->pfmemalloc_wait,并设置为TASK_KILLABLE状态,表示允许 TASK_UNINTERRUPTIBLE 响应致命信号的状态 * 返回真,表示此进程加入过pgdat->pfmemalloc_wait等待队列,并且已经被唤醒* 返回假,表示此进程没有加入过pgdat->pfmemalloc_wait等待队列*/if (throttle_direct_reclaim(gfp_mask, zonelist, nodemask))return 1;trace_mm_vmscan_direct_reclaim_begin(order,sc.may_writepage,gfp_mask);/* 进行内存回收,有三种情况到这里 * 1.当前进程为内核线程* 2.最优node是平衡的,当前进程没有加入到pgdat->pfmemalloc_wait中* 3.当前进程接收到了kill信号*/nr_reclaimed = do_try_to_free_pages(zonelist, &sc);trace_mm_vmscan_direct_reclaim_end(nr_reclaimed);return nr_reclaimed;
}

  主要通过throttle_direct_reclaim()函数判断是否加入到pgdat->pfmemalloc_wait等待队列中,主要看此函数:

/* 当zonelist中第一个node平衡,则返回,如果node不平衡,则将当前进程加入到pgdat->pfmemalloc_wait这个等待队列中 * 这个等待队列会在kswapd进行内存回收时,如果让node平衡了,则会唤醒这个等待队列中的进程* 判断node平衡的标准:* 此node的ZONE_DMA和ZONE_NORMAL的总共空闲页框数量 是否大于 此node的ZONE_DMA和ZONE_NORMAL的平均min阀值数量,大于则说明node平衡* 加入pgdat->pfmemalloc_wait的情况* 1.如果分配标志禁止了文件系统操作,则将要进行内存回收的进程设置为TASK_INTERRUPTIBLE状态,然后加入到node的pgdat->pfmemalloc_wait,并且会设置超时时间为1s * 2.如果分配标志没有禁止了文件系统操作,则将要进行内存回收的进程加入到node的pgdat->pfmemalloc_wait,并设置为TASK_KILLABLE状态,表示允许 TASK_UNINTERRUPTIBLE 响应致命信号的状态 */
static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist,nodemask_t *nodemask)
{struct zoneref *z;struct zone *zone;pg_data_t *pgdat = NULL;
/* 如果标记了PF_KTHREAD,表示此进程是一个内核线程,则不会往下执行 */if (current->flags & PF_KTHREAD)goto out;
/* 此进程已经接收到了kill信号,准备要被杀掉了 */if (fatal_signal_pending(current))goto out;
/* 遍历zonelist,但是里面只会在获取到第一个pgdat时就跳出 */for_each_zone_zonelist_nodemask(zone, z, zonelist,gfp_mask, nodemask) {/* 只遍历ZONE_NORMAL和ZONE_DMA区 */if (zone_idx(zone) > ZONE_NORMAL)continue;
/* 获取zone对应的node */pgdat = zone->zone_pgdat;/* 判断node是否平衡,如果平衡,则返回真* 如果不平衡,如果此node的kswapd没有被唤醒,则唤醒,并且这里唤醒kswapd只会对ZONE_NORMAL以下的zone进行内存回收* node是否平衡的判断标准是:* 此node的ZONE_DMA和ZONE_NORMAL的总共空闲页框数量 是否大于 此node的ZONE_DMA和ZONE_NORMAL的平均min阀值数量,大于则说明node平衡*/if (pfmemalloc_watermark_ok(pgdat))goto out;break;}
if (!pgdat)goto out;
count_vm_event(PGSCAN_DIRECT_THROTTLE);
if (!(gfp_mask & __GFP_FS)) {/* 如果分配标志禁止了文件系统操作,则将要进行内存回收的进程设置为TASK_INTERRUPTIBLE状态,然后加入到node的pgdat->pfmemalloc_wait,并且会设置超时时间为1s * 1.pfmemalloc_watermark_ok(pgdat)为真时被唤醒,而1s没超时,返回剩余timeout(jiffies)* 2.睡眠超过1s时会唤醒,而pfmemalloc_watermark_ok(pgdat)此时为真,返回1* 3.睡眠超过1s时会唤醒,而pfmemalloc_watermark_ok(pgdat)此时为假,返回0* 4.接收到信号被唤醒,返回-ERESTARTSYS*/wait_event_interruptible_timeout(pgdat->pfmemalloc_wait,pfmemalloc_watermark_ok(pgdat), HZ);goto check_pending;}/* Throttle until kswapd wakes the process *//* 如果分配标志没有禁止了文件系统操作,则将要进行内存回收的进程加入到node的pgdat->pfmemalloc_wait,并设置为TASK_KILLABLE状态,表示允许 TASK_UNINTERRUPTIBLE 响应致命信号的状态 * 这些进程在两种情况下被唤醒* 1.pfmemalloc_watermark_ok(pgdat)为真时* 2.接收到致命信号时*/wait_event_killable(zone->zone_pgdat->pfmemalloc_wait,pfmemalloc_watermark_ok(pgdat));check_pending:/* 如果加入到了pgdat->pfmemalloc_wait后被唤醒,就会执行到这 *//* 唤醒后再次检查当前进程是否接受到了kill信号,准备退出 */if (fatal_signal_pending(current))return true;out:return false;
}

  有四点需要注意:

  1. 当前进程已经接收到kill信号,则不会将其加入到pgdat->pfmemalloc_wait中。
  2. 只获取第一个node,也就是当前进程最希望从此node中分配到内存。
  3. 判断一个node是否平衡的条件是:此node的ZONE_NORMAL和ZONE_DMA两个区的空闲页框数量 > 此node的ZONE_NORMAL和ZONE_DMA两个区的平均min阀值。如果不平衡,则加入到pgdat->pfmemalloc_wait等待队列中,如果平衡,则直接返回,并由当前进程自己进行直接内存回收。
  4. 如果当前进程分配内存时使用的标志没有__GFP_FS,则加入pgdat->pfmemalloc_wait中会有一个超时限制,为1s。并且加入后的状态是TASK_INTERRUPTABLE。
  5. 其他情况的进程加入到pgdat->pfmemalloc_wait中没有超时限制,并且状态是TASK_KILLABLE。

  如果进程加入到了node的pgdat->pfmemalloc_wait等待队列中。在此node的kswapd进行内存回收后,会通过再次判断此node是否平衡来唤醒这些进程,如果node平衡,则唤醒这些进程,否则不唤醒。实际上,不唤醒也说明了node没有平衡,kswapd还是会继续进行内存回收,最后kswapd实在没办法让node达到平衡水平下,会在kswapd睡眠前,将这些进程全部进行唤醒。

zone的保留内存

  之前很多地方说明到,判断一个zone是否达到阀值,主要通过zone_watermark_ok()函数实现的,而在此函数中,又以 zone当前空闲内存 >= zone阀值(min/low/high) + 1 << order + 保留内存 这个公式进行判断的,而对于zone阀值和1<<order都很好理解,这里主要说说最后的那个保留内存。我们知道,如果打算从ZONE_HIGHMEM进行内存分配时,使用的zonelist是ZONE_HIGHMEM -> ZONE_NORMAL -> ZONE_DMA,当ZONE_HIGHMEM没有足够内存时,就会去ZONE_NORMAL和ZONE_DMA进行分配,这样会造成一种情况,有可能ZONE_NORMAL和ZONE_DMA的内存都被本应该从ZONE_HIGHMEM分配的内存占完了,特定需要从ZONE_NORMAL和ZONE_DMA分配的内存则无法进行分配,所以内核设计了一个功能,就是让其他ZONE内存不足时,可以在本ZONE进行内存分配,但是必须保留一些页框出来,不能让你所有都用完,而应该从本ZONE进行分配的时候则没办法获取页框,这个值就保存在struct zone中:

struct zone
{......long lowmem_reserve[MAX_NR_ZONES];......
}

  注意是以数组保存这个必须保留的页框数量的,而数组长度是node中zone的数量,为什么要这样组织,其实很好理解,对于对不同的zone进行的内存分配,如果因为目标zone的内存不足导致从本zone进行分配时,会因为目标zone的不同而保留的页框数量不同,比如说,一次内存分配本来打算在ZONE_HIGHMEM进行分配,然后内存不足,最后到了ZONE_DMA进行分配,这时候ZONE_DMA使用的保留内存数量就是lowmem_reserve[ZONE_HIGHMEM];而如果一次内存分配是打算在ZONE_NORMAL进行分配,因为内存不足导致到了ZONE_DMA进行分配,这时候ZONE_DMA使用的保留内存数量就是lowmem_reserve[ZONE_NORMAL]。这样,对于这两次内存分配过程中,当对ZONE_DMA调用zone_watermark_ok()进行阀值判断能否从ZONE_DMA进行内存分配时,公式就会变为 zone当前空闲内存 >= zone阀值(min/low/high) + 1 << order + lowmem_reserve[ZONE_HIGHMEM] 和 zone当前空闲内存 >= zone阀值(min/low/high) + 1 << order + lowmem_reserve[ZONE_NORMAL]。这样就可能会因为lowmem_reserve[ZONE_HIGHMEM]和lowmem_reserve[ZONE_NORMAL]的不同,导致一种能够顺利从ZONE_DMA分配到内存,另一种不能够。而对于本来就打算从本zone进行内存分配时,比如本来就打算从ZONE_DMA进行内存分配,就会使用lowmem_reserve[ZONE_DMA],而由于zone本来就是ZONE_DMA,所以ZONE_DMA的lowmem_reserve[ZONE_DMA]为0,也就是,当打算从ZONE_DMA进行内存分配时,会使用zone_watermark_ok()判断ZONE_DMA是否达到阀值,而判断公式中的保留内存lowmem_reverve[ZONE_DMA]是为0的。同理,当本来就打算从ZONE_NORMAL进行内存分配,并通过zone_watermark_ok()对ZONE_NORMAL进行阀值判断时,会使用ZONE_NORMAL区的lowmem_reserve[ZONE_NORAML],这个值也是0。对于ZONE_NORMAL区而言,它的lowmem_reserve[ZONE_DMA]和lowmem_reserve[ZONE_NORMAL]为0,因为需要从ZONE_DMA进行内存分配时,即使内存不足也不会到ZONE_NORMAL进行分配,而由于自己又是ZONE_NORMAL区,所有这两个数为0;而对于ZONE_HIGHMEM,它的lowmem_reserve[]中所有值都为0,它不必为其他zone限制保留内存,因为其他zone当内存不足时不会到ZONE_HIGHMEM中进行尝试分配内存。

  可以通过cat /proc/zoneinfo查看这个数组中的值为多少:

  这个是我的ZONE_DMA的区的参数,可以看到,对应ZONE_DMA就是为0,然后ZONE_NORMAL和ZONE_HIGHMEM都为1854,最后一个是虚拟的zone,叫ZONE_MOVABLE。

  对于zone保留内存的多少,可以通过/proc/sys/vm/lowmem_reserve_ratio进行修改。具体可见内核文档Documentation/sysctl/vm.txt,我的系统默认的lowmem_reserve_ratio如下:

  这个256和32代表的是1/256和1/32。而第一个256用于代表DMA区的,第二个256代表NORMAL区的,第三个32代表HIGHMEM区的,计算公式是:

ZONE_DMA对于ZONE_NORMAL分配需要保留的内存:

  zone_dma->lowmem_reserve[ZONE_NORMAL] = zone_normal.managed / lowmem_reserve_ratio[ZONE_DMA]

ZONE_DMA对于ZONE_HIGHMEM分配需要保留的内存:

  zone_dma->lowmem_reserve[ZONE_HIGHMEM] = zone_highmem.managed / lowmem_reserve_ratio[ZONE_HIGHMEM]

直接内存回收中的等待队列相关推荐

  1. Linux内核:内存管理——内存回收

    概述 当linux系统内存压力就大时,就会对系统的每个压力大的zone进程内存回收,内存回收主要是针对匿名页和文件页进行的.对于匿名页,内存回收过程中会筛选出一些不经常使用的匿名页,将它们写入到swa ...

  2. 内存回收在嵌入式系统应用方面的调研和总结

    前传 嵌入式系统的内存回收还是比较重要的,因为这块涉及到程序运行性能. 嵌入式系统(比如平板,手机)会更加关注单机性能优化,因而会更加重视系统内存回收. 嵌入式系统不像互联网那种大型分布式服务器系统, ...

  3. linux内存回收(二)--直接内存回收机制

    上一章,我们学习了kswapd的内存回收的机制,其本身是一个内核线程,它和调用者的关系是异步的,那么本章就开始学习内核的内存回收的方式.因为在不同的内存分配路径中,会触发不同的内存回收方式,内存回收针 ...

  4. JavaScript中的内存回收机制

    JS的内存回收 在js中,垃圾回收器每隔一段时间就会找出那些不再使用的数据,并释放其所占用的内存空间. 以全局变量和局部变量来说,函数中的局部变量在函数执行结束后这些变量已经不再被需要,所以垃圾回收器 ...

  5. Android 操作系统中的内存回收

    Android 系统中内存回收的触发点大致可分为三种情况: 第一种情况:用户程序调用 StartActivity(), 使当前活动的 Activity 被覆盖 第二种情况:按下Back键,会调用fin ...

  6. lua中的weak table及内存回收collectgarbage

    弱表(weak table)是一个很有意思的东西,像C++/Java等语言是没有的.弱表的定义是:Aweak table is a table whose elements are weak refe ...

  7. 一本书掌握自动内存回收的机制《垃圾回收的算法与实现》(好书分享更新中)

    垃圾回收的算法与实现分为"算法篇"和"实现篇"两大部分.算法篇介绍了标记-清除算法.引用计数法.复制算法.标记-压缩算法.保守式GC.分代垃圾回收.增量式垃圾回 ...

  8. linux内存回收(一)---kswapd回收

    ​ 正式开始十一之旅,有大量的时间将目前工作中遇到的内存回收进行总结下,主要是对内存回收的整个过程进行重新梳理.在linux操作系统中,当内存充足的时候,内核会尽量使用内存作为文件缓存(page ca ...

  9. linux内存管理笔记(三十九)----kswapd内存回收

    在linux操作系统中,当内存充足的时候,内核会尽量使用内存作为文件缓存(page cache),从而提高系统的性能.例如page cache缓冲硬盘中的内容,dcache.icache缓存文件系统的 ...

  10. [内核内存] [arm64] 内存回收2---快速内存回收和直接内存回收

    文章目录 内存紧张回收 快速内存回收 struct scan_control结构体 __node__reclaim函数介绍 快速内存回收注意事项和小结 直接内存回收 __perform_reclaim ...

最新文章

  1. Mars说光场(4)— 光场显示
  2. 小米故事:凭什么把MIUI用户做到1亿 | PMcaff-干货
  3. axure 如何设置选项联动_Axure教程|用中继器做信息流,高仿人人都是产品经理网...
  4. mc有什么红石机器人_我的世界10月考试!来测测你的MC成绩吧~
  5. 分层结构的生活例子_详解软件分层架构设计、工作原理、实例以及具体架构
  6. 【SQL篇章--CREATE TABLE】
  7. windows internals(深入解析windows操作系统)笔记
  8. 模糊控制算法的C++实现
  9. 记录08_7.15~7.16
  10. 服务器系统安装蓝牙驱动,win2008蓝牙驱动的装配教程详解
  11. 简单计算机c++代码
  12. WordPress如何变更图片存储目录uploads并取消按年月存放?
  13. onenote 实现不同端 秒同步
  14. python中数字加引号和不加引号的区别_高考完小白自学Python,不太懂print语句中一个加引号,一个不加?...
  15. 如何将网络上的共享文件映射到本地
  16. 记Windows系统里MD5、SHA1、CRC32编码值的查看
  17. unittest框架-基础知识
  18. 跨境平台到底好不好?跨境电商分析
  19. python数据分析实战项目—运用matplotlib可视化分析10000条北京各大区二手房区域信息(附源码)
  20. 2013-2014年总结

热门文章

  1. IT界的悲哀--做互联网,就要跳出互联网
  2. java 快速排序流程图_java简单快速排序实例解析
  3. Kafka副本同步机制理解
  4. Linux 系统磁盘满处理方法
  5. 监控web状态的脚本
  6. Lucene.Net中 FSDirectory存储方式下一个 Document是如何得到的
  7. itextSharp 附pdf文件解析
  8. 驱动lx4f120h,头文件配置,没有完全吃透,望指点
  9. Java一次跳出多重循环
  10. Spring 2.5配置文件详解