LowMemoryKiller机制分析

一、概述

Linux系统当可用内存较低的时候oom killer机制会根据一定的规则去杀掉一些进程来释放内存,而Android系统的LowMemoryKiller机制就是以此功能为基础做了一些调整。Android系统中的APP在使用完成之后并不会马上被杀掉,而是驻留在内存中,当下一次在此进入此应用的时候可以省去进程创建的过程,加快启动速度。LowMemoryKiller机制会在内存资源紧张的时候,杀掉一些进程来回收内存。

二、整体架构

在这里插入图片描述
1.AMS部分的ProcessList
2.Native进程lmkd
3.内核中的LowMemoryKiller部分

Framework中的ProcessList和Native的lmkd进程通过Socket进行进程间通信,而lmkd和内核中的LowMemoryKiller通过writeFileString向文件节点写内容方法进行通信

Framework层通过一定的规则调整进程的adj的值和内存空间阀值,然后通过socket发送给lmkd进程

lmkd两种处理方式:
一种将阀值写入文件节点发送给内核的LowMemoryKiller,由内核进行杀进程处理
另一种是lmkd通过cgroup监控内存使用情况,自行计算杀掉进程。

三、lmkd的启动和初始化

lmkd是一个native进程,由init进程启动,定义在/system/core/lmkd/lmkd.rc中

service lmkd /system/bin/lmkd
    class core
    group root readproc
    critical
    socket lmkd seqpacket 0660 system system
    writepid /dev/cpuset/system-background/tasks

在lmkd.rc中,启动了lmkd进程,并创建了一个名为lmkd的socket的描述符,用于socket进程间通信。lmkd启动后首先执行main方法。

int main(int argc __unused, char **argv __unused) {
    struct sched_param param = {
            .sched_priority = 1,
    };
    sched_setscheduler(0, SCHED_FIFO, &param);
    if (!init())
        mainloop();
}

main方法首先设置了当前进程的调度规则,然后执行了init方法和mainLoop方法。

static int init(void) {
    struct epoll_event epev;
    int i;
    int ret;
    //获取当前系统的页大小,单位kb
    page_k = sysconf(_SC_PAGESIZE);
    if (page_k == -1)
        page_k = PAGE_SIZE;
    page_k /= 1024;
    // 创建一个epollfd描述符
    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;
    }

    // 获取init.rc中创建的lmkd socket描述符
    ctrl_sock.sock = android_get_control_socket("lmkd");
    if (ctrl_sock.sock < 0) {
        ALOGE("get lmkd control socket failed");
        return -1;
    }
    
    // 监听socket的连接,即ProcessList的Socket连接
    ret = listen(ctrl_sock.sock, MAX_DATA_CONN);
    if (ret < 0) {
        ALOGE("lmkd control socket listen failed (errno=%d)", errno);
        return -1;
    }

    // Epoll 设置监听socket中的可读事件,当有可读事件的时候回调ctrl_connect_hander方法
   //处理socket连接过程
    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++;
   
    //检测内核是否支持lowMemoryKiller机制
    has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
    use_inkernel_interface = has_inkernel_module;

    //如果内核不支持LowMemoryKiller,则调用init_mp_common初始化,在lmkd中实现进程查杀过程
    if (use_inkernel_interface) {
        ALOGI("Using in-kernel low memory killer interface");
    } else {
        if (!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;
        }
    }

    //初始化lmkd中的进程列表
    for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) {
        procadjslot_list[i].next = &procadjslot_list[i];
        procadjslot_list[i].prev = &procadjslot_list[i];
    }

    return 0;
}

lmkd的init方法中做的工作
1.获取lmkd的socket描述符
2.创建epoll来监听socket的连接,如果有连接则回调ctrl_connect_handler方法来处理。
3.检测是否有minfree接口,即内核是否支持lowmemorykiller,如果内核不支持则调用init_mp_common初始化,在lmkd中实现进程查杀。

