> 文章列表 > LMKD分享

LMKD分享

LMKD分享

背景

  1. Android是一个多任务系统,可以同时运行多个程序,一般来说,启动运行一个程序是有一定的时间开销的,因此为了加快运行速度,当你退出一个程序时,Android并不会立即杀掉它,这样下次再运行该程序时,可以很快的启动。随着系统中保留的程序越来越多,内存肯定会出现不足,这个时候Android系统杀进程的刽子手---Lowmemory Killer就起作用了。        
  2. Android 使用内核中的 lowmemorykiller驱动程序来监控系统内存压力,该驱动程序是一种依赖于硬编码值的严格机制。从内核 4.12 开始,lowmemorykiller 驱动程序已从上游内核中移除,用户空间 lmkd会执行内存监控以及进程终止任务。        
  3. 用户空间 lmkd 可实现与内核中的驱动程序相同的功能,但它使用现有的内核机制检测和估测内存压力。这些机制包括使用内核生成的 vmpressure 事件或压力失速信息 (PSI) 监视器来获取关于内存压力级别的通知,以及使用内存 cgroup 功能限制分配给每个进程的内存资源(根据每个进程的重要性)。
  4. 参考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

  1. Android 10 及更高版本支持新的 lmkd 模式,它使用内核压力失速信息 (PSI) 监视器来检测内存压力。上游内核中的 PSI 补丁程序集(反向移植到 4.9 和 4.14 内核)测量由于内存不足而导致任务延迟的时间。由于这些延迟会直接影响用户体验,因此它们代表了确定内存压力严重性的便捷指标。上游内核还包括 PSI 监视器,该监视器允许特权用户空间进程(例如 lmkd)指定这些延迟的阈值,并在突破阈值时从内核订阅事件。        
  2. PSI是Pressure stall information的简称,记录CPU/Memory/IO的压力信息,达到用户空间自定义的预值之后通知用户空间。
  3. 详细见官方文档:https://facebookmicrosites.github.io/psi/docs/overview.html
  4. 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):

  1. computeOomAdjLocked:计算adj(对优先级高于cache和empty的进程进行adj的分配)。该方法执行是在updateOomAdjLocked中。
  2. updateOomAdjLocked:更新adj(分配computeOomAdjLocked没有处理的cache和empty优先级的进程adj)
  3. 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函数中,主要做了如下几件事:

  1. 读取配置参数
  2. 初始化 epoll 事件监听
  3. 锁住内存页
  4. 设置进程调度器
  5. 循环处理事件

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, &param)) {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_psilevel_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_ENABLEunion meminfo info;
#endifint 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_ENABLEif (psi_poll_period_scrit_ms < PSI_POLL_PERIOD_SHORT_MS) {psi_poll_period_scrit_ms = PSI_POLL_PERIOD_SHORT_MS;}
#endifepollfd = epoll_create(MAX_EPOLL_EVENTS);if (epollfd == -1) {ALOGE("epoll_create failed (errno=%d)", errno);return -1;}// mark data connections as not connectedfor (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 connectedfor (int i = 0; i < MAX_DATA_CONN; i++) {data_sock[i].sock = -1;}

获取socket并且监听

//socket lmkdctrl_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;

//未完待续....