Android LMKD(2) 源码分析3

作者 by adtxl / 2022-03-02 / 暂无评论 / 608 个足迹

转载自https://justinwei.blog.csdn.net/article/details/122268437

接上一篇Android LMKD(2) 源码分析2

8. mp_event_common

在Android R 中lmkd 是支持旧模式的,在init_mp_psi 的时候,会通过之前确认的是否为new strategy 来确认最终lmkd 处理部分采用的是PSI 监视器策略还是旧模式。

下面是init_mp_psi 中注册的策略处理选择:

static bool init_mp_psi(enum vmpressure_level level, bool use_new_strategy) {
    ...

    vmpressure_hinfo[level].handler = use_new_strategy ? mp_event_psi : mp_event_common;
    vmpressure_hinfo[level].data = level;

本节主要来分析下Android R 中旧模式的处理函数mp_event_common。因为代码比较多,下面分段来剖析。

8.1 step 1. 初始化工作

从代码中看到 Android R 中的lmkd 还支持非psi 监听器的策略,和旧的 kenerl 驱动程序策略。

    if (!use_psi_monitors) {
        /*
         * Check all event counters from low to critical
         * and upgrade to the highest priority one. By reading
         * eventfd we also reset the event counters.
         */
        for (int lvl = VMPRESS_LEVEL_LOW; lvl < VMPRESS_LEVEL_COUNT; lvl++) {
            if (mpevfd[lvl] != -1 &&
                TEMP_FAILURE_RETRY(read(mpevfd[lvl],
                                   &evcount, sizeof(evcount))) > 0 &&
                evcount > 0 && lvl > level) {
                level = static_cast<vmpressure_level>(lvl);
            }
        }
    }

8.2 step 2. 解析meminfo

通过meminfo_parse 函数来解析节点/proc/meminfo,感兴趣可以看源码,这里主要注意的是:

static int meminfo_parse(union meminfo *mi) {
    ...
    mi->field.nr_file_pages = mi->field.cached + mi->field.swap_cached +
        mi->field.buffers;

    return 0;
}

会根据meminfo 统计nr_file_pages

8.3 step 3. 解析zoneinfo

通过zoneinfo_parse 函数来解析节点/proc/zoneinfo,主要是:

static int zoneinfo_parse(struct zoneinfo *zi) {
    ...
    node->zone_count = zone_idx + 1;
    zi->node_count = node_idx + 1;

    /* calculate totals fields */
    for (node_idx = 0; node_idx < zi->node_count; node_idx++) {
        node = &zi->nodes[node_idx];
        for (zone_idx = 0; zone_idx < node->zone_count; zone_idx++) {
            struct zoneinfo_zone *zone = &zi->nodes[node_idx].zones[zone_idx];
            zi->totalreserve_pages += zone->max_protection + zone->fields.field.high;
        }
        zi->total_inactive_file += node->fields.field.nr_inactive_file;
        zi->total_active_file += node->fields.field.nr_active_file;
        zi->total_workingset_refault += node->fields.field.workingset_refault;
    }
    return 0;
}

显示分别解析zone 的各个node,然后再统一计算zone 的:

  • totalreserve_pages
  • total_inactive_file
  • total_active_file
  • total_workingset_refault

8.4 step 4. 找到此时的min_score_adj

这个是mp_event_common算法的核心,通过需要内存的压力等级,确认最终需要kill的进程。

比较重要的一个变量就是user_minfree_levels,即直接使用minfree 等级,即直接根据内存free 情况与lowmem_minfree数组中的值比较:

static int lowmem_adj[MAX_TARGETS];
static int lowmem_minfree[MAX_TARGETS];

这两个数组变量的初始化时在7.5 节cmd_target 中从AMS 中设置下来的。

变量user_minfree_levels 是根据prop ro.lmk.use_minfree_levels 的值两种方式:

  • 使用minfree_level (true)
  • 不使用minfree_level (false)

如果使用minfree_level,则通过判断当前的other_freeother_file 是否满足lowmem_minfree,如果达到了水位,则会记录min_score_adj,最终去kill 符合oom_adj > min_score_adj的进程。

    if (use_minfree_levels) {
        int i;

        other_free = mi.field.nr_free_pages - zi.totalreserve_pages;
        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++) {
            minfree = lowmem_minfree[i];
            if (other_free < minfree && other_file < minfree) {
                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;
        }

        goto do_kill;
    }

other_free 是meminfo 中free size 转换为页数,再与zoneinfo 中totalreserve_pages 的差值;

other_file是出去share mem、swap cache 和unevictable 之后的剩余page;

如果两者都小于minfree 数组中的一个元素,说明出现了 low memory,需要记录min_score_adj然后进行kill,否则认为没有触发low memory。

下图是整理的大致示意图:

image.png

详细的可以查看另一篇博文:Android lmkd中adj score的算法剖析

如果不是使用minfree_level,仍使用旧模式的策略,通过mem pressure 计算出最终的level(low、mdeium、critical),然后根据level 从level_oomadj 数组中确定最终的min_score_adj:
(ps:这里说的不对,根据google文档,minfree_level属于旧模式的策略。vmpressure是推荐使用的)

        if (!use_minfree_levels) {
            ...
            min_score_adj = level_oomadj[level];
        }

8.5 step 5. find_and_kill_process

不管使用minfree_level或者其它策略,最终都会根据min_score_adj 去kill process:

pages_freed = find_and_kill_process(min_score_adj, NONE, NULL, &mi, &wi, &curr_tm);

其中mi 是meminfo、wi 是wakeup_info。

代码逻辑后面会补充说明,详细看第 9 节。

9. find_and_kill_process

/*
 * Find one process to kill at or above the given oom_score_adj level.
 * Returns size of the killed process.
 */
static int find_and_kill_process(int min_score_adj, struct kill_info *ki, union meminfo *mi,
                                 struct wakeup_info *wi, struct timespec *tm) {
    int i;
    int killed_size = 0;
    bool lmk_state_change_start = false;
    bool choose_heaviest_task = kill_heaviest_task;

    for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) {
        struct proc *procp;

        if (!choose_heaviest_task && i <= PERCEPTIBLE_APP_ADJ) {
            /*
             * If we have to choose a perceptible process, choose the heaviest one to
             * hopefully minimize the number of victims.
             */
            choose_heaviest_task = true;
        }

        while (true) {
            procp = choose_heaviest_task ?
                proc_get_heaviest(i) : proc_adj_lru(i);

            if (!procp)
                break;

            killed_size = kill_one_process(procp, min_score_adj, ki, mi, wi, tm);
            if (killed_size >= 0) {
                if (!lmk_state_change_start) {
                    lmk_state_change_start = true;
                    stats_write_lmk_state_changed(STATE_START);
                }
                break;
            }
        }
        if (killed_size) {
            break;
        }
    }

    if (lmk_state_change_start) {
        stats_write_lmk_state_changed(STATE_STOP);
    }

    return killed_size;
}

