当前位置: 首页 > news >正文

LMKD(Low Memory Killer Daemon)原理初识

一、背景

早期 Android 基于 Linux 内核的 lowmemorykiller 驱动,通过静态阈值触发杀进程。但是该机制如下缺陷:

响应滞后:依赖固定内存阈值(如 6,8,16,32,64,128MB),无法实时感知内存压力

策略僵化:仅按进程优先级(oom_adj)和内存占用排序,缺乏灵活性

为优化这一问题,Android 5.0(Lollipop) 引入了 Low Memory Killer Daemon (LMKD),将内存回收逻辑从内核迁移至用户空间,实现动态策略,结合 PSI(Pressure Stall Information) 监控内存压力,更精准触发回收。

0

二、原理

lmkd触发的时候是通过psi的压力值触发,当内存水位不足时,根据oomAdjScore选择性杀进程,并将内存紧张事件通知存活的App,保证系统的稳定运行。

0

2.1 何时杀

因为lmkd是一个用户空间的进程,如果想做到就需要检测内存、io、cpu等硬件的使用情况,而用户空间的进程是无权直接访问这些硬件资源的。但是内核空间有大佬可以解决这个问题,那就是PSI、vmpressure、lowmemorykiller。

2.1.1 PSI(Pressure Stall Information)

PSI在Android高版本上基本都采用了这种监听方式,而vmpressure在Android高版本上已经不在使用,并且lowmemorykiller内核杀进程的方式也已经不再采用了,因此来介绍下PSI。

PSI(Pressure Stall Information)是一个可以监控CPU、内存及IO性能异常的内核功能。

文件路径:system/memory/lmkd/lmkd.cpp​​​​​​

文件路径:system/memory/lmkd/lmkd.cpp//init方法在上面的main方法中会被调用static int init(void) {    省略代码......    //在高版本use_inkernel_interface这值为false,也就是不会使用lowmemorykiller这个功能if (use_inkernel_interface) {        省略代码......    } else {        //初始化监听器,查看 [1.2]if (!init_monitors()) {return -1;        }        /* let the others know it does support reporting kills */        property_set("sys.lmk.reportkills", "1");    }    省略代码......return 0;}//初始化监听器[1.2]static bool init_monitors() {    /* Try to use psi monitor first if kernel has it */    //use_psi_monitors为true代表使用PSI来实现资源紧张监控,在高版本为true, 如果 use_psi为true,代表使用PSI则使用init_psi_monitors()来初始化PSI  查看[1.3]    use_psi_monitors = GET_LMK_PROPERTY(bool, "use_psi", true) &&        init_psi_monitors();    //use_psi_monitors不可用的时候,使用vmpressure实现监控    /* Fall back to vmpressure */if (!use_psi_monitors &&        (!init_mp_common(VMPRESS_LEVEL_LOW) ||        !init_mp_common(VMPRESS_LEVEL_MEDIUM) ||        !init_mp_common(VMPRESS_LEVEL_CRITICAL))) {        ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");returnfalse;    }if (use_psi_monitors) {        ALOGI("Using psi monitors for memory pressure detection");    } else {        ALOGI("Using vmpressure for memory pressure detection");    }returntrue;}初始化PSI监听器[1.3]static bool init_psi_monitors() {    /*     * When PSI is used on low-ram devices or on high-end devices without memfree levels     * use new kill strategy based on zone watermarks, free swap and thrashing stats.     * Also use the new strategy if memcg has not been mounted in the v1 cgroups hiearchy since     * the old strategy relies on memcg attributes that are available only in the v1 cgroups     * hiearchy.     */    bool use_new_strategy =        GET_LMK_PROPERTY(bool, "use_new_strategy", low_ram_device || !use_minfree_levels);if (!use_new_strategy && memcg_version() != MemcgVersion::kV1) {        ALOGE("Old kill strategy can only be used with v1 cgroup hierarchy");returnfalse;    }    /* In default PSI mode override stall amounts using system properties */if (use_new_strategy) {        /* Do not use low pressure level */        psi_thresholds[VMPRESS_LEVEL_LOW].threshold_ms = 0;        psi_thresholds[VMPRESS_LEVEL_MEDIUM].threshold_ms = psi_partial_stall_ms;        psi_thresholds[VMPRESS_LEVEL_CRITICAL].threshold_ms = psi_complete_stall_ms;    }    //初始化低级别,查看 [1.4]if (!init_mp_psi(VMPRESS_LEVEL_LOW, use_new_strategy)) {returnfalse;    }    //初始化中级别,查看 [1.4]if (!init_mp_psi(VMPRESS_LEVEL_MEDIUM, use_new_strategy)) {        destroy_mp_psi(VMPRESS_LEVEL_LOW);returnfalse;    }    //初始化高级别,查看 [1.4]if (!init_mp_psi(VMPRESS_LEVEL_CRITICAL, use_new_strategy)) {        destroy_mp_psi(VMPRESS_LEVEL_MEDIUM);        destroy_mp_psi(VMPRESS_LEVEL_LOW);returnfalse;    }returntrue;}[1.4]static bool init_mp_psi(enum vmpressure_level level, bool use_new_strategy) {    int fd;    /* Do not register a handler if threshold_ms is not set */if (!psi_thresholds[level].threshold_ms) {returntrue;    }    fd = init_psi_monitor(psi_thresholds[level].stall_type,        psi_thresholds[level].threshold_ms * US_PER_MS,        PSI_WINDOW_SIZE_MS * US_PER_MS);if (fd < 0) {returnfalse;    }    //当有资源紧张的通知时,会调用mp_event_psi或者mp_event_common方法    vmpressure_hinfo[level].handler = use_new_strategy ? mp_event_psi : mp_event_common;    vmpressure_hinfo[level].data = level;    //使用epoll机制来监听fd上的通知,有资源紧张的通知会在fd上有数据写入if (register_psi_monitor(epollfd, fd, &vmpressure_hinfo[level]) < 0) {        destroy_psi_monitor(fd);returnfalse;    }    maxevents++;    mpevfd[level] = fd;returntrue;}

