Android用户空间lowmemorykiller

2020-08-05  本文已影响0人  superme_

为了提高性能,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)) {

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

上一篇下一篇

猜你喜欢

热点阅读