代码的逻辑很清晰,通过mp_event_common 或者mp_event_psi 处理后会根据内存使用情况确定oom adj 的限度,并根据该oom adj 会从 OOM_SCORE_ADJ_MAX 开始选择一个进程进行kill 用以释放内存,而该进程procp 的选择有两种方式(LRU 或 heaviest),确定最终的procp 后会调用kill_one_process() 进行最后的kill 操作。

9.1 变量choose_heaviest_task的确定

choose_heaviest_task 用以确定是否kill 使用内存最多的进程。

有两种方式确定:

  • 根据prop ro.lmk.kill_heaviest_task 的值;(请注意,即使把该属性设为false,choose_heaviest_task根据判断条件仍可能为true)
  • oom adj 是否为perceptible app(adj 为200),对于mp_event_psi 尤其突出这个oom adj,应该是在这里用以确定是否kill heaviest task;

如果选择heaviest 方式会从procadjslot_list 找到占用内存最多(rss) 的proc,其中结构体数组procadjslot_list 是在cmd_procprio 中添加,详细看7.1 节。另外需要注意的是,进程proc 的内存是根据procadjslot_list 中proc 进程的pid 查看节点 /proc/pid/statm

    snprintf(path, PATH_MAX, "/proc/%d/statm", pid);
    fd = open(path, O_RDONLY | O_CLOEXEC);
    if (fd == -1)
        return -1;

    ret = read_all(fd, line, sizeof(line) - 1);
    ...

    sscanf(line, "%d %d ", &total, &rss);
    close(fd);
    return rss;

如果不是选择heaviest,则使用LRU 方式:

            procp = choose_heaviest_task ?
            proc_get_heaviest(i) : proc_adj_lru(i);

这里补充下/proc/pid/statm

frost:/ # cat /proc/1822/statm
3568153 52137 38465 7 0 311617 0

分别对应:

Field            Content

size           total program size (pages)        (same as VmSize in status)

resident    size of memory portions (pages)   (same as VmRSS in status)

trs             number of pages that are 'code'   (i.e. backed by a file, same as RssFile+RssShmem in status)

lrs            number of pages of library        (always 0 on 2.6)

drs           number of pages of data/stack   (including libs; broken, includes library text)

dt             number of dirty pages         (always 0 on 2.6) 

9.2 kill_one_process

代码这里就不贴了,所有信息都确定了,只需要进行kill 操作即可,只不过在lmkd 中kill 进程会有一定的阻塞:

    if (pidfd < 0) {
        start_wait_for_proc_kill(pid);
        r = kill(pid, SIGKILL);
    } else {
        start_wait_for_proc_kill(pidfd);
        r = sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
    }

函数start_wait_for_proc_kill 参数是将要kill 的进程的pid fd,将其添加到epoll 中进行监听。

并通过sys_pidfd_send_signal 发送SIGKILL 信号进行kill 操作。

独特见解