美文网首页
Android用户空间lowmemorykiller

Android用户空间lowmemorykiller

作者: superme_ | 来源:发表于2020-08-05 20:31 被阅读0次

    为了提高性能,Android系统中进程的匿名页、文件页按照一定的策略进行缓存,在内存紧张的时候再进行回收。但内存回收并不总是理想的,在一定条件下,为了保证系统的正常运行,会采用更加激进、直接的方式——杀进程,也就是这里要介绍的low memory killer(lmk)。

    lmk有内核空间和用户空间两种实现,如果getprop ro.lmk.enable_userspace_lmk为false,同时/sys/module/lowmemorykiller/parameters/minfree节点存在且可被lmkd访问就用内核lmk,否则用后者。本文介绍用户空间lmk。

    注册监听

    1.1 内存压力监听

    用户空间lmk内存压力事件上报方式有vmpressure和psi两种方式,通过属性ro.lmk.use_psi来控制使用哪个。

    a) psi

    file: system/core/lmkd/lmkd.c

    2252 use_psi_monitors = property_get_bool("ro.lmk.use_psi", true) &&

    2253    init_psi_monitors(); 

    2120static bool init_psi_monitors() {

            //注册3个级别的内存压力事件监听

    2121    if (!init_mp_psi(VMPRESS_LEVEL_LOW)) .......

    2124    if (!init_mp_psi(VMPRESS_LEVEL_MEDIUM)) ......

    2128    if (!init_mp_psi(VMPRESS_LEVEL_CRITICAL)) ......

    2134}

    注册过程如下:

    file: system/core/lmkd/lmkd.c

    1) 打开/proc/pressure/memory节点并写入"stall_type threshold_ms PSI_WINDOW_SIZE_MS"。

    PSI_WINDOW_SIZE_MS为1000ms,stall_type和threshold_ms分别为:

    170static struct psi_threshold psi_thresholds[VMPRESS_LEVEL_COUNT] = {

    171    { PSI_SOME, 70 },    /* 70ms out of 1sec for partial stall */

    172    { PSI_SOME, 100 },  /* 100ms out of 1sec for partial stall */

    173    { PSI_FULL, 70 },    /* 70ms out of 1sec for complete stall */

    174};

    其中partial stall指的是该时间段内有一个或多个task因为缺少资源而等待,

    complete stall指的是该时间段内所有的task都因得不到资源而等待。

    psi可以是针对system-wide的,也可以是per-cgroup的。

    2) 注册到epoll,回调函数为mp_event_common,通过data可以得到内存压力级别。

    2097  vmpressure_hinfo[level].handler = mp_event_common;

    //level对应VMPRESS_LEVEL_LOW/MEDIUM/CRITICAL

    2098  vmpressure_hinfo[level].data = level;

    80    struct epoll_event epev;

    82    epev.events = EPOLLPRI;

    83    epev.data.ptr = &vmpressure_hinfo;

    84    res = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &epev);

    b) vmpressure

    file: system/core/lmkd/lmkd.c

    //同样是注册三个级别的内存压力监听,而且可以看到psi的优先级更高

    2255        if (!use_psi_monitors &&

    2256            (!init_mp_common(VMPRESS_LEVEL_LOW) ||

    2257            !init_mp_common(VMPRESS_LEVEL_MEDIUM) ||

    2258            !init_mp_common(VMPRESS_LEVEL_CRITICAL))) {

    2260            return -1;

    2261        }

    注册过程如下:

    file: system/core/lmkd/lmkd.c

    79#define MEMCG_SYSFS_PATH "/dev/memcg/"

    2136static bool init_mp_common(enum vmpressure_level level) {

    1) // 向/dev/memcg/cgroup.event_control节点写入"evfd mpfd levelstr"即可注册监听,

    其中evfd告诉内核事件发生后通知谁,

    mpfd表示监听的是什么事件(这里为memory.pressure_level),

    levelstr表示监听内存压力级别,可以是low,medium,critical

    2147    mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level",O_RDONLY|O_CLOEXEC);

    2153    evctlfd = open(MEMCG_SYSFS_PATH "cgroup.event_control",O_WRONLY|O_CLOEXEC);

    2159    evfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);

    2165    ret = snprintf(buf, sizeof(buf), "%d %d %s", evfd, mpfd, levelstr);

    2171    ret = TEMP_FAILURE_RETRY(write(evctlfd, buf, strlen(buf) + 1));

    2) //将evfd添加到epoll中,回调函数为mp_event_common,通过data部分可以知道内存压力级别。

    2178    epev.events = EPOLLIN;

    2179    /* use data to store event level */

    2180    vmpressure_hinfo[level_idx].data = level_idx;

    2181    vmpressure_hinfo[level_idx].handler = mp_event_common;

    2182    epev.data.ptr = (void *)&vmpressure_hinfo[level_idx];

    2183    ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, evfd, &epev);

    2201}

    1.2 客户端socket监听

    2203static int init(void) {

    2224    ctrl_sock.sock = android_get_control_socket("lmkd");

    2230    ret = listen(ctrl_sock.sock, MAX_DATA_CONN);

    2236    epev.events = EPOLLIN;

    2237    ctrl_sock.handler_info.handler = ctrl_connect_handler;

    2238    epev.data.ptr = (void *)&(ctrl_sock.handler_info);

    2239    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_sock.sock, &epev) == -1) {

    2. 事件通知

    2.1 PSI

    使用psi_memstall_enter和psi_memstall_leave包裹相关内存操作,实现事件信息统计。这些操作有以下几种:

    kernel/msm-4.14/mm/vmscan.c

    在try_to_free_mem_cgroup_pages函数中,调用do_try_to_free_pages进行内存回收的时候

    kswapd在调用balance_pgdat进行内存回收的时候

    kernel/msm-4.14/mm/compaction.c

    kcompactd函数在进行内存压缩的时候

    kernel/msm-4.14/mm/page_alloc.c

    调用__alloc_pages_direct_compact进行内存压缩的时候

    调用__perform_reclaim进行内存回收的时候

    kernel/msm-4.14/mm/filemap.c

    调用wait_on_page_locked,等待文件页就绪的时候

    //满足注册条件后开始上报

    2.2 vmpressure

    通过以下几条路径触发事件的上报:

    kernel/msm-4.14/mm/vmscan.c:

    //分配物理页时,水位不满足要求,触发内存回收时上报

    __alloc_pages ->__alloc_pages_nodemask -> get_page_from_freelist

    -> node_reclaim -> __node_reclaim -> shrink_node -> vmpressure

    //分配物理页时,进入slow path,进行直接内存回收的过程中上报

    __alloc_pages -> __alloc_pages_nodemask -> __alloc_pages_slowpath

    -> __alloc_pages_direct_reclaim -> __perform_reclaim -> try_to_free_pages

    -> do_try_to_free_pages -> shrink_zones -> shrink_node -> vmpressure

    //回收cgroup内存的过程中上报

    try_to_free_mem_cgroup_pages -> do_try_to_free_pages -> shrink_zones

    -> shrink_node -> vmpressure

    //分配物理页时,进入slow path,唤醒kswapd,kswapd在回收内存的过程中上报

    kswapd -> balance_pgdat -> kswapd_shrink_node ->  shrink_node -> vmpressure

    //vmpressure中进行事件上报

    其中存在变量 vmpressure_win = SWAP_CLUSTER_MAX*16;

    3. 事件处理

    3.1 内存压力事件处理

    lmk使用minfree和oom_score_adj来决定杀进程的策略。比如如下一组配置, /sys/module/lowmemorykiller/parameters/minfree :15360,19200,23040,26880,34415,43737;/sys/module/lowmemorykiller/parameters/adj : 0,1,2,4,9,12 表示当系统可用内存低于26880个page的时候杀oom_score_adj>=4的进程。具体逻辑在回调函数mp_event_common中,包含两个步骤,首先根据当前系统的可用内存状态定位到minfree的某一档,进而确定相应的min oom_score_adj,细节如下代码所示:

    // 其中mi.field是解析/proc/meminfo得到的

    1930        other_free = mi.field.nr_free_pages - zi.field.totalreserve_pages;

    1931        if (mi.field.nr_file_pages > (mi.field.shmem + mi.field.unevictable +

                    mi.field.swap_cached)) {

    1932            other_file = (mi.field.nr_file_pages - mi.field.shmem -

    1933                          mi.field.unevictable - mi.field.swap_cached);

    1934        } else {

    1935            other_file = 0;

    1936        }

    1937

    1938        min_score_adj = OOM_SCORE_ADJ_MAX + 1;

    1939        for (i = 0; i < lowmem_targets_size; i++) {

    1940            minfree = lowmem_minfree[i];

    1941            if (other_free < minfree && other_file < minfree) {

    1942                min_score_adj = lowmem_adj[i];

    然后就是从OOM_SCORE_ADJ_MAX到min oom_score_adj遍历,从属于当前oom_score_adj的进程中选择一个进行kill,选择策略有两种,如果ro.lmk.kill_heaviest_task属性设为true,则解析/proc/pid/statm选择占用物理内存最大的进程,否则按照LRU的原则选择。

    3.2 客户端事件处理

    a) minfree和adj

    //1. 系统启动过程中,客户端向lmkd发送minfree和adj信息

    SystemServer.java : startOtherServices()

    -> WindowManagerService.java : displayReady()

    -> ActivityTaskManagerService.java : updateConfiguration()

    -> ActivityManagerService.java: updateOomLevelsForDisplay()

    -> ProcessList.java : applyDisplaySize() -> ProcessList.java :

    updateOomLevels() :

    601    private void updateOomLevels(int displayWidth, int displayHeight,

          boolean write) {

    682            ByteBuffer buf = ByteBuffer.allocate(4 * (2 * mOomAdj.length + 1));

    683            buf.putInt(LMK_TARGET);

    684            for (int i = 0; i < mOomAdj.length; i++) {

    685                buf.putInt((mOomMinFree[i] * 1024)/PAGE_SIZE);

    686                buf.putInt(mOomAdj[i]);

    687            }

    689            writeLmkd(buf, null);

    //2. lmkd将接收到的信息,对应写入/sys/module/lowmemorykiller/parameters/minfree和

        /sys/module/lowmemorykiller/parameters/adj节点

    b) app进程修改adj

    //1. 不同类型的app进程有着不同的adj,比如FOREGROUND_APP_ADJ为0,SERVICE_ADJ为500等,

    // 这样就可以在内存不足的情况下杀掉不重要的进程来缓解内存压力。

    // 同一个app进程因其状态的变化,比如前后台切换,其adj也是不断变化的,

    // 因此需要调用updateOomAdjLocked来进行更新。

    ActivityManagerService.java :

    updateOomAdjLocked()

    -> OomAdjuster.java : applyOomAdjLocked()

    -> ProcessList.java : setOomAdj() :

    1185    public static void setOomAdj(int pid, int uid, int amt) {

    1194        ByteBuffer buf = ByteBuffer.allocate(4 * 4);

    1195        buf.putInt(LMK_PROCPRIO);

    1196        buf.putInt(pid);

    1197        buf.putInt(uid);

    1198        buf.putInt(amt);

    1199        writeLmkd(buf, null);

    //2. 将adj写入/proc/pid/oom_score_adj节点

    system/core/lmkd/lmkd.c

    971    case LMK_PROCPRIO:

    974        cmd_procprio(packet);

    617static void cmd_procprio(LMKD_CTRL_PACKET packet) {

    646    snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);

    647    snprintf(val, sizeof(val), "%d", params.oomadj);

    648    if (!writefilestring(path, val, false)) {

    ---------------------

    相关文章

      网友评论

          本文标题:Android用户空间lowmemorykiller

          本文链接:https://www.haomeiwen.com/subject/hzynrktx.html