LMK实现原理

前言

系统运行一段时间之后,内存被逐渐分配出去,剩余的内存页越来越少,为了让系统能够继续运行,内核会适时启动page reclaim机制回收一部分内存,当回收的内存仍然不够内存分配时,便会触发OOM机制,选择性的kill掉系统中的某个进程,以瞬间回收大片的内存。内核的OOM在选择要被kill的进程时,优先选择占用内存较多、静态优先级较低的进程,但并不区分android应用中的前台程序和后台程序。从用户体验的角度来说,并不希望系统在内存不足时kill掉前台程序而被用户感知(如应用闪退等状况),而是希望首先kill掉后台程序。LMK(Low memory killer)就是这样一个用户层面的程序,用于在系统内存过低并达到OOM之前,按照用户定义的规则来kill进程。

根据以上描述,欲实现LMK,需要解决两个问题:

  1. OOM是内核在内存分配时,发现无法分配足够的内存触发的内核机制,LMK作为应用程序如何感知系统内存的紧缺?
  2. LMK在感知到内存压力时,如何选择合适的进程去kill,规则如何制定?

如何感知

采用PSI(Pressure Stall Information)评估系统资源压力,包括对CPU、Memory和IO三种系统资源的评估。当进程由于缺少某种资源而阻塞时,PSI技术捕捉这种状态变化,并统计进程处于阻塞状态的持续时间(即进程缺少资源的持续时间),根据该段持续时间在固定时间片段的时间占比衡量系统资源的紧缺程度。根据同一时间段,所有进程还是部分进程处于因缺少某一资源而导致的stuck状态,分别使用full和some指标对系统资源的紧缺程度进行衡量。

some是指一段时间内至少有一个task处于缺少某个资源而阻塞,而full则表示一段时间内所有task同时因为缺少某个相同的资源而被阻塞。以memory的some和full指标进行举例说明,在60s内task的运行情况如下所示:

其中,虚线部分表示task因为缺少memory而阻塞的时间,实线部分表示task的运行时间。假设当前系统中只有两个task,那么两个task虚线重合的部分即为系统处于full的时长,而虚线合并的长度即为系统处于some的时长,各自时长在该段时间内的占比即为处于各自状态的资源紧缺程度。

PSI技术利用epoll机制向用户暴露了三个设备节点,即/proc/pressure/memory、/proc/pressure/cpu以及/proc/pressure/io,节点的作用一方面用于设定阈值(即some和full指标),一方面用于监听(当系统资源的紧缺程度达到设定的阈值时,用户可以监听到事件)。其中,监听功能可通过epoll机制来实现。

lmk程序中,通过init_psi_monitor向/proc/pressure/memory节点写入设置的阈值,并且通过register_psi_monitor函数来监控/proc/pressure/memory节点,当系统内的内存紧缺程度超过设定的阈值,就会触发event。

如何选择

内存使用概念

内核中为可用内存设定了三条水位线:
在这里插入图片描述
当系统剩余可用内存达到不同的水位线时,执行不同的操作:

  1. High
    当剩余内存在high以上时,系统认为当前内存使用压力不大。
  2. Low
    当剩余内存降低到low时,系统就认为内存已经不足了,会触发kswapd内核线程进行内存回收处理。
  3. Min
    当剩余内存在min以下时,则系统内存压力非常大,此时会触发direct reclaim操作(直接内存回收)。当系统在direct reclaim之后仍然分配不到足够的内存时,便会启动OOM机制杀进程。

内存回收包含文件页(file-backed page)和匿名页(anonymous page)的回收。文件页和匿名页的定义简单来说就是:文件页是指从磁盘中加载到内存中的页,内存页中的内容在磁盘中有备份;匿名页是指由应用程序动态分配的页面,内容只存在于内存,磁盘中没有备份。对文件页进行回收时,clean page(没有经过修改,内存和磁盘中的内容保持一致)可直接释放,dirty page(内存中的数据与磁盘中的不一致)则需要先将内存页中的数据回写到磁盘,然后再释放内存页,涉及到磁盘写操作,会耗费时间。匿名页的回收,由于匿名页的内容在磁盘中没有备份,不能被直接释放,所以借用swap分区,将swap分区当做磁盘使用,此时内存中的匿名页相当于dirty page,需要将其先写入swap分区,然后再释放掉内存中的匿名页。所有的匿名页在回收时,都需要进行回写操作(swap分区页是一种磁盘介质),而文件页的回收在当该页为clean page时无需进行回写,所以在内存回收时,系统更倾向于回收文件页(减少IO操作)。至于具体的比例关系,则由swapness参数决定,其值从 0 到 100 不等(默认一般为 60),数值越高,则回收的时候越优先选择 anonymous pages。当它 等于 100 的时候,anonymous pages 和 page cache 就具有相同的优先级。

