架构

Framework
位于ProcessList.java中定义了3种命令类型,这些文件的定义必须跟lmkd.c定义完全一致,格式分别如下:
LMK_TARGET <minfree> <minkillprio> ... (up to 6 pairs)
LMK_PROCPRIO <pid> <prio>
LMK_PROCREMOVE <pid>
上述3个命令的使用都通过ProcessList.java中的如下方法:
| 功能 | 命令 | 对应方法 | 
|---|---|---|
| LMK_PROCPRIO | 设置进程adj | PL.setOomAdj() | 
| LMK_TARGET | 更新oom_adj | PL.updateOomLevels() | 
| LMK_PROCREMOVE | 移除进程 | PL.remove() | 
- 当AMS.applyOomAdjLocked()过程,则会设置某个进程的adj;
- 当AMS.updateConfiguration()过程中便会更新整个各个级别的oom_adj信息.
- 当AMS.cleanUpApplicationRecordLocked()或者handleAppDiedLocked()过程,则会将某个进程从lmkd策略中移除.
applyOomAdjLocked
设置某个进程的adj—/proc/pid/oom_score_adj
// ActivityManagerService.java
    private final int computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
            boolean doingAll, long now) {
        --------------------------------------------------
    }
    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
            long nowElapsed) {
        -----------------------------------------------------
            ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
        -----------------------------------------------------
    }
// ProcessList.java
    static final byte LMK_TARGET = 0;            // 更新水位线
    static final byte LMK_PROCPRIO = 1;          // 更新oom_adj
    static final byte LMK_PROCREMOVE = 2;
    public static final void setOomAdj(int pid, int uid, int amt) {
        buf.putInt(LMK_PROCPRIO);
        buf.putInt(pid);
        buf.putInt(uid);
        buf.putInt(amt);
        writeLmkd(buf);
    }
    private static void writeLmkd(ByteBuffer buf) {
        // lmkd socket write
    }

| 命令查看:adb shell dumpsys activity oom | grep “com.android.settings” -A 5 | 
updateConfiguration
更新窗口配置,这个过程中,分别向/sys/module/lowmemorykiller/parameters目录下的minfree和adj节点写入相应数值
// AMS
    public void updateConfiguration(Configuration values) {
        -------------------------------------------------
        // Update OOM levels based on display size.
        mProcessList.applyDisplaySize(mWindowManager);
        -------------------------------------------------
    }
// ProcessList
    void applyDisplaySize(WindowManagerService wm) {
        ---------------------------------------
        // 传入屏幕的尺寸
        updateOomLevels(p.x, p.y, true);
        ---------------------------------------
    }
    private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
        -----------------------------------------------
        if (write) {
            ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
            buf.putInt(LMK_TARGET);
            for (int i=0; i<mOomAdj.length; i++) {
                buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE); // 五个水位线
                buf.putInt(mOomAdj[i]); // 与上面水位线对应的五个adj数值
            }
            writeLmkd(buf);
            SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
        }
        -----------------------------------------------
    }
这里携带的命令协议是LMK_TARGET, 要求kernel更新如下两个文件
/sys/module/lowmemorykiller/parameters/minfree // 水位线 /sys/module/lowmemorykiller/parameters/adj // 水位线对应的adj
L2# cat /sys/module/lowmemorykiller/parameters/minfree
15360,19200,23040,26880,34415,43737
L2# cat /sys/module/lowmemorykiller/parameters/adj
0,100,200,300,900,906
cleanUpApplicationRecordLocked
**这里携带的命令协议是LMK_PROCREMOVE, 删除/proc/
// AMS
    private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
            boolean restarting, boolean allowRestart, int index, boolean replacingPid) {
        Slog.d(TAG, "cleanUpApplicationRecord -- " + app.pid);
        if (index >= 0) {
            removeLruProcessLocked(app);
            ProcessList.remove(app.pid);
        }
    }
    private final void handleAppDiedLocked(ProcessRecord app,
            boolean restarting, boolean allowRestart) {
                ProcessList.remove(pid);
    }
// ProcessList.java
    public static final void remove(int pid) {
        ByteBuffer buf = ByteBuffer.allocate(4 * 2);
        buf.putInt(LMK_PROCREMOVE);
        buf.putInt(pid);
        writeLmkd(buf);
    }