我们先分析内核实现的LowMemoryKiller进程查杀机制, 然后再分析lmkd实现的机制。两者最终的结果都是在内存紧张的时候杀死一些进程来释放内存, 但是实现机制去不太一样。

static void mainloop(void) {
    struct event_handler_info* handler_info;
    struct epoll_event *evt;

    //循环等待epoll事件的上报
    while (1) {
        struct epoll_event events[maxevents];
        int nevents;
        int i;

        nevents = epoll_wait(epollfd, events, maxevents, -1);

        if (nevents == -1) {
            if (errno == EINTR)
                continue;
            ALOGE("epoll_wait failed (errno=%d)", errno);
            continue;
        }

        //获取到对应的epoll事件,分发给对应的handler处理
        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);
            }
        }
    }
}

init执行初始化完成之后, 进入mainloop方法,循环等待epoll事件的上报,init的时候epoll监听的socket连接, 当有socket连接的时候就会调用ctrl_connect_handler方法。

static void ctrl_connect_handler(int data __unused, uint32_t events __unused) {
    struct epoll_event epev;
    int free_dscock_idx = get_free_dsock();

    if (free_dscock_idx < 0) {
        /*
         * Number of data connections exceeded max supported. This should not
         * happen but if it does we drop all existing connections and accept
         * the new one. This prevents inactive connections from monopolizing
         * data socket and if we drop ActivityManager connection it will
         * immediately reconnect.
         */
        for (int i = 0; i < MAX_DATA_CONN; i++) {
            ctrl_data_close(i);
        }
        free_dscock_idx = 0;
    }
    //接受framework的socket连接
    data_sock[free_dscock_idx].sock = accept(ctrl_sock.sock, NULL, NULL);
    if (data_sock[free_dscock_idx].sock < 0) {
        ALOGE("lmkd control socket accept failed; errno=%d", errno);
        return;
    }

    ALOGI("lmkd data connection established");
    //监听连接的socket通信,当socket有消息的时候会掉ctrl_data_handler方法。
    data_sock[free_dscock_idx].handler_info.data = free_dscock_idx;
    data_sock[free_dscock_idx].handler_info.handler = ctrl_data_handler;
    epev.events = EPOLLIN;
    epev.data.ptr = (void *)&(data_sock[free_dscock_idx].handler_info);
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, data_sock[free_dscock_idx].sock, &epev) == -1) {
        ALOGE("epoll_ctl for data connection socket failed; errno=%d", errno);
        ctrl_data_close(free_dscock_idx);
        return;
    }
    maxevents++;
}

监听到socket连接, 我们知道此时连接lmkd的socket客户端就是framework,当有连接到来的时候accept方法返回连接的socketFD, 然后将连接的socketFD同样加入epoll中, 当socketFD中有可读消息,即framework给lmkd发送消息的时候,epoll唤醒然后会掉ctrl_data_handler方法来处理。

四、Framework和lmkd通信

Framework和lmkd进程通过socket来进行进程间通信,在lmkd初始化的时候,通过监听socket描述符lmkd来等待Framework发送的消息。
Framework向lmkd发送命令相关的方法有三个。

1.AMS.updateConfiguration
更新配置,手机屏幕的尺寸和内存大小不一样,对应的最小内存阀值和adj值也不一样, 最终调用ProcessList的updateOomLevel方法向lmkd发送调整命令
2.AMS.applyOomAdjLocked AMS根据一定的规则调整进程的adj值,最用通过ProcessList的setOomAdj方法发送给lmkd调整命令
3.AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked 进程死亡后,调用ProcessList的remove方法移除进程

上面的三种情况Framework最终是通过socket向lmkd发送了三种消息。

    // LMK_TARGET <minfree> <minkillprio> ... (up to 6 pairs)
    // LMK_PROCPRIO <pid> <uid> <prio>
    // LMK_PROCREMOVE <pid>
    //调整minfree和adj的值
    static final byte LMK_TARGET = 0;
    //设置对应进程的adj值
    static final byte LMK_PROCPRIO = 1;
    //移除对应进程
    static final byte LMK_PROCREMOVE = 2;

