在linux操作系统中,当内存充足的时候,内核会尽量使用内存作为文件缓存(page cache),从而提高系统的性能。例如page cache缓冲硬盘中的内容,dcache、icache缓存文件系统的数据,这些内容是为了提升性能而设计的,还可以再次从硬盘中重新读取来构建对象,这部分内容可以在内存紧张的时候可以直接释放。

所以内存回收在Linux内存管理中占据非常重要的地位,系统的内存毕竟是有限的,跑的进程成百上千,系统的内存越来越小,必须提供内存回收的机制,以满足别的任务的需求。在内存回收的过程中,会遇到以下问题

  • 有哪些内存可以回收
  • 什么时候回收,就需要了解回收解决什么问题?回收内存的策略是如何的
  • 回收内存时,如何尽可能的减小对系统的性能的影响

1 内存回收的目标

对于内核并不是所有的物理内存都可以参与回收,比如内核的代码段,如果被内核回收了,系统就无法正常运行了,所以一般内核代码段、数据段、内核申请的内存、内核线程占用的内存等都是不可以回收的,除此之外的内存都可以是我们要回收的目标。

内核空间是所有进程公用的,内核中使用的页通常是伴随整个系统运行周期的,频繁的页换入和换出是非常影响性能的,所以内核中的页基本上不能回收,不是技术上实现不了而是这样做得不偿失。

同时,另外一种是应用程序主动申请锁定的页,它的实时性要求比较高,频繁的换入换出和缺页异常处理无法满足它对于时间上的要求,所以这部分程序可能使用mlock api将页主动锁定,不允许它进行回收。

那么我们就比较明确了,并非内存中的所有页面都是可以交换出去的。事实上,只有与用户空间建立了映射关系的物理页面才会被换出去,而内核空间中内核所占的页面则常驻内存。我们下面对用户空间中的页面和内核空间中的页面给出进一步的分类讨论。可以把用户空间中的页面按其内容和性质分为以下几种:

  • 进程映像所占的页面,包括进程的代码段、数据段、堆栈段以及动态分配的“存储堆

    进程的代码段和数据段所占用的内存页面是可以被换入换出的

  • 通过系统调用mmap()把文件的内容映射到用户空间

    这些页面所使用的交换区就是被映射的文件本身

  • 进程间共享内存区

    其页面的换入换出比较复杂

除此之外,内核在执行过程中使用的页面要经过动态分配,但永驻内存,此类页面根据其内容和性质可以分为两类:

  • 内核调用kmalloc()或vmalloc()为内核中临时使用的数据结构而分配的页于是立即释放。但是,由于一个页面中存放有多个同种类型的数据结构,所以要到整个页面都空闲时才把该页面释放。
  • 内核中通过调用alloc_pages(),为某些临时使用和管理目的而分配的页面,例如,每个进程的内核栈所占的两个页面、从内核空间复制参数时所使用的页面等等。这些页面也是一旦使用完毕便无保存价值,所以立即释放。

在内核中还有一种页面,虽然使用完毕,但其内容仍有保存价值,因此,并不立即释放。这类页面“释放”之后进入一个LRU队列,经过一段时间的缓冲让其“老 化”。如果在此期间又要用到其内容了,就又将其投入使用,否则便继续让其老化,直到条件不再允许时才加以回收。这种用途的内核页面大致有以下这些:

  • 文件系统中用来缓冲存储一些文件目录结构dentry的空间
  • 文件系统中用来缓冲存储一些索引节点inode的空间
  • 用于文件系统读/写操作的缓冲区

按照以上所述,对于内存回收,大致可以分为以下两类:

  • 文件映射的页,包括page cache、slab中的dcache、icache、用户进程的可执行程序的代码段,文件映射页面。

其中page cache包括文件系统的page,还包括块设备的buffer cache,万物皆文件,block也是一种文件,它也有关联的file、inode等。另外根据页是否是脏的,在回收的时候处理有所不同,脏页需要先回写到磁盘再回收,干净的页可以直接释放。

  • 匿名页,括进程使用各种api(malloc,mmap,brk/sbrk)申请到的物理内存(这些api通常只是申请虚拟地址,真实的页分配发生在page fault中),包括堆、栈,进程间通信中的共享内存,pipe,bss段,数据段,tmpfs的页。这部分没有办法直接回写,为他们创建swap区域,这些页也转化成了文件映射的页,可以回写到磁盘。

2 内存回收机制

