Android low_mem_kill

本文基于AndroidQ

属性 使用 默认
ro.lmk.use_minfree_levels 使用可用内存和文件缓存阈值来做出进程终止决策 即与内核中的 LMK 驱动程序的功能一致 false
ro.lmk.kill_heaviest_task 终止符合条件的最繁重任务()最佳决策) 与终止符合条件的任何任务(快速决策) true
ro.lmk.debug 启用 lmkd 调试日志。 false
ro.lmk.use_psi 使用 PSI 监视器(而不是 vmpressure 事件) /proc/pressure/ true
ro.lmk.medium 在中等 vmpressure 水平下可被终止的进程的最低 oom_adj 得分。 800 已缓存或非必要服务)
system/core/lmkd/README.md:38:                             killed at low vmpressure level. Default = 1001
system/core/lmkd/README.md:42:                             killed at medium vmpressure level. Default = 800
system/core/lmkd/README.md:46:                             killed at critical vmpressure level. Default = 0

初始化

内核lowmemkill优先级最高,其次是psi初始化init_psi_monitors(),psi也没有就初始化vmpressure调用 init_mp_common()

  /* gid containing AID_SYSTEM required */
  #define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree"
  #define INKERNEL_ADJ_PATH "/sys/module/lowmemorykiller/parameters/adj"

    has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
    use_inkernel_interface = has_inkernel_module;

    if (use_inkernel_interface) {
        ALOGI("Using in-kernel low memory killer interface");
    } else {
        /* Try to use psi monitor first if kernel has it */
        use_psi_monitors = property_get_bool("ro.lmk.use_psi", true) &&
            init_psi_monitors();
        /* 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 -1;
        }
        if (use_psi_monitors) {
            ALOGI("Using psi monitors for memory pressure detection");
        } else {
            ALOGI("Using vmpressure for memory pressure detection");
        }
    }

我们是pis,这里只看psi部分,
init_psi_monitor()函数主要/proc/pressure/memory中写入数据,并返回fd
mp_event_common()函数是事件处理函数
register_psi_monitor()函数封装了epoll_ctl,做了EPOLL_CTL_ADD操作

#define PSI_MON_FILE_MEMORY "/proc/pressure/memory"

static bool init_mp_psi(enum vmpressure_level level) {
    int 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;
    }

    vmpressure_hinfo[level].handler = mp_event_common;
    vmpressure_hinfo[level].data = level;
    if (register_psi_monitor(epollfd, fd, &vmpressure_hinfo[level]) < 0) {
        destroy_psi_monitor(fd);
        return false;
    }
    maxevents++;
    mpevfd[level] = fd;

    return true;
}

mainloop()中回调mp_event_common()函数,做出相应处理

static void mainloop(void) {
        ....
        nevents = epoll_wait(epollfd, events, maxevents, -1);        

        /* Second pass to handle all other events */
        for (i = 0, evt = &events[0]; i < nevents; ++i, evt++) {
            if (evt->events & EPOLLERR)
                ALOGD("EPOLLERR on event #%d", i);
            if (evt->events & EPOLLHUP) {
                /* This case was handled in the first pass */
                continue;
            }
            if (evt->data.ptr) {
                handler_info = (struct event_handler_info*)evt->data.ptr;
                handler_info->handler(handler_info->data, evt->events);  // 回调mp_event_common

                if (use_psi_monitors && handler_info->handler == mp_event_common) {
                    /*
                     * Poll for the duration of PSI_WINDOW_SIZE_MS after the
                     * initial PSI event because psi events are rate-limited
                     * at one per sec.
                     */
                    polling = PSI_POLL_COUNT;
                    poll_handler = handler_info;
                    clock_gettime(CLOCK_MONOTONIC_COARSE, &last_report_tm);
                }
            }
        }
}

psi参考

mp_event_common

mp_event_common函数前,先认识两个结构体meminfozoneinfo,有利于后面阅读

meminfo_parse 解析/proc/meminfo

nr_free_pages对应MemFree
nr_file_pages对应Buffers+cached+swap_cached

static int meminfo_parse(union meminfo *mi) {
    static struct reread_data file_data = {
        .filename = MEMINFO_PATH, //   /proc/meminfo
        .fd = -1,
    };
    char buf[PAGE_SIZE];
    char *save_ptr;
    char *line;

    memset(mi, 0, sizeof(union meminfo));

    if (reread_file(&file_data, buf, sizeof(buf)) < 0) {
        return -1;
    }

    for (line = strtok_r(buf, "\n", &save_ptr); line;
         line = strtok_r(NULL, "\n", &save_ptr)) {
        if (!meminfo_parse_line(line, mi)) {
            ALOGE("%s parse error", file_data.filename);
            return -1;
        }
    }
    
    /*  
        /proc/meminfo
        Buffers:           27964 kB
        Cached:          5084224 kB
        SwapCached:            0 kB
    */
    mi->field.nr_file_pages = mi->field.cached + mi->field.swap_cached +
        mi->field.buffers;
    
    return 0;
}