lmkd接收命令处理逻辑

static void ctrl_command_handler(int dsock_idx) {
    LMKD_CTRL_PACKET packet;
    int len;
    enum lmk_cmd cmd;
    int nargs;
    int targets;
    //从socket中读取数据
    len = ctrl_data_read(dsock_idx, (char *)packet, CTRL_PACKET_MAX_SIZE);
    if (len <= 0)
        return;

    if (len < (int)sizeof(int)) {
        ALOGE("Wrong control socket read length len=%d", len);
        return;
    }

    //解析Socket的命令和参数
    cmd = lmkd_pack_get_cmd(packet);
    nargs = len / sizeof(int) - 1;
    if (nargs < 0)
        goto wronglen;

    switch(cmd) {
    case LMK_TARGET:
        targets = nargs / 2;
        if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj))
            goto wronglen;
        //调整minfree和adj阀值
        cmd_target(targets, packet);
        break;
    case LMK_PROCPRIO:
        if (nargs != 3)
            goto wronglen;
        //设置对应进程的adj值
        cmd_procprio(packet);
        break;
    case LMK_PROCREMOVE:
        if (nargs != 1)
            goto wronglen;
        //移除对应的进程
        cmd_procremove(packet);
        break;
    default:
        ALOGE("Received unknown command code %d", cmd);
        return;
    }

    return;

lmkd通过epoll监听socket中是否有数据, 当接受的framework发送的socket命令之后,调用ctrl_cmmand_handler方法处理,显示解析socket中的命令和参数,根据对于的命令来调用不同的方法处理。

cmd_target 调整最小内存阀值和adj值
cmd_procprio 调整进程的adj值
cmd_procremove 移除对应的进程
对于进程查杀有两种实现方式,一种是内核的LMK,通过shrinker来触发低内存回收, 另一种是lmkd通过cgroup监控内存使用情况,自行计算杀掉进程。两种实现不太一样,需要逐个分析。

五、内核LMK的实现

static void cmd_target(int ntargets, LMKD_CTRL_PACKET packet) {
    int i;
    struct lmk_target target;
    lowmem_targets_size = ntargets;

    //使用kernel中的LMK
    if (has_inkernel_module) {
        char minfreestr[128];
        char killpriostr[128];

        minfreestr[0] = '\0';
        killpriostr[0] = '\0';

        //将从framework收到的内存阀值和adj值封装成字符串,以,分隔
       //如 18432,23040,27648,32256,55296,80640 
       //0,100,200,300,900,906
        for (i = 0; i < lowmem_targets_size; i++) {
            char val[40];
           
            if (i) {
                strlcat(minfreestr, ",", sizeof(minfreestr));
                strlcat(killpriostr, ",", sizeof(killpriostr));
            }

            snprintf(val, sizeof(val), "%d", use_inkernel_interface ? lowmem_minfree[i] : 0);
            strlcat(minfreestr, val, sizeof(minfreestr));
            snprintf(val, sizeof(val), "%d", use_inkernel_interface ? lowmem_adj[i] : 0);
            strlcat(killpriostr, val, sizeof(killpriostr));
        }
        // 将字符串分别写入 /sys/module/lowmemorykiller/parameters/minfree
        // 和/sys/module/lowmemorykiller/parameters/adj
        writefilestring(INKERNEL_MINFREE_PATH, minfreestr);
        writefilestring(INKERNEL_ADJ_PATH, killpriostr);
    }
}

设置内存阀值和adj的值就是将从framework收到的数据封装成字符串,通过writefilestring写入到两个文件节点,以供内核LMK使用。
/sys/module/lowmemorykiller/parameters/minfree : 内存级别限额
/sys/module/lowmemorykiller/parameters/adj :内存级别限额对应的要杀掉的进程的adj值.