内核之所以要进行内存回收,主要原因有两个:

  1. 内核需要为任何时刻突发到来的内存申请提供足够的内存,以便cache的使用和其他相关内存的使用不至于让系统的剩余内存长期处于很少的状态。

    内核使用内存中的page cache对部分文件进行缓存,以便提升文件的读写效率。所以内核有必要设计一个周期性回收内存的机制,以便cache的使用和其他相关内存的使用不至于让系统的剩余内存长期处于很少的状态。

  2. 当真的有大于空闲内存的申请到来的时候,会触发强制内存回收。

所以内核针对这两种回收的需求,分别实现了两种不同的机制。

  1. 针对第①种,Linux系统设计了kswapd后台程序,当内核分配物理页面时,由于系统内存短缺,没法在低水位情况下分配内存,因此会唤醒kswapd内核线程来异步回收内存
  2. 针对第②种,Linux系统会触发直接内存回收(direct reclaim),在内核调用页分配函数分配物理页面时,由于系统内存短缺,不能满足分配请求,内核就会直接触发页面回收机制,尝试回收内存来解决问题

这两种回收的触发方式不同,其区别如下图所示

回收方式 特点
kswapd回收 是内核线程,它和调用者是异步关系
直接内存回收 该机制是内存短缺直接回收,所以是同步回收,会阻塞调用者进程的执行

3 kswapd内核线程

为了避免总在CPU忙碌时也就是缺页异常发生时,临时再来搜寻空页面换出的页面进行换出,内核将定期检查并预先将若干页面换出以腾出空间,维持系统空闲内存的的保有量,以减轻系统在缺页异常发生时的负担。为此内核设置了一个专司页面换出的守护神kswapd进程。

kswapd内核线程初始化时会为系统每个NUMA内存节点创建一个名为“kswapd%d”的内核线程,kswapd进程创建的代码如下:

static int __init kswapd_init(void)
{int nid;swap_setup();for_each_node_state(nid, N_MEMORY)kswapd_run(nid);hotcpu_notifier(cpu_callback, 0);return 0;
}
  • swap_setup函数根据物理内存大小设定全局变量page_cluster,当megs小于16时候,page_cluster为2,否则为3

page_cluster为每次swap in或者swap out操作多少内存页 为2的指数,当为0的时候,为1页,为1的时候,2页,2的时候4页,通过/proc/sys/vm/page-cluster 查看

  • 然后通过for_each_node_state遍历所有 节点,kswapd_run中kthread_run为每个节点创建一 个kswapd%d线程

Kswapd的主循环是一个死循环,只有当kthread _should_stop的时候才会break跳出循环体,会kswapd_try_to_sleep中睡眠,并让出CPU控制权。当系统内存紧张时,这时内存分配函数会调用wakeup_kswapd()来唤醒kswapd内核线程,此时kswapd内核线程在kswapd_try_to_sleep函数中被唤醒,然后调用balance_pgdat()函数来回收页面。下面重点是看看kswapd_try_to_sleep

其主要的流程为:

  • 首先,定义一个wait在kswapd_wait上等待,设置进程状态为TASK_INTERRUPTIBLE,通过prepare_kswapd_sleep判断kswapd是否准好睡眠
  • 可以尝试睡眠HZ/10,若返回不为0,则说明没有HZ/10内没有被唤醒了,HZ一般定义为1000,则是100ms
  • 如果中途没有被唤醒,说明kswap可以睡眠,让出CPU,schedule出去
  • 如果中途被唤醒则返回上层函数,执行内存回收

3.1 kswap的触发条件

kswap进程虽然是系统启动时就会创建,但是大多数时候它处于睡眠状态,只有在进程由于内存不足导致分配内存失败时会被唤醒,从而回收内存,供进程使用。

在NUMA系统中,使用pg_data_t来描述物理内存布局,和kswapd相关参数有:

typedef struct pglist_data {...wait_queue_head_t kswapd_wait; ----------------------------//等待队列wait_queue_head_t pfmemalloc_wait;struct task_struct *kswapd;    /* Protected by  mem_hotplug_begin/end() */int kswapd_max_order;-------------------------------------enum zone_type classzone_idx;-----------------------------//最合适分配内存的zone序号
...
} pg_data_t;
  • kswapd_wait是一个等待队列,每个pg_data_t都有一个等待队列,在free_area_init_core函数中初始化。
  • kswapd_max_order和classzone_idx:在分配内存路径上的唤醒函数wakeup_kswapd作为参数传递给kswapd内核线程