zoneinfo_parse解析/proc/zoneinfo, 可以看出totalreserve_pages就是high+ protection累加

static int zoneinfo_parse(union zoneinfo *zi) {
    static struct reread_data file_data = {
        .filename = ZONEINFO_PATH, // /proc/zoneinfo
        .fd = -1,
    };
    char buf[PAGE_SIZE];
    char *save_ptr;
    char *line;

    memset(zi, 0, sizeof(union zoneinfo));

    if (reread_file(&file_data, buf, sizeof(buf)) < 0) {
        return -1;
    }

    for (line = strtok_r(buf, "\n", &save_ptr); line;
         line = strtok_r(NULL, "\n", &save_ptr)) {
        if (!zoneinfo_parse_line(line, zi)) {
            ALOGE("%s parse error", file_data.filename);
            return -1;
        }
    }
    /*  zoneinfo_parse_line 还有一段代码,这里就不贴了
        if (!strcmp(cp, "protection:")) {
            zi->field.totalreserve_pages +=
                zoneinfo_parse_protection(ap);
        }
        所以 totalreserve_pages = high + protection
     */
    zi->field.totalreserve_pages += zi->field.high;

    return 0;
}

一种是开启use_minfree_levels,如果满足条件直接return,不进入do_kill了

lowmem_minfree 可以通过属性sys.lmk.minfree_levels查看, 通过other_free 和 lowmem_minfree 比较,判断是否回收进程

static void mp_event_common(int data, uint32_t events __unused) {
    .... 省略
    
    //meminfo_parse  解析 /proc/meminfo,完成mi初始化
    //zoneinfo_parse 解析 /proc/zoneinfo,完成zi初始化
    if (meminfo_parse(&mi) < 0 || zoneinfo_parse(&zi) < 0) {
        ALOGE("Failed to get free memory!");
        return;
    }

    if (use_minfree_levels) {
        int i;
        //计算剩余内存 meminfo.memfree - (zoneinfo.high + zoneinfo.protected)
        other_free = mi.field.nr_free_pages - zi.field.totalreserve_pages;

        // 计算meminfo内存页 
        // (buffers + swap_cached + cached) > (unevictable + shmem + swap_cached)
        if (mi.field.nr_file_pages > (mi.field.shmem + mi.field.unevictable + mi.field.swap_cached)) {
            other_file = (mi.field.nr_file_pages - mi.field.shmem -
                          mi.field.unevictable - mi.field.swap_cached);
        } else {
            other_file = 0; //没有内存页
        }

        min_score_adj = OOM_SCORE_ADJ_MAX + 1;
        for (i = 0; i < lowmem_targets_size; i++) {
            // 详见1.2
            // lowmem_minfree 可以通过属性`sys.lmk.minfree_levels`查看 
            minfree = lowmem_minfree[i];
            if (other_free < minfree && other_file < minfree) {
                // 触发do_kill
                min_score_adj = lowmem_adj[i];
                break;
            }
        }

        if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
            if (debug_process_killing) {
                ALOGI("Ignore %s memory pressure event "
                      "(free memory=%ldkB, cache=%ldkB, limit=%ldkB)",
                      level_name[level], other_free * page_k, other_file * page_k,
                      (long)lowmem_minfree[lowmem_targets_size - 1] * page_k);
            }
            return;
        }
        // 见1.3
        goto do_kill;
    }
  
    if (level == VMPRESS_LEVEL_LOW) {
        record_low_pressure_levels(&mi);  //记录压力位
    }

    if (level_oomadj[level] > OOM_SCORE_ADJ_MAX) {
        /* Do not monitor this pressure level */
        return;
    }

    if ((mem_usage = get_memory_usage(&mem_usage_file_data)) < 0) {
        goto do_kill;
    }
    if ((memsw_usage = get_memory_usage(&memsw_usage_file_data)) < 0) {
        goto do_kill;
    }
    ...
}

lowmem_minfree的赋值

