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分享_第1张图片

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分享_第2张图片

LMKD 的演变过程

lmkd(Low Memory Killer Daemon)是低内存终止守护进程,用来监控运行中android系统内存的状态,通过终止最不必要的进程来应对内存压力较高的问题,使系统以可接受的水平运行。

Android 版本

所处空间

杀进程时机

8.1 之前

kernel

监听 kswapd 触发的 shrink 回调

8.1 - 9.0

userspace

监听 vmpressure

10

userspace

监听 psi

相关配置属性

LMKD分享_第3张图片

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

LMKD分享_第4张图片

vg10 、avg60 、avg300分别代表 10s、60s、300s 的时间周期内的阻塞时间百分比。total 是总累计时间,以毫秒为单位。

some 这一行,代表至少有一个任务在某个资源上阻塞的时间占比,full 这一行,代表所有的非idle任务同时被阻塞的时间占比.

PSI 中的 full 与 some

some : 至少有一个任务在某个资源上阻塞时间占比 

LMKD分享_第5张图片

full : 所有任务同时阻塞的时间占比

LMKD分享_第6张图片

参考:纯干货,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交互

LMKD分享_第7张图片

 在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, ¶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;

//未完待续....

你可能感兴趣的:(#,功耗/性能/内存管理,android,LMKD)