在分配内存路径上,如果在低水位(ALLOC_WMARK_LOW)的情况下无法成功分配内存,那么就会通过wakeup_kswapd函数唤醒kswapd内核线程来回收页面以便释放一些内存。

  • kswap唤醒路径1

    在这种情况下,使用wake_all_kswapds函数唤醒kswapd内核线程来回收内存,以便释放一些内存。

  • kswap唤醒路径2——直接内存回收(阻塞)

    当路径1唤醒kswap返回后,会尝试分配内存,如果还是失败,会再一次切换zone,如果切换后还是无法分配出内存,就只能进行直接内存回收了。直接回收内存阻塞在于throttle_direct_reclaim,它会一直唤醒kswap回收内存,直到空闲内存满足要求才返回。

3.2 balance_pgdat函数

kswapd内核线程被唤醒后,调用balance_pgdat来回收页面,balance_pgdat()是回收页面的主函数。这是一个大循环,首先从高端zone往低端zone方向查找第一个处于不平衡状态end_zone;而后从最低端zone开始回收页面,直到end_zone;在大循环里检查从最低端zone到classzone_idx的zone是否处于平衡状态,而后不断加大扫描力度。

在kswapd回收内存过程中有一个扫描控制结构体,用于控制这个回收过程。既然是回收内存,就需要明确要回收多少内存,在哪里回收,以及回收时的操作权限等,我们看下这个控制结构struct scan_control主要的一些变量,

struct scan_control {unsigned long nr_to_reclaim; //shrink_list()需要回收的页面数量gfp_t gfp_mask;//分配掩码int order; //进程内存分配页面数量,从分配器传递过来的参数nodemask_t  *nodemask; //指定可以在那个node回收内存struct mem_cgroup *target_mem_cgroup; //是否针对某个cgroup扫描回收内存int priority; //控制每次扫描数量,默认是总页数的1/4096enum zone_type reclaim_idx; //进行页面回收的最大zone idunsigned int may_writepage:1; //是否可以回写unsigned int may_unmap:1; //是否可以执行unmapunsigned int may_swap:1; //是否可以将页面交换...unsigned int compaction_ready:1; //是否可以进行内存压缩,即碎片整理unsigned long nr_scanned; //已扫描的非活动页面数量unsigned long nr_reclaimed; //shrink_zones()中已回收页面数量...
};

该函数中有两个重要的函数,zone_balanced用于判断zone在分配order个页面以后的空闲页面是否处于WMARK_HIGH水位之上。返回true,表示zone处于WMARK_HIGH之上。

static bool zone_balanced(struct zone *zone, int order, int classzone_idx)
{unsigned long mark = high_wmark_pages(zone);if (!zone_watermark_ok_safe(zone, order, mark, classzone_idx))return false;/** If any eligible zone is balanced then the node is not considered* to be congested or dirty*/clear_bit(PGDAT_CONGESTED, &zone->zone_pgdat->flags);clear_bit(PGDAT_DIRTY, &zone->zone_pgdat->flags);return true;
}

对于这块后面再单独分析,我们主要关注重点的函数kswapd_shrink_node,其处理流程如下

对于此内容,后面单独章节进行介绍。

4 总结

本章主要是学习了Linux内核触发页面回收的机制有3个:

  • 直接页面回收机制: 在内核态调用页面分配接口函数分配物理内存时,由于系统内存短缺,不能满足分配请求,因此会直接到页面回收机制,尝试回收内存来解决当前的问题
  • 周期性内存回收机制:也就是kswapd内核线程的工作职责,当内核路径调用alloc_pages分配物理内存页面时,由于系统内存短缺,没法再低水位情况下分配内存,因此会唤醒kswapd的内核线程来异步回收内存
  • slab(slab shrinker机制):对于slab,是由缓存的,所以当内存短缺,直接页面回收和周期性回收内存会调用slab回收机制回收对象。

slab机制分配的内存主要是用于slab对象和kmalloc接口,页可以用于内核空间的内存分配,比较文件的Node缓存等。

kswapd本身是一个内核线程,它和调用的关系是异步的,如我们用户空间的进程尝试调用alloc_pages来分配内存,当发现在低水位的情况下无法分配出内存时,它将唤醒kswapd内核线程。这时,Kswapd内核线程就开始执行页面回收工作了,同时test进程会尝试其他办法来分配内存,如调用直接回收内存机制。所以对于页面回收机制的主要调用关系如下图所示

