lmkd白名单方案的实现,避免应用被杀

发布时间 2023-06-07 18:20:24作者: luke4212

1 如何避免app被lmk杀掉

公司的项目可能有一些核心app(java app或native app),不想因为low memory被lmk杀掉。

粗糙一些的做法是在lmkd中添加白名单,白名单的应用永远不会被lmkd杀死。

// lmkd.c
static int kill_one_process(struct proc* procp, int min_oom_score) {
    ...
    taskname = proc_get_name(pid);
    if (!taskname) {
        goto out;
    }

    tasksize = proc_get_size(pid);
    if (tasksize <= 0) {
        goto out;
    }
    // 自己实现的is_in_white_list函数
    if (is_in_white_list(taskname)) {
        ALOGI("%s (%d) in the white list.", taskname, pid);
        goto out;
    }
    ...
out:
    pid_remove(pid);
    return result;
}

上面代码是使用白名单的位置。

2 动态添加lmkd白名单

我的平台是Android 10的版本。

遵循第1节的思路,还是添加白名单。但是要求动态添加,就需要lmkd暴露一个接口。

参考原生Android的实现,java层是通过ProcessList这样一个class和lmkd,通过local socket进行通信的。

所以我添加了一个新的cmd,从ProcessList向lmkd发送字符串(包名,即白名单)。

这里说一下lmkd中init的时候,使用了epoll的机制,我的理解就是epoll会向内核注册,内核负责监听fd(如socket的fd),fd有变化了才会通知lmkd。

这里就有一个问题:

// lmkd.h
/*
 * Max number of targets in LMK_TARGET command.
 */
#define MAX_TARGETS 6

/*
 * Max packet length in bytes.
 * Longest packet is LMK_TARGET followed by MAX_TARGETS
 * of minfree and oom_adj_score values
 */
#define CTRL_PACKET_MAX_SIZE (sizeof(int) * (MAX_TARGETS * 2 + 1))

/* LMKD packet - first int is lmk_cmd followed by payload */
typedef int LMKD_CTRL_PACKET[CTRL_PACKET_MAX_SIZE / sizeof(int)];


// lmkd.c
static void ctrl_command_handler(int dsock_idx) {
    LMKD_CTRL_PACKET packet;
    int len;
    len = ctrl_data_read(dsock_idx, (char *)packet, CTRL_PACKET_MAX_SIZE);
    ...
}

简单来说,问题就是,原生用于读socket的buffer size太小了,

buffer size = 4 *(6 * 2 + 1) = 52 字节

减去同于标识cmd的int的大小,最多可以用的pay load的大小就是48字节,用来传递包名有可能就不够用。

而epoll机制又要求你只能read一次fd,如果想第二次read,内核不通知fd有变化,read函数就会一直阻塞。

经过分析代码,可以对数据结构LMKD_CTRL_PACKET进行扩容:

#define MAX_TARGETS_EXT 16
#define CTRL_PACKET_MAX_SIZE (sizeof(int) * (MAX_TARGETS_EXT * 2 + 1))

这样方案是可行的。

另外,动态白名单我想用支持动态扩容的容器存放。但是Android 10中,lmkd的源码是.c的后缀,没办法用c++的set容器。这里需要从c代码调到cpp代码。Android 12,lmk使用c++写的,可能不存在这个问题。