static void cmd_target(int ntargets, LMKD_CTRL_PACKET packet)
{
    ... //省略部分
    
    char minfree_str[PROPERTY_VALUE_MAX];
    char *pstr = minfree_str;
    char *pend = minfree_str + sizeof(minfree_str);

    // packet 是am 通过socket传递
    for (i = 0; i < ntargets; i++) {
        lmkd_pack_get_target(packet, i, &target);
        lowmem_minfree[i] = target.minfree;
        lowmem_adj[i] = target.oom_adj_score;
        // pstr指向minfree_str的地址
        pstr += snprintf(pstr, pend - pstr, "%d:%d,", target.minfree,
            target.oom_adj_score);
        if (pstr >= pend) {
            /* if no more space in the buffer then terminate the loop */
            pstr = pend;
            break;
        }
    }

    lowmem_targets_size = ntargets;

    /* Override the last extra comma */
    pstr[-1] = '\0';
    property_set("sys.lmk.minfree_levels", minfree_str);

}

开启use_minfree_levels的情况下是要么跳到do_kill,要么直接return,不会走record_low_pressure_levels函数,所以这函数就是给第二种情况使用不开启use_minfree_levels来更新压力位

当前meminfo free减去之前的记录的max_nr_free_pages,波动不超过的10%才更新记录low_pressure_mem.max_nr_free_pages

void record_low_pressure_levels(union meminfo *mi) {
    if (low_pressure_mem.min_nr_free_pages == -1 ||
        low_pressure_mem.min_nr_free_pages > mi->field.nr_free_pages) {
        if (debug_process_killing) {
            ALOGI("Low pressure min memory update from %" PRId64 " to %" PRId64,
                low_pressure_mem.min_nr_free_pages, mi->field.nr_free_pages);
        }
        low_pressure_mem.min_nr_free_pages = mi->field.nr_free_pages;
    }
    /*
     * Free memory at low vmpressure events occasionally gets spikes,
     * possibly a stale low vmpressure event with memory already
     * freed up (no memory pressure should have been reported).
     * Ignore large jumps in max_nr_free_pages that would mess up our stats.
     */

    // 波动不超过10% 才更新记录max_nr_free_pages 
    if (low_pressure_mem.max_nr_free_pages == -1 ||
        (low_pressure_mem.max_nr_free_pages < mi->field.nr_free_pages &&
         mi->field.nr_free_pages - low_pressure_mem.max_nr_free_pages <
         low_pressure_mem.max_nr_free_pages * 0.1)) {
        if (debug_process_killing) {
            ALOGI("Low pressure max memory update from %" PRId64 " to %" PRId64,
                low_pressure_mem.max_nr_free_pages, mi->field.nr_free_pages);
        }
        low_pressure_mem.max_nr_free_pages = mi->field.nr_free_pages;
    }
}

当前meminfo free小于压力位low_pressure_mem.max_nr_free_pages就开始干活了kill进程了

do_kill:
    if (low_ram_device) {
        /* For Go devices kill only one task */
        if (find_and_kill_process(level_oomadj[level]) == 0) {
            if (debug_process_killing) {
                ALOGI("Nothing to kill");
            }
        } else {
            meminfo_log(&mi);
        }
    } else {
        int pages_freed;
        static struct timespec last_report_tm;
        static unsigned long report_skip_count = 0;

        if (!use_minfree_levels) {
            /* Free up enough memory to downgrate the memory pressure to low level */
            if (mi.field.nr_free_pages >= low_pressure_mem.max_nr_free_pages) {
                if (debug_process_killing) {
                    ALOGI("Ignoring pressure since more memory is "
                        "available (%" PRId64 ") than watermark (%" PRId64 ")",
                        mi.field.nr_free_pages, low_pressure_mem.max_nr_free_pages);
                }
                return;
            }
            min_score_adj = level_oomadj[level];
        }

        pages_freed = find_and_kill_process(min_score_adj);

总结

如果使用use_minfree_levels则是计算meminfo.memfree - zoneinfo.high 并且 meminfo.buffers + mem.cached 是否小于minfree_levels属性值

130|console:/ # getprop |grep lmk
[init.svc.lmkd]: [running]
[ro.boottime.lmkd]: [104017140428644]
[ro.lmk.debug]: [true]
[ro.lmk.kill_heaviest_task]: [true]
[ro.lmk.kill_timeout_ms]: [15]
[ro.lmk.use_minfree_levels]: [true]
[sys.lmk.minfree_levels]: [18432:0,23040:100,27648:200,32256:250,55296:900,80640:950]

80640:950为例: (一页等于4K)
内存低于80640 * 4 / 1024 = 315MB则killoom_adj大于950的进程

开启use_minfree_levels情况下,通过meminfo和zoneinfo计算出other_free,然后比较minfree_levels,判断是否回收进程
不开启use_minfree_levels情况下,使用meminfo free比较low_pressure_mem.max_nr_free_pages,判断是否回收进程,通过record_low_pressure_levels()更新low_pressure_mem

posted @ 2022-01-04 11:54  梦过无声  阅读(836)  评论(0编辑  收藏  举报