linux内存管理笔记(三十九)----kswapd内存回收相关推荐

  1. Linux内存管理(三十九):页面回收简介和 kswapd详解(1)

    源码基于:Linux5.4 0. 前言 在 LRU简介 一文和 LRU 第二次机会法 一文中,提到当内存出现紧张的时候,会将 inactive list 尾部的 page 进行换出,从而将page 释 ...

  2. linux内存管理笔记(四十二)----内存规整

    伙伴系统是以页面为单位管理内存,内存碎片也是基于页面,即由大量离散且不连续的页面组成.从内核的角度,出现内存碎片不是什么好的事情,例如 有些情况下物理设备需要大量的连续的物理内存,如果内核无法满足,就 ...

  3. linux内存管理笔记(三十四)----匿名映射

    匿名内存是用户空间的概念,不涉及内核态内存.匿名内存的概念是指一段虚拟内存映射是否与之相关联的对象,如果没有关联对象就称为匿名的.本章就主要学习缺页异常的匿名映射,其中涉及到以下内容 匿名映射的概念 ...

  4. 内存管理器(十)kernel内存管理----数据结构

    内存管理器(十) kernel内存管理----概况与数据结构 前言 正式开始学习内核的内存管理了,先学习下接口函数,每一个例字都必须写内核模块实验,然后深入到函数的内部研究源码,最后写写练习的小程序. ...

  5. 【Visual C++】游戏开发笔记三十九 浅墨DirectX教程之七 他山之石:几种几何体的快捷绘制法

    本篇文章里,我们对Direct3D之中几种几何体的简洁绘制方法进行了详细的剖析,最后依旧是提供文章配套的详细注释的demo源代码的欣赏,并在文章末尾提供了源代码下载.(这标题有些歧义的,这个几种是修饰 ...

  6. Linux内存管理(三十五):内存规整简介和 kcompactd详解

    源码基于:Linux5.4 0. 前言 伙伴系统以页面为单位来管理内存,内存碎片也是基于页面的,即由大量离散且不连续的页面组成的.从内核角度来看,出现内存碎片不是好事情,有些情况下物理设备需要大段的连 ...

  7. Linux内存管理(五十):内存规整简介

     源码基于:Linux5.4 0. 前言 伙伴系统以页面为单位来管理内存,内存碎片也是基于页面的,即由大量离散且不连续的页面组成的.从内核角度来看,出现内存碎片不是好事情,有些情况下物理设备需要大段的 ...

  8. JAVA学习笔记(三十九)-打印流

    import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; i ...

  9. 【js学习笔记三十九】简单工厂模式

    目录 前言 导语 代码部分 总结 前言 我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大 ...

最新文章

  1. mysql删除有空格字符名称的触发器
  2. CSS属性中Display与Visibility
  3. 1059. Prime Factors (25)
  4. Pycharm 2018.2.1-2018.1
  5. 机器学习知识总结系列-机器学习中的数学-矩阵(1-3-2)
  6. android 基类fragment,Android DialogFragment 基类的定制
  7. 科学•转化医学 | 中国科大发现NK细胞促进胚胎发育的转录调控新机制
  8. 不插网线终端缓慢的问题解决办法
  9. Windows2003 + SQL2000群集安装手册(DELL MD3000) 之MD3000 RAID配置方法(2)
  10. sparkstreaming自定义kafka
  11. vscodemaven 配置_vscode配置maven的settings.json
  12. 400,404,500报错页面总结
  13. JDBC(尚硅谷宋红康老师笔记)
  14. 计算机三级网络技术考过指南 【历年考点汇总】
  15. Linux如何关闭自动锁屏
  16. 韩信点兵问题(C语言)
  17. React 还是 Vue: 你应该选择哪一个Web前端框架?
  18. java编程:假定公鸡5元钱1只,母鸡3元钱1只,小鸡1元钱3只。现在有100元钱要求买100只鸡,请编程列出所有可能的购鸡方案。
  19. Guitar Pro 功能介绍之RSE引擎
  20. SVN报错:Cannot checkout from svn: svn: E155000: 'F:\SVN-Flx\。。。。' is alrea

热门文章

  1. react-activation缓存React.lazy异步组件问题记录
  2. Qt播放HTML网页视频
  3. 404究竟是什么意思呢?像404,200,503等数字究竟是什么东西
  4. 【2022最新】手把手教你拥有自己的服务器与网站(无需备案)
  5. 关于 continue 用法
  6. 【Python代码】文件查重(02)-简易版
  7. SpringBoot Junit单元测试
  8. unityShader入门了解
  9. 如何在 Windows 右键菜单中新建自己想要的文件格式
  10. h5 开源移动开发平台_5个开源移动应用