背景
- Android是一个多任务系统,可以同时运行多个程序,一般来说,启动运行一个程序是有一定的时间开销的,因此为了加快运行速度,当你退出一个程序时,Android并不会立即杀掉它,这样下次再运行该程序时,可以很快的启动。随着系统中保留的程序越来越多,内存肯定会出现不足,这个时候Android系统杀进程的刽子手---Lowmemory Killer就起作用了。
- Android 使用内核中的 lowmemorykiller驱动程序来监控系统内存压力,该驱动程序是一种依赖于硬编码值的严格机制。从内核 4.12 开始,lowmemorykiller 驱动程序已从上游内核中移除,用户空间 lmkd会执行内存监控以及进程终止任务。
- 用户空间 lmkd 可实现与内核中的驱动程序相同的功能,但它使用现有的内核机制检测和估测内存压力。这些机制包括使用内核生成的 vmpressure 事件或压力失速信息 (PSI) 监视器来获取关于内存压力级别的通知,以及使用内存 cgroup 功能限制分配给每个进程的内存资源(根据每个进程的重要性)。
- 参考wiki:低内存终止守护程序 | Android 开源项目 | Android Open Source Project
LMKD什么时候启动?
在手机开机的时候,会调用lmkd.rc(system/memory/lmkd)初始化lmkd。
lmkd是系统一个非常重要的服务,开机是由init进程启动,如下所示:system/core/lmkd/lmkd.rc
system/memory/lmkd/lmkd.rc
LMKD的运行周期?
手机运行,LMKD全程都在。
水线
camera在后台
camera在后台,使用"sys.lmk.minfree_levels" 系统的水线
wj@wj:~/SSD_1T/M1_Stable$ adb shell getprop | grep -aEi "minfree"
[dalvik.vm.heapminfree]: [2m]
[sys.lmk.minfree_levels]: [18432:0,23040:100,27648:200,32256:250,55296:900,80640:950]
camera在前台
camera在前台,使用"persist.sys.lmk.camera_minfree_levels" 相机的水线
水线参数介绍
- 水线中有六个level,每个level包含两个参数(minfree,min_score_adj)
- minfree:代表内存大小,单位pages
- min_score_adj:代表进程优先级,framework层的AMS获取并更新
当前系统剩余内存小于80640,会查杀oom_score_adj=950及以上的进程
LMKD 的基本工作原理
这张图简单地展示了 lmkd 的基本工作流程。
LMKD 的演变过程
lmkd(Low Memory Killer Daemon)是低内存终止守护进程,用来监控运行中android系统内存的状态,通过终止最不必要的进程来应对内存压力较高的问题,使系统以可接受的水平运行。
Android 版本 | 所处空间 | 杀进程时机 |
8.1 之前 | kernel | 监听 kswapd 触发的 shrink 回调 |
8.1 - 9.0 | userspace | 监听 vmpressure |
10 | userspace | 监听 psi |
相关配置属性
PSI
- Android 10 及更高版本支持新的 lmkd 模式,它使用内核压力失速信息 (PSI) 监视器来检测内存压力。上游内核中的 PSI 补丁程序集(反向移植到 4.9 和 4.14 内核)测量由于内存不足而导致任务延迟的时间。由于这些延迟会直接影响用户体验,因此它们代表了确定内存压力严重性的便捷指标。上游内核还包括 PSI 监视器,该监视器允许特权用户空间进程(例如 lmkd)指定这些延迟的阈值,并在突破阈值时从内核订阅事件。
- PSI是Pressure stall information的简称,记录CPU/Memory/IO的压力信息,达到用户空间自定义的预值之后通知用户空间。
- 详细见官方文档:https://facebookmicrosites.github.io/psi/docs/overview.html
- PSI 是 Facebook 开源的一套解决重要计算集群管理问题的 Linux 内核组件和相关工具之一;是一种实时监测系统资源竞争程度的方法;以资源竞争等待时间的方式呈现memory、CPU 和 I/O 的资源短缺情况;PSI 统计数据为即将发生的资源短缺提供早期预警,从而实现更积极主动、细致的响应。
wj@wj:~/SSD_1T/M1_Stable$ adb shell
ishtar:/ # cd /proc/pressure/
ishtar:/proc/pressure # cat memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=3083343
full avg10=0.00 avg60=0.00 avg300=0.00 total=2506968
ishtar:/proc/pressure # cat io
some avg10=0.00 avg60=0.01 avg300=0.04 total=38859160
full avg10=0.00 avg60=0.00 avg300=0.00 total=26192413
ishtar:/proc/pressure # cat cpu
some avg10=2.32 avg60=3.10 avg300=5.89 total=3776585362
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
vg10 、avg60 、avg300分别代表 10s、60s、300s 的时间周期内的阻塞时间百分比。total 是总累计时间,以毫秒为单位。
some 这一行,代表至少有一个任务在某个资源上阻塞的时间占比,full 这一行,代表所有的非idle任务同时被阻塞的时间占比.
PSI 中的 full 与 some
some : 至少有一个任务在某个资源上阻塞时间占比
full : 所有任务同时阻塞的时间占比
参考:纯干货,PSI 原理解析与应用_psi原理_内核工匠的博客-CSDN博客
进程的级别
在Android中,进程主要分为以下几种:
ADJ级别 | 取值 | 解释 |
UNKNOWN_ADJ | 1001 | 一般指将要会缓存进程,无法获取确定值 |
CACHED_APP_MAX_ADJ | 999 | 不可见进程的adj最大值 |
CACHED_APP_MIN_ADJ | 900 | 不可见进程的adj最小值 |
SERVICE_B_ADJ | 800 | B List中的Service(较老的、使用可能性更小) |
PREVIOUS_APP_ADJ | 700 | 上一个App的进程(往往通过按返回键) |
HOME_APP_ADJ | 600 | Home进程 |
SERVICE_ADJ | 500 | 服务进程 |
HEAVY_WEIGHT_APP_ADJ | 400 | 后台的重量级进程,system/rootdir/init.rc文件中设置 |
BACKUP_APP_ADJ | 300 | 备份进程 |
PERCEPTIBLE_APP_ADJ | 200 | 可感知进程,比如后台音乐播放 |
VISIBLE_APP_ADJ | 100 | 可见进程 |
ADJ级别 | 取值 | 解释 |
FOREGROUND_APP_ADJ | 0 | 前台进程 |
PERSISTENT_SERVICE_ADJ | -700 | 关联着系统或persistent进程 |
PERSISTENT_PROC_ADJ | -800 | 系统persistent进程,比如telephony |
SYSTEM_ADJ | -900 | 系统进程 |
NATIVE_ADJ | -1000 | native进程(不被系统管理) |
相关进程级别的定义在文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java中。
从上面定义的adj数值来看:adj越小表示进程类型就越重要,系统进程的默认oom_adj 为-900,这类进程被杀的概率很低。
AMS与LMKD交互
在AMS初始化时,通过调用ProcessList.java中updateOomLevels方法,计算出阈值adj 和 minfree ,通过socket与lmkd进行通信,传送数据(LMK_TARGET、minfree、adj),在lmkd中将adj 和minfree写入sys.lmk.minfree_levels中保存。
AMS调整进程的adj相关接口(OomAdjuster.java):
- computeOomAdjLocked:计算adj(对优先级高于cache和empty的进程进行adj的分配)。该方法执行是在updateOomAdjLocked中。
- updateOomAdjLocked:更新adj(分配computeOomAdjLocked没有处理的cache和empty优先级的进程adj)
- applyOomAdjLocked:应用adj,直接保存对应进程的adj:ProcessList执行setOomAdj方法,通过socket传送数据(LMK_PROCPRIO、pid、uid等)给lmkd.c,最终lmkd.c针对每一个进程创建单独文件并写入adj。该方法执行是在updateOomAdjLocked中,最终通过它把computeOomAdjLocked和updateOomAdjLocked计算好的adj更新并保存。
AMS(ActivityManagerService)
CameraBoost
LMKD流程
服务启动后,入口在system/memory/lmkd/lmkd.c文件的main函数中,主要做了如下几件事:
- 读取配置参数
- 初始化 epoll 事件监听
- 锁住内存页
- 设置进程调度器
- 循环处理事件
system/memory/lmkd/lmkd.cpp
int main(int argc, char **argv) {
if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
if (property_set(LMKD_REINIT_PROP, "")) {
ALOGE("Failed to reset " LMKD_REINIT_PROP " property");
}
return issue_reinit();
}
update_props();
ctx = create_android_logger(KILLINFO_LOG_TAG);
if (!init()) {
if (!use_inkernel_interface) {
/*
* MCL_ONFAULT pins pages as they fault instead of loading
* everything immediately all at once. (Which would be bad,
* because as of this writing, we have a lot of mapped pages we
* never use.) Old kernels will see MCL_ONFAULT and fail with
* EINVAL; we ignore this failure.
*
* N.B. read the man page for mlockall. MCL_CURRENT | MCL_ONFAULT
* pins ⊆ MCL_CURRENT, converging to just MCL_CURRENT as we fault
* in pages.
*/
/* CAP_IPC_LOCK required */
if (mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) {
ALOGW("mlockall failed %s", strerror(errno));
}
/* CAP_NICE required */
struct sched_param param = {
.sched_priority = 1,
};
if (sched_setscheduler(0, SCHED_FIFO | SCHED_RESET_ON_FORK, ¶m)) {
ALOGW("set SCHED_FIFO failed %s", strerror(errno));
}
}
if (init_reaper()) {
ALOGI("Process reaper initialized with %d threads in the pool",
reaper.thread_cnt());
}
if (!watchdog.init()) {
ALOGE("Failed to initialize the watchdog");
}
if(!low_free_kill_init()) {
ALOGE("Failed to initialize the low memory kill");
}
mainloop();
}
android_log_destroy(&ctx);
close_handle_for_perf_iop();
ALOGI("exiting");
return 0;
}
update_props
static void update_props() {
// step 1 :设置vmpressure level对应的oom_adj,这部分应该是mp_event_common用,目前弃用
// low level vmpressure events : low 1001 ; medium 800 ; critical 0 ;super_critical 606
// 现调用mp_event_psi
level_oomadj[VMPRESS_LEVEL_LOW] =
GET_LMK_PROPERTY(int32, "low", OOM_SCORE_ADJ_MAX + 1);
level_oomadj[VMPRESS_LEVEL_MEDIUM] =
GET_LMK_PROPERTY(int32, "medium", 800);
level_oomadj[VMPRESS_LEVEL_CRITICAL] =
GET_LMK_PROPERTY(int32, "critical", 0);
#ifdef QCOM_FEATURE_ENABLE
/* This will gets updated through perf_wait_get_prop. */
level_oomadj[VMPRESS_LEVEL_SUPER_CRITICAL] = 606;
#endif
.....
#ifdef QCOM_FEATURE_ENABLE
// step 2 : Update Perf Properties LmkdImpl::update_perf_props
// 更新很多信息:
LmkdStub::update_perf_props();
#endif
...
#if defined(QCOM_FEATURE_ENABLE) && defined(MI_PERF_FEATURE)
//step 3 : XM_update props LmkdImpl::mi_update_props
// AndoridS后,目前会走这部分逻辑
LmkdStub::mi_update_props();
#endif
}
static int init(void)
static int init(void) {
static struct event_handler_info kernel_poll_hinfo = { 0, kernel_event_handler };
struct reread_data file_data = {
.filename = ZONEINFO_PATH,
.fd = -1,
};
struct epoll_event epev;
int pidfd;
#ifdef QCOM_FEATURE_ENABLE
union meminfo info;
#endif
int i;
int ret;
page_k = sysconf(_SC_PAGESIZE);
if (page_k == -1)
page_k = PAGE_SIZE;
page_k /= 1024;
update_psi_window_size();
#if defined(QCOM_FEATURE_ENABLE) && defined(MI_PERF_FEATURE)
if (!meminfo_parse(&info)) {
LmkdStub::mi_init(page_k, info);
} else {
ULMK_LOG(E, "Failed to parse the meminfo\n");
}
#endif
/*
* Ensure min polling period for supercritical event is no less than
* PSI_POLL_PERIOD_SHORT_MS.
*/
#ifdef QCOM_FEATURE_ENABLE
if (psi_poll_period_scrit_ms < PSI_POLL_PERIOD_SHORT_MS) {
psi_poll_period_scrit_ms = PSI_POLL_PERIOD_SHORT_MS;
}
#endif
epollfd = epoll_create(MAX_EPOLL_EVENTS);
if (epollfd == -1) {
ALOGE("epoll_create failed (errno=%d)", errno);
return -1;
}
// mark data connections as not connected
for (int i = 0; i < MAX_DATA_CONN; i++) {
data_sock[i].sock = -1;
}
ctrl_sock.sock = android_get_control_socket("lmkd");
if (ctrl_sock.sock < 0) {
ALOGE("get lmkd control socket failed");
return -1;
}
ret = listen(ctrl_sock.sock, MAX_DATA_CONN);
if (ret < 0) {
ALOGE("lmkd control socket listen failed (errno=%d)", errno);
return -1;
}
epev.events = EPOLLIN;
ctrl_sock.handler_info.handler = ctrl_connect_handler;
epev.data.ptr = (void *)&(ctrl_sock.handler_info);
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_sock.sock, &epev) == -1) {
ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno);
return -1;
}
maxevents++;
has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
use_inkernel_interface = has_inkernel_module && !enable_userspace_lmk;
if (use_inkernel_interface) {
ALOGI("Using in-kernel low memory killer interface");
if (init_poll_kernel()) {
epev.events = EPOLLIN;
epev.data.ptr = (void*)&kernel_poll_hinfo;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, kpoll_fd, &epev) != 0) {
ALOGE("epoll_ctl for lmk events failed (errno=%d)", errno);
close(kpoll_fd);
kpoll_fd = -1;
} else {
maxevents++;
/* let the others know it does support reporting kills */
property_set("sys.lmk.reportkills", "1");
}
}
} else {
if (!init_monitors()) {
return -1;
}
/* let the others know it does support reporting kills */
property_set("sys.lmk.reportkills", "1");
}
for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) {
procadjslot_list[i].next = &procadjslot_list[i];
procadjslot_list[i].prev = &procadjslot_list[i];
}
memset(killcnt_idx, KILLCNT_INVALID_IDX, sizeof(killcnt_idx));
/*
* Read zoneinfo as the biggest file we read to create and size the initial
* read buffer and avoid memory re-allocations during memory pressure
*/
if (reread_file(&file_data) == NULL) {
ALOGE("Failed to read %s: %s", file_data.filename, strerror(errno));
}
/* check if kernel supports pidfd_open syscall */
pidfd = TEMP_FAILURE_RETRY(pidfd_open(getpid(), 0));
if (pidfd < 0) {
pidfd_supported = (errno != ENOSYS);
} else {
pidfd_supported = true;
close(pidfd);
}
ALOGI("Process polling is %s", pidfd_supported ? "supported" : "not supported" );
return 0;
}
- 创建epoll,用以监听 9 个event;
- 初始化socket /dev/socket/lmkd,并将其添加到epoll 中;
- 根据prop ro.lmk.use_psi 确认是否使用PSI 还是vmpressure;
- 根据prop ro.lmk.use_new_strategy 或者通过 prop ro.lmk.use_minfree_levels 和 prop ro.config.low_ram 使用PSI 时的新策略还是旧策略;
- 新、旧策略主要体现在mp_event_psi 和mp_event_common 的选择, AndroidS /proc/pressure/memory 获取内存压力是否达到some/full 指定来确认是否触发event;
- 后期epoll 的触发主要的处理函数是mp_event_psi 或 mp_event_common;
- extend_reclaim_init
epoll_create
/* 1个socket 监听 lmkd fd dev/socket/lmkd
3个client下发的socket ctrl_connect_handle添加到epoll中
3个pressure init_mp_psi/init_mo_common添加到epoll中
1个监听lmkd事件 但是现在弃用
1个wait for process death,start_wait_for_proc_kill添加到epoll
*/
epollfd = epoll_create(MAX_EPOLL_EVENTS);
if (epollfd == -1) {
ALOGE("epoll_create failed (errno=%d)", errno);
return -1;
}
// mark data connections as not connected
for (int i = 0; i < MAX_DATA_CONN; i++) {
data_sock[i].sock = -1;
}
获取socket并且监听
//socket lmkd
ctrl_sock.sock = android_get_control_socket("lmkd");
if (ctrl_sock.sock < 0) {
ALOGE("get lmkd control socket failed");
return -1;
}
ret = listen(ctrl_sock.sock, MAX_DATA_CONN);
if (ret < 0) {
ALOGE("lmkd control socket listen failed (errno=%d)", errno);
return -1;
}
epev.events = EPOLLIN;
//未完待续....