写作网站5妙不写就删除,抚州建设网站,网站开发的体会,沅江网站制作本文概要
这是Android系统启动的第三篇文章#xff0c;本文以自述的方式来讲解lmkd进程#xff0c;通过本文您将了解到lmkd进程在安卓系统中存在的意义#xff0c;以及它是如何杀进程的。#xff08;文中的代码是基于android13#xff09;
我是谁
init#xff1a;“大…本文概要
这是Android系统启动的第三篇文章本文以自述的方式来讲解lmkd进程通过本文您将了解到lmkd进程在安卓系统中存在的意义以及它是如何杀进程的。文中的代码是基于android13
我是谁
init“大家好还记得我吗我是你们的老朋友init进程直接叫我init吧今天我把我的第二个孩子lmkd进程介绍给大家认识我第一个孩子是logd进程那就让lmkd进程来介绍下自己吧。”
lmkd“大家好我是lmkd进程大家可以叫我lmkdlmkd’这几个字母代表啥意思呢应该大部分人都不清楚lmkd是’low momery killer demon’的首字母缩写翻译成中文就是’低内存杀手守护’进程。”
各进程纷纷惊叫到“啊它是一个杀手咱们Android大家庭中怎么能存在杀手呢这不是制造不稳定性因素吗”
lmkd急忙说到“大家不要慌听我解释我是个杀手不假可我不是你们想象的那种杀人不眨眼的杀人狂魔我可是一个合法的、守法的好杀手并且我是为了大家的利益最大化而存在的。”
“合法、守法还好杀手还为我们利益考虑那我们就听听你是如何狡辩的。”
lmkd整理了下思路不急不慢的说“我们都生存在Android大家庭里咱们共用cpu、io、内存等资源但是这些资源都是有限的当大家庭里面的进程越来越多的时候每个进程能分到的资源就越来越少了那进而整个Android系统会运行越来越慢整个系统都慢了那咱们每个进程肯定也就慢了。于是乎针对这种情况我就出现了当cpu、io、内存资源出现紧张的时候我会从所有进程中把一些不重要、不干活但又占据大量内存的用户空间进程杀掉以释放公共资源供大家使用。”
“这些用户空间进程我杀掉它们不冤吧它们白白占用大量资源但却产出很少、甚至没有任何用处。好了我的观点已经表达清楚了到底给大家带来了利弊我想大家心理都有数了。”
各进程对lmkd所做的事情都纷纷表达了认可。
好了我来总结下我自己吧我的名字是lmkd翻译为中文是低内存杀手守护进程在Android系统出现系统资源紧张的情况下就该我就出手了杀掉那些不重要的进程以释放掉系统资源。既然介绍了我是谁那该介绍下我的出生了。
我的出生
我出生在一个单亲家庭我只有父亲没有母亲我的父亲是init进程我的父亲是一个“不称职的父亲“为啥这样说呢它对于我何时创建、创建后叫啥名字等这些信息它统统不知道你们说它负责吗我只需要把这些信息用init脚本语言配置好后交给它即可剩下的事就全权交给它了脚本语言配置的信息如下
//文件路径/system/memory/lmkd/lmkd.rc//lmkd是进程的名字/system/bin/lmkd 代表当init进程fork lmkd成功后需要执行的可执行文件
service lmkd /system/bin/lmkdclass coreuser lmkdgroup lmkd system readproccapabilities DAC_OVERRIDE KILL IPC_LOCK SYS_NICE SYS_RESOURCEcriticalsocket lmkd seqpacketpasscred 0660 system systemtask_profiles ServiceCapacityLow省略其他信息......//文件路径/system/core/rootdir/init.rc下面的内容是该文件的其中一部分//on init代表init触发器触发的时候会执行下面的各种命令
on init省略其他命令......# Start lmkd before any other services run so that it can register themwrite /proc/sys/vm/watermark_boost_factor 0chown root system /sys/module/lowmemorykiller/parameters/adjchmod 0664 /sys/module/lowmemorykiller/parameters/adjchown root system /sys/module/lowmemorykiller/parameters/minfreechmod 0664 /sys/module/lowmemorykiller/parameters/minfree//start lmkdstart命令会创建进程lmkd与上面lmkd.rc中service后面的lmkd一致start lmkd
我的出生是不是非常的简单啊这其实是脚本语言和init进程的功劳可以点击 我是init进程 这篇文章来了解init进程的知识、以及如何通过脚本语言来创建子进程。
当init把我创建成功后会执行下面的方法执行完下面的方法后我才真正“长大”才真正有了杀进程的能力。
文件路径system/memory/lmkd/lmkd.cpp
int main(int argc, char **argv) {省略代码......//init方法成功返回0从而进入下面的逻辑if (!init()) {省略代码......//初始化reaper主要用来杀进程if (init_reaper()) {ALOGI(Process reaper initialized with %d threads in the pool,reaper.thread_cnt());}//watchdog用来监听lmkd的主线程是否有耗时有耗时的话watchdog会来尝试杀进程if (!watchdog.init()) {ALOGE(Failed to initialize the watchdog);}mainloop();}android_log_destroy(ctx);ALOGI(exiting);return 0;
}杀进程这件事情
我想问大家个问题“如果让大家来做杀进程这件事情应该需要考虑哪些关键点呢“
“没人回答是吧那我就来说下我的想法。”
杀进程这件事情我觉得需要考虑三个关键点何时杀意思是什么时候开始杀进程杀进程是需要一个通知的不可能无脑的去杀收集进程把有可能需要杀掉的进程收集起来这样为杀进程提供目标对象该杀谁收到杀进程通知后从收集的进程中选择目标进而杀之。那就从这三个关键点来介绍杀进程这件事情。
何时杀
何时杀进程呢大家肯定想到的是当内存紧张的时候杀呗这不用疑惑。确实是这样的但是对于我lmkd进程来说我不知道什么时候内存紧张了我没有这个能力别以为我不想有这个能力主要是因为我是一个用户空间的进程如果想做到就需要检测内存、io、cpu等硬件的使用情况而用户空间的进程是无权直接访问这些硬件资源的。但是内核空间有大佬可以解决这个问题那就是PSI、vmpressure、lowmemorykiller。
PSI在Android高版本上基本都采用了这种监听方式而vmpressure在Android高版本上已经不在使用并且lowmemorykiller内核杀进程的方式也已经不再采用了因此来介绍下PSI。
PSIPressure Stall Information是一个可以监控CPU、内存及IO性能异常的内核功能。关于它的介绍可不是三言两语就能讲清楚的点击PSI进行了解。知道了它的作用后就结合代码来看下是如何实现监听功能的
文件路径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);return false;}if (use_psi_monitors) {ALOGI(Using psi monitors for memory pressure detection);} else {ALOGI(Using vmpressure for memory pressure detection);}return true;
}初始化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);return false;}/* 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)) {return false;}//初始化中级别查看 [1.4]if (!init_mp_psi(VMPRESS_LEVEL_MEDIUM, use_new_strategy)) {destroy_mp_psi(VMPRESS_LEVEL_LOW);return false;}//初始化高级别查看 [1.4]if (!init_mp_psi(VMPRESS_LEVEL_CRITICAL, use_new_strategy)) {destroy_mp_psi(VMPRESS_LEVEL_MEDIUM);destroy_mp_psi(VMPRESS_LEVEL_LOW);return false;}return true;
}[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) {return true;}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) {return false;}//当有资源紧张的通知时会调用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);return false;}maxevents;mpevfd[level] fd;return true;
}基于以上的代码当出现资源紧张的时候mp_event_psi或者mp_event_common方法是会被调用的被调用的时候也就是开始杀进程的时机了。
收集进程
“lmkd为啥要收集进程呢”有个进程疑惑的问道。
lmkd“这位进程朋友问的好那我就给大家讲讲”
上面谈到的何时杀只是会通知我应该啥时候开始杀进程但是具体应该杀哪个进程它是没有告知我的也就是说何时杀只告诉我lmkd你应该杀进程了至于你应该杀哪个进程PSI是完全不关心的。如果杀的进程还是没有解决内存紧缺的问题那还会继续通知你lmkd继续杀进程直到达到内存要求为止。是不是特别类似于一个舍得放权的领导他会把做的事情通知下属具体下属应该怎么做他不关心但是一定要做好。
俗话说巧妇难为无米之炊我这对于系统都起了哪些进程以及哪些进程需要被杀都一无所知那我还杀进程杀个鬼吧。因此我lmkd就想到了一个办法就是把有可能被杀的进程收集起来进而为杀进程提供目标。
我把存放有可能被杀进程的地方叫做进程监狱这样叫主要是能帮助大家能更好的理解其实真正代码是procadjslot_list这个数组。你们人类的监狱的主要作用是为了关闭罪犯当罪犯犯法严重到需要执行死刑的时候就需要从监狱里面把对应罪犯找到进而执行死刑。而进程监狱里面存放的是有可能被杀进程为啥是有可能被杀进程呢而不是一定被杀进程就像你们人类监狱关的不一定都是要执行死刑的犯罪分子是一回事。在进程监狱存放的进程是有可能被被杀掉的。当需要杀进程的时候会从进程监狱把目标进程找出来进而杀之。
进程监狱
你们人类的监狱是有布局或区域划分的哪些区域或布局用来关押不同的罪犯。同样进程监狱也是有自己的布局的这里的布局指用什么数据结构来存储收集到的进程。
首先先来介绍一个概念oom_adj_score每个被押送到进程监狱的进程都有一个对应的属性oom_adj_score看了这个属性是不是不清楚它的意思其实只需要关注score分数即可前面的oom_adj都是分数的定语。通俗点讲就是每个被押送到进程监狱的进程都有一个对应的分数。
oom_adj_score的作用是啥呢假设没有它的话虽然我收集了很多的进程但是在杀进程的时候我只能随机挑选目标进程去杀了因为我收集的进程没有一个标志或者属性来区分它们之间的优先级。oom_adj_score的作用就是规定了进程的优先级这个分数可不像你们人类考试的分数考试分数是越高越好是吧而oom_adj_score这个分数是越高就代表优先被杀。oom_adj_score的取值范围是[-1000,1000]只能是整数-1000的分数代表该进程绝对绝对不会并且不能被杀而1000则相反代表只要杀进程肯定最先把它杀掉。
既然每个进程在进入监狱的时候都会携带一个对应的分数那就来想想应该使用什么数据结构来存储收集到的进程
这个数据结构肯定得查找效率最高毋庸置疑数组的查找效率是最高的那就选用数组数组的每个元素存储的是进程。但是怎样来确定进程应该存储在哪个位置呢这个容易每个进程都对应一个分数分数刚好都是整数类型并且存在负数那就通过算法把分数转换为数组索引进而把进程存储到数组的对应索引处即可转换算法为oom_adj_score 1000比如最小分数-1000经过转换后是不是就对应到了数组的0索引分数0是不是就对应数组的1000索引。
“我真是个天才”lmkd被自己的想法给深深的折服了。
“如果多个进程出现了同样的分数该怎么解决呢”突然一个进程说道。
这也好办可以参照HashMap的做法存储进程的数组每一个元素就不只存一个进程了而是一个进程链表为了在进程链表上更快、更方便的去查找进程可以使用双向循环链表。
那就来总结下存储进程的数据结构就是一个数组数组的索引是从[0,2001]数组的每个元素是一个双向循环链表链表的每一个节点指向一个进程
具体的数据结构如下有兴趣可以看下
文件路径:system/memory/lmkd/lmkd.cpp//ADJTOSLOT(adj)定义了 分数转换为索引的算法
#define ADJTOSLOT(adj) ((adj) -OOM_SCORE_ADJ_MIN)
//数组长度
#define ADJTOSLOT_COUNT (ADJTOSLOT(OOM_SCORE_ADJ_MAX) 1)//链表节点定义了next和prev
struct adjslot_list {struct adjslot_list *next;struct adjslot_list *prev;
};//进程信息
struct proc {struct adjslot_list asl; //指向上面的数据结构int pid; //进程idint pidfd; //进程id对应的fduid_t uid; //uid在app安装后就会分配一个唯一的id值与身份证类似int oomadj; //分数pid_t reg_pid; /* PID of the process that registered this record */bool valid;struct proc *pidhash_next; //指向的下个进程
};//adjslot_list数组索引从0到2000进程的分数会通过 ADJTOSLOT(adj) 算法转换为数组的索引
static struct adjslot_list procadjslot_list[ADJTOSLOT_COUNT];
用一张图来总结下 谁来押送进程
进程监狱的布局已经设计完成了那谁来把有可能被杀的进程押送到监狱呢
进程自己这肯定是不行的如若允许这样做了那开发者都希望自己的进程能存活的时间更长都会在押送进程的时候把进程的分数设置成最低那到最后是谁也杀不掉因为分数都是最低的。
其实办法也特别简单可以从用户空间进程的分类来入手整个用户空间的进程主要分为两类系统native进程和运行java代码的进程。系统native进程主要是由init进程创建的比如logd、lmkd进程这些进程的特性是系统级别的、并且非常的重要万一有一个死掉系统就出现问题并且它们是不能运行java代码的因此这些进程基本上是不会被杀掉的运行java代码的进程它们是由zygote进程创建的比如system_server、launcher进程一个app进程一般就是一个应用system_server进程除外这些进程的重要程度是有所区别的比如zygote、system_server进程是最重要的也是最不能被杀掉的像一些退到后台的进程就变的没那么重要了那它们是可以被杀掉的。
还存在一类进程由运行java代码的进程创建的native进程通过fork/clone创建的进程在android低版本的时候用来做保活这类进程可以不关心因为在android高版本的时候杀掉它们的父进程的时候也会把它们杀掉。
因此依据上面进程的分类押送进程的任务完全可以交给它们的各自的大管家init进程和system_server进程。它们是非常清楚哪些进程可以被杀掉哪些完全不能杀的进而给不同的进程打不通的分数进而押送即可。
通信渠道
既然init进程和system_server进程会押送进程到“进程监狱”而它们三个是分别属于不同进程的那如何解决它们之间通信问题呢或者说它们之间的“通信渠道”如何设计呢
进程之间的通信方式有socket、binder、signal、共享内存等。该选用哪种通信方式呢咱们先来分析下咱们的使用场景不结合使用场景而去选择通信方式都是耍流氓。
lmkd进程既可以与init进程通信也可以与system_server进程通信这完全就是c/s模式lmkd是server端init进程和system_server进程是client端其次在通信渠道上传输的数据都是非常简单的主要有进程id、uid等再其次传输的数据频率并不是很频繁对于传输速度也没有非常高的要求最后对于传输的数据是排队式的传输的传递完一个再去传递下一个。
因此基于上面所描述的使用场景socket通信方式是非常适合的因为传输的数据简单虽然在传输过程中有两次复制但数据简单都可以忽略不计其次socket就是一种c/s的模式传递的数据时候也是排队式的。有兴趣的同学可以看下对应的代码代码如下
文件路径system/memory/lmkd/lmkd.cpp
//在main方法会调用该方法
static int init(void) {省略代码......//通过lmkd获得socket对应的fdctrl_sock.sock android_get_control_socket(lmkd);if (ctrl_sock.sock 0) {ALOGE(get lmkd control socket failed);return -1;}省略代码......return 0;
}数据协议
既然通信渠道搭建好了那来确定下渠道上传递的数据的协议吧数据协议是非常的简单的格式如下
//cmd是对应命令值后面的xxx是该命令对应的参数每种命令的参数是不一样的
cmd xxx xxx xxx//下面是一些例子
//LMK_PROCPRIO代表注册一个进程并设置它的oom_adj_score
LMK_PROCPRIO pid uid oom_adj_score
//刚好与上面相反注销一个进程参数pid
LMK_PROCREMOVE pid//代表client订阅一些事件evt_type是事件类型
LMK_SUBSCRIBE evt_typeLMK_PROCPRIO和LMK_PROCREMOVE是和押送进程到进程监狱有关的两个命令LMK_PROCPRIO是押送进程到进程监狱的协议它的参数分别是pid进程id、uidapp安装时候分配的唯一id值、oom_adj_score分数LMK_PROCREMOVE代表从进程监狱把对应进程移除的协议它的参数就只有一个pid。
打分
我lmkd规定了押送到进程监狱的进程需要携带一个分数这分数用来确定进程被杀的优先级同时也规定了分数的一个取值范围但是对于进程应该打多少分这个事情我是完全不关心的也就没有这个能力。我把打分的这个权利全权放开给了init和system_server因此让它俩来介绍下打分这个重要的环节吧。
system_server打分
我是AMSActivityManagerService服务的简称为AMS是常驻于system_server进程的一个服务主要是负责四大组件的所有相关事情在后面会有我的专场就不在赘述了我是负责给用户空间所有运行java代码的进程打分那就把打分的过程分享给大家。
作为打分的依据需要先对运行java代码的进程进行分类按是否是app进程可以分为非app进程和app进程。
非app进程它没有包含四大组件用户完全感觉不到它的存在但是它确实默默地在后台运行,system_server进程就是其中之一。
app进程指包含了四大组件中任一一个的进程比如微信、设置等而app进程还可以分为系统app进程和普通app进程系统app进程如systemui进程等系统app进程需要在AndroidManifest清单文件中配置android:persistenttrue并且还需要安装在system区域在系统启动的时候会启动系统app进程当系统app进程死掉的时候我AMS会负责重新启动它们普通app进程app比如微信、抖音等。
不论是系统app进程还是普通app进程按前后台可以分为前台进程、后台失去焦点进程、后台进程前台进程指app有一个Activity处于或即将处于resume状态或者包含一个前台Service、或者正在处理一个广播后台失去焦点进程是app有一个Activity处于pause状态即可以看到界面但是不可以与用户交互后台进程app的所有Activity都处于stop状态即完全不可见.因此三种状态的进程重要程度是前台进程 后台失去焦点进程 后台进程也就是如果打分的话前台进程的分数最低剩下的分数越来越高。
依据上面进程的分类我AMS也定义了一套依据进程分类和进程状态的打分规则如下图
system_server进程的分数是-900代表它是不会被杀的系统app进程的分数是-800也是不会被杀的前台app进程的分数是0基本上不会被杀掉home进程桌面进程退到后台后的分数是600它的被杀优先级要低于别的退到后台的进程被缓存的app进程即后台进程越是处于缓存队列后面的进程被杀的优先级更高它的最小分数是900最大分数是999因此在杀进程时候它们是最先被杀的
依据上面的这套打分规则我AMS会把进程对应的分数算出来如若发现分数有变动则会通过通信渠道把进程和它的分数“押送”到进程监狱。进程对应的分数oom_adj_score并不是一成不变的会随着进程的状态发生变化而变化比如前台进程的app用户按了home键回到桌面则这个进程会变为后台进程它的分数会变为PERCEPTIBLE_APP_ADJ(值为700)而桌面进程的分数变为FOREGROUND_APP_ADJ(值为0)
通信渠道和传输的代码如下有兴趣可以看下
//与lmkd建立socket链接代码
//文件路径frameworks/base/services/core/java/com/android/server/am/LmkdConnection.java
private LocalSocket openSocket() {final LocalSocket socket;try {socket new LocalSocket(LocalSocket.SOCKET_SEQPACKET);//lmkd代表server端socket.connect(new LocalSocketAddress(lmkd,LocalSocketAddress.Namespace.RESERVED));} catch (IOException ex) {Slog.e(TAG, Connection failed: ex.toString());return null;}return socket;
}//传输数据到lmkd
//文件路径frameworks/base/services/core/java/com/android/server/am/ProcessList.java
private static boolean writeLmkd(ByteBuffer buf, ByteBuffer repl) {//如若链接没有创建则创建if (!sLmkdConnection.isConnected()) {// try to connect immediately and then keep retryingsKillHandler.sendMessage(sKillHandler.obtainMessage(KillHandler.LMKD_RECONNECT_MSG));// wait for connection retrying 3 times (up to 3 seconds)if (!sLmkdConnection.waitForConnection(3 * LMKD_RECONNECT_DELAY_MS)) {return false;}}//把二进制数据发送给lmkd server端return sLmkdConnection.exchange(buf, repl);
}init打分
init进程看了AMS的打分后说道“AMS老兄你的这个打分过程实在是太复杂了吧相对你的打分环节我的就简单的不得了啊”
AMS“init老兄有些情况你不了解首先我管理的进程肯定要比你多的多并且我面对的进程状态要比你复杂的多当app进程进入后台后我会缓存它们当用户再次进入对应app后就可以立马把它上次的状态展示给用户给用户带来一个非常好的用户体验但是缓存也不能缓存过多否则影响系统的性能并且缓存的进程有很多会在后台继续做一些耗电耗内存的工作那针对这种不守规矩的进程那我会通过onTrimMemory(int)方法向它们多次发出警告如若不听则会非常干脆的杀掉它们。”
AMS向init投来羡慕的眼光继续接着说“而你init老兄就轻松多了你管理的都是系统native进程而这些进程首先都非常重要所以基本不会被杀的并且也没有那么多状态变化其次它们都非常的守规矩。而我管理的普通app进程那就不这样了开发者水平参差不齐有的app进入后台还想着偷摸的多干点事情甚至在前台的时候对内存管理的不好会导致内存占用过多像这种情况越来越多的话就导致系统性能不好你说针对这些情况都是我的责任我必须处理它们。”
init“老兄我体会到你的不易了那我就来介绍下我的打分吧”
打分这个事情对我来说非常简单我定的规则也是非常简单的因为我管理的进程都是系统native进程它们都非常重要所有它们默认的分数就是-1000-1000代表着它们是非常不会被杀掉的除非自己死掉。我把打分的权限放开给每个进程因为我充分的相信它们它们可以自己设置分数而不像AMS是完全不敢把这权限放开给每个进程的首先它对这些进程不信任其次如若放开每个进程都设置自己的分数最低以防止被杀那最后就无进程可杀最终导致系统死翘翘。
在我的子进程创建的时候如若发现它的分数oom_adj_score不为-1000的时候我才会把这个进程和它的分数通过socket通信“押送”到进程监狱l
该杀谁
lmkd:“经过上面的层层铺垫终于到了介绍该杀谁的内容了”
该杀哪个进程的核心逻辑是非常简单的因为进程监狱中使用数组来存放收集到的进程它的索引值越大代表进程分数越高比如索引2000处对应进程的分数是1000、索引0处对应进程的分数是-1000。因此杀谁的逻辑就非常清晰了首先从最大分数1000开始从数组的的最末尾位置开始找进程如果找到了杀掉这个进程否则从分数999开始从数组的次末尾位置找进程找到则杀掉进程否则继续重复上面的逻辑直到找到了要杀的进程为止。整个循环肯定不能一直循环下去因为一些关键进程是不能杀的比如system_server进程分数直到min_score_adj后就结束。对应代码如下有兴趣可以看下
文件路径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的具体值是多少呢依据PSIPSI上面提到会在需要杀进程的时候发出通知给lmkd和watchdogwatchdog是会监听lmkd的主线程是否出现耗时耗时的话watchdog就会去杀进程会分别定义不同的值。非常明确的一点是min_score是有最小值的最小值是0也就是所有分数为负值的进程肯定是不会被lmkd杀掉的在系统资源极度极度紧张的情况下分数大于等于0的进程都会被杀掉前台进程、后台无焦点进程、后台进程
PSI取值逻辑
min_score_adj在PSI条件下一般情况下它的值是201PREVIOUS_APP_ADJ 1内存极度极度紧张的情况下它的值为0具体代码位于system/memory/lmkd/lmkd.cpp的mp_event_psi方法
watchdog取值逻辑
min_score_adj在watchdog条件下它的值为0具体代码位于system/memory/lmkd/lmkd.cpp的watchdog_callback方法
好了以上就是该杀谁的内容其实杀的进程都是app进程后台app进程是先被杀掉的像system_server进程、系统persistent进程它们都是负值是不会被杀掉的甚至系统native进程它们的分数基本都是-1000因此也是不会被杀掉的。
总结
以上就是关于我的介绍我来总结下吧我是lmkd进程翻译为中文是低内存杀手守护进程我的主要使命是在系统资源处于紧张的时候开始杀掉不重要、基本不工作的用户空间进程以释放系统资源。
那我是如何知道什么时候该杀进程的呢我是通过使用PSIPressure Stall Information的机制来监听系统资源是否处于紧张若如出现紧张则mp_event_psi或者mp_event_common方法会收到相应的通知。
因为我既没有init进程那样创建系统native进程的能力也没有zygote进程那样创建运行java代码进程的能力因此我是无法知道用户空间都创建了哪些进程不知道都有哪些进程那如何杀进程呢因此我想到了收集进程收集的每个进程都会对应一个分数oom_adj_score这分数越高则代表该进程优先被杀分数的取值范围是[-10001000]。收集并不是主动收集而是由init进程和AMSActivityManagerService把各自管理的进程通过socket“押送”到进程监狱。
在收到杀进程的通知后我会从进程监狱中从最高分数1000开始去查找目标进程如若找到则杀之否则继续从999开始查找不断循环此步骤直到找到目标进程杀掉它为止。