基于以上的代码,当出现资源紧张的时候,mp_event_psi或者mp_event_common方法是会被调用的,被调用的时候也就是开始杀进程的时机了。

2.2 根据oom_adj_score决定杀哪些进程

oom_adj_score的作用就是规定了进程的优先级,oom_adj_score的取值范围是[-1000,1000]只能是整数,-1000的分数代表该进程绝对绝对不会并且不能被杀,而1000则相反代表只要杀进程肯定最先把它杀掉。

ADJ 分数

英文标识

中文描述

典型进程类型

-1000

SYSTEM

系统核心进程(如

system_server

Android 系统服务

-900

PERSISTENT_SYSTEM

系统常驻进程

关键守护进程(如

surfaceflinger

-800

PERSISTENT_PROC

常驻应用进程

厂商预装不可卸载应用

0

FOREGROUND_APP

前台交互应用(Activity 可见)

用户正在操作的 App

100

VISIBLE_APP

可见但不可交互(如悬浮窗)

半透明 Activity 或画中画

200

PERCEPTIBLE_APP

可感知进程(如后台音乐播放)

播放音乐/导航的后台应用

300

BACKUP_APP

备份进程

正在执行备份操作的 App

400

HEAVY_WEIGHT_APP

重量级进程

占用大量资源的后台应用

500

SERVICE_APP

服务进程

通过

startService()

运行的进程

600

HOME_APP

桌面进程

Launcher(如 Pixel Launcher)

700

PREVIOUS_APP

上一个应用

用户最近使用的 App(非当前前台)

900

CACHED_APP_MIN

缓存进程(最低优先级)

完全后台无 Activity 的进程

999

CACHED_APP_MAX

缓存进程(最高优先级)

即将被系统回收的进程

该杀哪个进程的核心逻辑是非常简单的,首先从最大分数1000开始从数组的的最末尾位置开始找进程,如果找到了杀掉这个进程;否则从分数999开始从数组的次末尾位置找进程,找到则杀掉进程;否则继续重复上面的逻辑直到找到了要杀的进程为止。整个循环肯定不能一直循环下去(因为一些关键进程是不能杀的比如system_server进程),分数直到min_score_adj后就结束。

文件路径:system/memory/lmkd/lmkd.cpp​​​​​​

文件路径:system/memory/lmkd/lmkd.cpp//查找需要杀掉的进程, min_score_adj代表查找到最小分数截止static int find_and_kill_process(int min_score_adj, struct kill_info *ki, union meminfo *mi,                                 struct wakeup_info *wi, struct timespec *tm,                                 struct psi_data *pd) {    int i;    int killed_size = 0;    bool choose_heaviest_task = kill_heaviest_task;    //OOM_SCORE_ADJ_MAX:代表最大分数它的值是1000,从最大分数开始查找for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) {        struct proc *procp;        //choose_heaviest_task代表是否杀掉任务繁重的进程,PERCEPTIBLE_APP_ADJ的值是200        //下面逻辑代表:choose_heaviest_task不为true并且分数小于200的时候,需要杀任务繁重的进程了if (!choose_heaviest_task && i <= PERCEPTIBLE_APP_ADJ) {            /*             * If we have to choose a perceptible process, choose the heaviest one to             * hopefully minimize the number of victims.             */            choose_heaviest_task = true;        }        //循环去找进程while (true) {            //如果是杀繁重任务的进程,则调用proc_get_heaviest(i)方法找到繁重任务进程;否则调用proc_adj_tail(i)去查找            //分数i对应的索引处的循环双向链表的尾节点的进程            procp = choose_heaviest_task ?                proc_get_heaviest(i) : proc_adj_tail(i);if (!procp)break;            //调用kill_one_process方法开始杀进程,killed_size代表释放的空间            killed_size = kill_one_process(procp, min_score_adj, ki, mi, wi, tm, pd);            //若释放的空间大于0,则跳出循环if (killed_size >= 0) {break;            }        }        //若释放空间大于0,则跳出查杀进程循环逻辑if (killed_size) {break;        }    }    //返回释放空间大小return killed_size;}

那min_score_adj的具体值是多少呢,依据PSI(PSI上面提到,会在需要杀进程的时候发出通知给lmkd)和watchdog(watchdog是会监听lmkd的主线程是否出现耗时,耗时的话watchdog就会去杀进程)会分别定义不同的值。非常明确的一点是min_score是有最小值的,最小值是0,也就是所有分数为负值的进程肯定是不会被lmkd杀掉的,在系统资源极度极度紧张的情况下,分数大于等于0的进程都会被杀掉(前台进程、后台无焦点进程、后台进程)

2.2.1 PSI取值逻辑

min_score_adj在PSI条件下,一般情况下它的值是201(PREVIOUS_APP_ADJ + 1),内存极度极度紧张的情况下它的值为0,具体代码位于

system/memory/lmkd/lmkd.cpp的mp_event_psi方法

2.2.2 watchdog取值逻辑

min_score_adj在watchdog条件下,它的值为0,具体代码位于system/memory/lmkd/lmkd.cpp的watchdog_callback方法

以上就是该杀谁的内容,其实杀的进程都是app进程,后台app进程是先被杀掉的,像system_server进程、系统persistent进程它们都是负值是不会被杀掉的,甚至系统native进程它们的分数基本都是-1000,因此也是不会被杀掉的。

三、查看手机的LMKD配置信息

命令:

adb shell getprop | grep sys.lmk.minfree_levels [sys.lmk.minfree_levels]: [18432:0,23040:100,27648:200,32256:250,55296:900,80640:950]

内存阈值 (KB)

内存阈值 (MB)

ADJ 分数

目标进程类型

进程示例

18432

18

0

前台应用(FOREGROUND_APP)

用户正在操作的 App

23040

22.5

100

可见但无交互(VISIBLE_APP)

画中画、悬浮窗

27648

27

200

可感知进程(PERCEPTIBLE_APP)

后台音乐播放、导航

32256

31.5

250

备份进程(BACKUP_APP)

正在执行备份的 App

55296

54

900

缓存进程(CACHED_APP_MIN)

完全后台无 Activity 的进程

80640

78.75

950

缓存进程(CACHED_APP_MAX)

即将被回收的进程

即内存水位与进程终止规则对照表

进程优先级

(ADJ分数)

内存水位

(页)

内存水位(MB)

终止规则

0

18,432

72 MB

当可用内存 ≤ 72MB,杀死 ADJ > 0的进程(包括前台应用外的所有进程)。

100

23,040

90 MB

当可用内存 ≤ 90MB,杀死 ADJ > 100的进程(如后台服务、非可见应用)。

200

27,648

108 MB

当可用内存 ≤ 108MB,杀死 ADJ > 200的进程(如后台音乐播放等)。

250

32,256

126 MB

当可用内存 ≤ 126MB,杀死 ADJ > 250的进程(如备份操作中的进程)。

900

36,864

144 MB

当可用内存 ≤ 144MB,杀死 ADJ > 900的进程(最低优先级的缓存进程)。

950

46,080

180 MB

当可用内存 ≤ 180MB,杀死 ADJ > 950的进程(极端情况下的强制回收)。

内存水位以 页(Page) 为单位(通常 1页 = 4KB),转换为 MB 的公式:MB=1024页数×4。,例如:18,432×4/1024=72MB

四、缺陷

在mtk上lmdk,swappiness可能会用满,totalused也可能会很多(UsedRAM很大)。lmkd里面策略相对比较缓和,故需增加swap&total用量的判断

在高通项目上,各类情况都出现。

1、psi不正确,没有多少内存使用的时候出现高内存压力

2、cached kernel+ free也出现很大,但是cached很少

3、swap用满了

4、totalused很多,swap没怎么用

ps:

需在lmkd增加swap、cached kernel+ free、total used、cached app nums的判断

进程回收存在以下问题:

1、进程回收功能比较耗时,且没有即时退出功能。

2、进程回收发生在adj改变的时刻,没有考虑计算场景,可能带来性能影响,引起流畅性问题。

3、没有针对系统做内存水线调节机制。只有lmkd在内存极端紧张的时候,做强制性进程剔除,此时内存压力较大,可能出现流畅性问题。同时由于lmkd只做基于oomadj的强制性内存剔除,不做到更弹性的策略性查杀,不利于系统做策略性优化。

另外,基于进程维度的内存回收(压缩)方式,效率极低。由于需要遍历vma,且有大量的pte为空,进行一次进程内存回收,需要耗费很多空循环。频繁的进行进程维度的内存回收,会有性能衰退的风险。

​​​​​​​

scaned:285005, empty: 226640, unnormal: 468, unevict: 0, tried: 57897,reclaimed: 57894scaned:196916, empty: 157842, unnormal: 476, unevict: 0, tried: 38598,reclaimed: 38595scaned:257753, empty: 200805, unnormal: 483, unevict: 0, tried: 56465,reclaimed: 56461scaned:214477, empty: 138932, unnormal: 547, unevict: 0, tried: 74998,reclaimed: 74995

文章参考:

最新Android 内存管理之LMKD

Android帝国之进程杀手--lmkd

相关文章:

  • wandb转为csv
  • LeetCode - 238. 除自身以外数组的乘积
  • Vue 模板配置项深度解析
  • ArcPy扩展模块的使用(3)
  • Quick BI 自定义组件开发 -- 第二篇 添加 echart 组件,开发图表
  • Redis群集
  • 精准夹持,稳定控制:IXTUR气控永磁铁选型全攻略(涵盖MAP、MRP与LI-120系列)
  • push [特殊字符] present
  • 【数据集处理】拼接MODIS 1 kmNDVI数据集(MRT工具处理+Python全代码)
  • 【大厂机试题解法笔记】报文响应时间
  • Qt+OPC开发笔记(二):OPC客户端介绍与读取和写入bool类型Demo
  • rknn toolkit2搭建和推理
  • rknn优化教程(二)
  • 零基础设计模式——行为型模式 - 责任链模式
  • Jenkins自动发布C# EXE执行程序
  • UI框架-通知组件
  • 分布式计算框架学习笔记
  • 奥特曼:大模型可靠性已过拐点,企业如何跑步入场?
  • 无人机视觉跟踪模块技术解析!
  • Linux 文本比较与处理工具:comm、uniq、diff、patch、sort 全解析
  • 网站建设中英文表述/谷歌搜索引擎入口手机版
  • 深圳电子网站建设/全国最新疫情最新消息
  • 工程建设国家标准网站/排名优化公司电话
  • 网站风格主要包括/网站seo谷歌
  • wordpress评论点回复不刷新/沈阳网站关键词优化多少钱
  • 网站设计步骤及注意事项/今年疫情最新消息