static void cmd_procprio(LMKD_CTRL_PACKET packet) {
    struct proc *procp;
    char path[80];
    char val[20];
    int soft_limit_mult;
    struct lmk_procprio params;

    //解析进程adj相关的参数
    lmkd_pack_get_procprio(packet, &params);

    if (params.oomadj < OOM_SCORE_ADJ_MIN ||
        params.oomadj > OOM_SCORE_ADJ_MAX) {
        ALOGE("Invalid PROCPRIO oomadj argument %d", params.oomadj);
        return;
    }

    //将进程优先级写入到/proc/进程id/oom_score_adj文件中
    snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);
    snprintf(val, sizeof(val), "%d", params.oomadj);
    writefilestring(path, val);

    if (use_inkernel_interface)
        return;
}

由于使用内核LMK, 所以调整进程优先级直接将优先级写入对应进程的oom_adj_score文件即可。

static void cmd_procremove(LMKD_CTRL_PACKET packet) {
    struct lmk_procremove params;

    //内核LMK,移除进程什么都不需要做,全部有内核处理
    if (use_inkernel_interface)
        return;
}

移除进程的时候不需要做任何操作

六 内核LowMemoryKiller的实现原理

在linux中,有一个名为kswapd的内核线程,当linux回收存放分页的时候,kswapd线程将会遍历一张shrinker链表,并执行回调,或者某个app启动,发现可用内存不足时,则内核会阻塞请求分配内存的进程分配内存的过程,并在该进程中去执行lowmemorykiller来释放内存。虽然之前没有接触过,大体的理解就是向系统注册了这个shrinker回调函数之后,当系统空闲内存页面不足时会调用这个回调函数。 struct shrinker的定义在linux/kernel/include/linux/shrinker.h中:
内核LowMemoryKiller shrinker的注册过程如下:

static struct shrinker lowmem_shrinker = {
    .scan_objects = lowmem_scan,
    .count_objects = lowmem_count,
    .seeks = DEFAULT_SEEKS * 16
};

static int __init lowmem_init(void)
{
    register_shrinker(&lowmem_shrinker);
    return 0;
}

static void __exit lowmem_exit(void)
{
    unregister_shrinker(&lowmem_shrinker);
}

源链接:http://events.jianshu.io/p/4dbe9bbe0449

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android内存机制在不同版本之间有着较大的演变,其中最主要的就是内存管理策略的变化。在早期的Android版本中,内存管理采用的是进程优先级的方式,即根据进程的重要性和消耗的资源量来确定内存使用的权重,但这种方式容易导致内存不足的情况,进而导致应用程序崩溃。 后来,Google在Android 2.0中引入了LowMemoryKiller机制,通过监控系统内存使用情况,当内存不足时自动清理不必要的进程,以释放内存资源。LowMemoryKiller机制的实现是通过kernel的oom-killer机制来实现的,当系统内存不足时,通过oom_adj值来判断哪些进程可以杀掉,以释放内存资源。在Android 2.x中,LowMemoryKiller机制主要依赖于进程oom_adj值的设置,以及进程的重要性和消耗的资源量来判断哪些进程可以被杀掉。 随着Android版本的不断升级,Google也对LowMemoryKiller机制进行了多次优化,主要包括: 1. Android 3.0中引入了memcg机制,通过将进程的内存资源划分为多个cgroup,实现对内存资源的精细化管理。 2. Android 4.0中引入了Lmkd机制,通过对进程的内存资源进行动态调整,以更加精准地释放内存资源。 3. Android 4.4中引入了Zram机制,通过将一部分物理内存作为压缩内存使用,提高内存使用效率。 4. Android 6.0中引入了Doze机制,通过限制应用程序的后台运行,以降低系统内存负载。 总的来说,Android的内存管理机制是不断演变和优化的过程,不断追求更加高效和精细化的内存管理方式,以保证系统的稳定性和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值