内存回收时,将文件页和匿名页写入到磁盘或swap分区的操作称之为换出,当应用程序需要再次访问到这些页中的内容时,又将处于磁盘或者swap分区的页加载到内存,此过程称之为换入。内存回收时,会扫描系统中的页面,并选择合适的页面换出,那么什么样的页是合适的呢?选择最近不常使用的页面(即不活跃页面),依据是最近不常使用的页面在较短的时间内也不会被频繁使用,这就是LRU(Least Recently Used)的思想。如果选择了一个较常使用的页面进行回收,该页面刚被换出就有可能被再次使用到,就又需要从磁盘中读取到内存,造成内存抖动(thrashing),不仅缓解不了内存紧张的局面还会额外增加换入换出的两次IO操作。

对于文件页和匿名页,内核中分别为其维护了两个双向链表:一个是包含了最近使用页面的active list,另一个是包含了最近不使用页面的inactive list。两个双向链表均采用FIFO的形式,即新的元素在头部加入,中间的元素逐渐往后移动,当内存回收时,总是只扫描inactive list并从其尾部开始回收。

对于文件页而言,采用了一个refault distance的算法来减少内存抖动。其中,当页面被回收之后又再次被访问时,需要从磁盘之中将页面换入,此时触发的page fault称之为refault。 举例说明,假设target为此后再次访问的页面,当系统可用内存较多时,此时不会对inactive list进行扫描并进行回收,新的inactive page将被添加到inactive list的头部,inactive list增长,此时target page一直存在于inactive list当中。当系统可用内存少到需要进行page reclaim时,系统开始回收inactive list尾部的page,此时target page就随着reclaim过程被回收。当target被回收之后,如果被再次访问,需要将其从磁盘中再次加载到内存即page refault。从target page被回收到其被重新加载到内存称之为refault distance。
在这里插入图片描述
如上所述,如果当前系统的可用内存足够,target一直存在于inactive list,当被再次访问时,只需将其转移到active list,并不会有IO操作;而如果系统可用内存非常匮乏,不断的从inactive list中回收page,那么target刚被换出,就有可能立即被换入,然后又被当做inactive page加入到inactive list(相对最少访问),然后又会被换出、换入,如此往复,造成内存抖动。refault distance算法就是为了减少这种抖动,大致的含义是:设定inactive list的长度为refault distance,当target移动到inactive list尾部时,其间回收的page小于inactive list,将target page转移到active page,避免让其被回收,当超过refault distance时,可以直接回收。

内存状态判断

根据内存使用概念一节的描述,当LMK感知到内存紧张时,需要对内存紧缺程度进行判断,从而决定是否要杀进程,以及可以杀哪些进程:
1、当前是否处于page reclaim状态,如果是处于page reclaim状态,则证明此时系统内存已经较低了,需要进一步判断低到何种地步,相反,则当前系统内存充足,无需通过kill进程获取内存。是否处于page reclaim状态可以通过/proc/vmstat中的pgscan_kswapd和pgscan_direct来判断,其中pgscan_kswapd是从系统启动开始在kswapd reclaim过程中扫描的页面数,pgscan_direct为从系统启动开始系统在direct reclaim过程中扫描到的总的页面数。当两者的数量在两次PSI event通知之间有增加时,表明系统正处于对应的reclaim状态。
2、如果处于page reclaim的状态,那么此时的内存紧缺到何种地步?根据前面描述,系统进行page reclaim时,倾向于先对文件页进行回收,然后再对匿名页进行回收,此时可通过判断匿名页和文件页回收的频率来判断内存的紧缺程度。匿名页的回收频率可根据swap分区剩余空间来判断,文件页的回收频率可通过page refault在page reclaim过程的增长比例来判断,即page refault在该段时间的增长页面占总页面的占比越高,证明这段时间换出换入的文件页越多,频率越高,page refault的文件页的数量可通过/proc/vmstat中的workingset_refault获得。紧缺程度由严重到轻度的程度占比为:两者回收频率都很高 > 匿名页回收频率高 > 文件页回收频率高。

andorid系统中为各个应用程序划分了进程优先级(https://paul.pub/android-process-priority/),LMK中目前只关注了可感知进程,以可感知进程为分水岭,在内存紧缺不到非常严重的时候,不选择杀可感知进程获得内存。LMK的具体判断如下:
在这里插入图片描述

LMK选择进程kill时,以进程的oom_score_adj从大到小依次排列进行选择,oom_score_adj越小表示进程越重要。上图中,min_score_adj的值为根据内存紧缺程度设定的待杀进程的oom_score_adj的下限,即只杀oom_score_adj大于等于该值的进程。

LMK在内部维护了一个链表数组,用于维护应用程序oom_score_adj和应用程序pid的关系,当LMK检测到需要杀应用进程时,通过扫描该链表数组,即可找到大于min_score_adj的目标进程。该链表数组的元素需要引用程序向其中添加,LMK提供接口给应用程序进行设置,以保证LMK严格按照用户定义的规则杀进程。当用户没有定义规则时,LMK可以选择扫描系统中所有的进程(通过is_scan_all全局变量),并选择其中最大的oom_score_adj对应的进程进行杀死。

参考

https://cloud.tencent.com/developer/article/2003334
https://blog.csdn.net/feelabclihu/article/details/105534140

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值