lmkd
L2K:/ $ ps | grep lmkd
root      1     0     12164  1364  SyS_epoll_ 00000000 S /init
root      493   1     4716   1464  SyS_epoll_ 00000000 S /system/bin/lmkd
lmk.rc
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.c
#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree"
#define INKERNEL_ADJ_PATH "/sys/module/lowmemorykiller/parameters/adj"
// socket 通信协议
enum lmk_cmd {
    LMK_TARGET,
    LMK_PROCPRIO,
    LMK_PROCREMOVE,
};
static int lowmem_adj[MAX_TARGETS];
static int lowmem_minfree[MAX_TARGETS];
static int lowmem_targets_size;
struct proc {
    struct adjslot_list asl;
    int pid;
    uid_t uid;
    int oomadj;
    struct proc *pidhash_next;
};
int main(int argc __unused, char **argv __unused) {
    struct sched_param param = {
            .sched_priority = 1,
    };
    mlockall(MCL_FUTURE);
    sched_setscheduler(0, SCHED_FIFO, ¶m);
    // init socket 创建
    if (!init())
        // 等待AMS的指令
        mainloop();
    ALOGI("exiting");
    return 0;
}
static int init(void) {
    // 创建epoll监听文件句柄
    epollfd = epoll_create(MAX_EPOLL_EVENTS);
    // 获取lmkd控制描述符
    ctrl_lfd = android_get_control_socket("lmkd");
    // 监听lmkd socket
    ret = listen(ctrl_lfd, 1);
    // 将文件句柄ctrl_lfd,加入epoll句柄
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1) {
        return -1;
    }
}
static void mainloop(void) {
    while (1) {
        struct epoll_event events[maxevents];
        int nevents;
        int i;
        ctrl_dfd_reopened = 0;
        // 等待socket指令
        nevents = epoll_wait(epollfd, events, maxevents, -1);
        if (nevents == -1) {
            if (errno == EINTR)
                continue;
            ALOGE("epoll_wait failed (errno=%d)", errno);
            continue;
        }
        for (i = 0; i < nevents; ++i) {
            if (events[i].events & EPOLLERR)
                ALOGD("EPOLLERR on event #%d", i);
            // 当事件到来,则调用ctrl_connect_handler方法
            if (events[i].data.ptr)
                (*(void (*)(uint32_t))events[i].data.ptr)(events[i].events);
        }
    }
}
ctrl_command_handler
处理上层的socket指令
static void ctrl_command_handler(void) {
    int ibuf[CTRL_PACKET_MAX / sizeof(int)];
    int len;
    int cmd = -1;
    int nargs;
    int targets;
    len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX);
    if (len <= 0)
        return;
    nargs = len / sizeof(int) - 1;
    if (nargs < 0)
        goto wronglen;
    cmd = ntohl(ibuf[0]);
    switch(cmd) {
    case LMK_TARGET:
        targets = nargs / 2;
        if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj))
            goto wronglen;
        cmd_target(targets, &ibuf[1]);
        break;
    case LMK_PROCPRIO:
        if (nargs != 3)
            goto wronglen;
        cmd_procprio(ntohl(ibuf[1]), ntohl(ibuf[2]), ntohl(ibuf[3]));
        break;
    case LMK_PROCREMOVE:
        if (nargs != 1)
            goto wronglen;
        cmd_procremove(ntohl(ibuf[1]));
        break;
    default:
        ALOGE("Received unknown command code %d", cmd);
        return;
    }
}
Kernel
drivers/staging/android/lowmemorykiller.c
初始化
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);
	vmpressure_notifier_register(&lmk_vmpr_nb);
	return 0;
}
static void __exit lowmem_exit(void)
{
	unregister_shrinker(&lowmem_shrinker);
}
shrinker
LMK驱动通过注册shrinker来实现的,shrinker是linux kernel标准的回收内存page的机制,由内核线程kswapd负责监控。
当内存不足时kswapd线程会遍历一张shrinker链表,并回调已注册的shrinker函数来回收内存page,kswapd还会周期性唤醒来执行内存操作。每个zone维护active_list和inactive_list链表,内核根据页面活动状态将page在这两个链表之间移动,最终通过shrink_slab和shrink_zone来回收内存页,有兴趣想进一步了解linux内存回收机制,可自行研究,这里再回到LowMemoryKiller的过程分析。
lowmem_count
    static unsigned long lowmem_count(struct shrinker *s,
                      struct shrink_control *sc)
    {
        return global_page_state(NR_ACTIVE_ANON) +
            global_page_state(NR_ACTIVE_FILE) +
            global_page_state(NR_INACTIVE_ANON) +
            global_page_state(NR_INACTIVE_FILE);
    }
ANON代表匿名映射,没有后备存储器;FILE代表文件映射; 内存计算公式= 活动匿名内存 + 活动文件内存 + 不活动匿名内存 + 不活动文件内存
lowmem_scan
当触发lmkd,则先杀oom_score_adj最大的进程, 当oom_adj相等时,则选择rss最大的进程.
    static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
    {
        struct task_struct *tsk;
        struct task_struct *selected = NULL;
        unsigned long rem = 0;
        int tasksize;
        int i;
        short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
        int minfree = 0;
        int selected_tasksize = 0;
        short selected_oom_score_adj;
        int array_size = ARRAY_SIZE(lowmem_adj);
        //获取当前剩余内存大小
        int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
        int other_file = global_page_state(NR_FILE_PAGES) -
                            global_page_state(NR_SHMEM) -
                            total_swapcache_pages();
        //获取数组大小
        if (lowmem_adj_size < array_size)
            array_size = lowmem_adj_size;
        if (lowmem_minfree_size < array_size)
            array_size = lowmem_minfree_size;
        //遍历lowmem_minfree数组找出相应的最小adj值
        for (i = 0; i < array_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) {
            return 0;
        }
        selected_oom_score_adj = min_score_adj;
        rcu_read_lock();
        for_each_process(tsk) {
            struct task_struct *p;
            short oom_score_adj;
            if (tsk->flags & PF_KTHREAD)
                continue;
            p = find_lock_task_mm(tsk);
            if (!p)
                continue;
            if (test_tsk_thread_flag(p, TIF_MEMDIE) &&
                time_before_eq(jiffies, lowmem_deathpending_timeout)) {
                task_unlock(p);
                rcu_read_unlock();
                return 0;
            }
            oom_score_adj = p->signal->oom_score_adj;
            //小于目标adj的进程,则忽略
            if (oom_score_adj < min_score_adj) {
                task_unlock(p);
                continue;
            }
            //获取的是进程的Resident Set Size,也就是进程独占内存 + 共享库大小。
            tasksize = get_mm_rss(p->mm);
            task_unlock(p);
            if (tasksize <= 0)
                continue;
            //算法关键,选择oom_score_adj最大的进程中,并且rss内存最大的进程.
            if (selected) {
                if (oom_score_adj < selected_oom_score_adj)
                    continue;
                if (oom_score_adj == selected_oom_score_adj &&
                    tasksize <= selected_tasksize)
                    continue;
            }
            selected = p;
            selected_tasksize = tasksize;
            selected_oom_score_adj = oom_score_adj;
            lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n",
                     p->comm, p->pid, oom_score_adj, tasksize);
        }
        if (selected) {
            long cache_size = other_file * (long)(PAGE_SIZE / 1024);
            long cache_limit = minfree * (long)(PAGE_SIZE / 1024);
            long free = other_free * (long)(PAGE_SIZE / 1024);
            //输出kill的log
            lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \ ...);
            lowmem_deathpending_timeout = jiffies + HZ;
            set_tsk_thread_flag(selected, TIF_MEMDIE);
            //向选中的目标进程发送signal 9来杀掉目标进程
            send_sig(SIGKILL, selected, 0);
            rem += selected_tasksize;
        }
        rcu_read_unlock();
        return rem;
    }
- 选择oom_score_adj最大的进程中,并且rss内存最大的进程作为选中要杀的进程。
- 杀进程方式:send_sig(SIGKILL, selected, 0)`向选中的目标进程发送signal 9来杀掉目标进程。
总结
本文主要从frameworks的ProcessList.java调整adj,通过socket通信将事件发送给native的守护进程lmkd;lmkd再根据具体的命令来执行相应操作,其主要功能 更新进程的oom_score_adj值以及lowmemorykiller驱动的parameters(包括minfree和adj);
最后讲到了lowmemorykiller驱动,通过注册shrinker,借助linux标准的内存回收机制,根据当前系统可用内存以及parameters配置参数(adj,minfree)来选取合适的selected_oom_score_adj,再从所有进程中选择adj大于该目标值的并且占用rss内存最大的进程,将其杀掉,从而释放出内存
