從源碼角度看LowMemoryKiller

Android系統中,進程的運行狀態常常是各個應用開發者關注點的重中之重,進程是否能夠存活基本也就意味著應用能否從用戶手中獲益。之前我寫過一篇文章專門講過進程保活與進程優先級的計算方式,有興趣的話大家可以再看看。

進程一直存活固然很重要,一來應用可以隨時隨地的在用戶手機上做業務操作,二來用戶再次啟動時速度也會更快些。然而,作為移動設備,應用進程如果只啟動而不能被Android系統被動的殺除,那么越來越多的內存被尚在運行的無用進程霸占,系統只會越用越卡。對此,Android基于OOM機制引入了lowmemmorykiller,可以在系統內存緊缺時殺死不必要的低優先級進程,進程的優先級越低就越容易被殺死。

對于lowmemorykiller, 我總結為三層:AMS, lmkd 與 lowmemorykiller。其中AMS負責更新各應用的進程優先級與閾值數組,lmkd負責接收AMS傳輸過來的數據然后寫入到sys與proc節點,lowmemorykiller則在內存低于閾值時才會被觸發并負責殺死低優先級的進程。

這篇文章中,我會先列出一張總結流程圖,便于大家掌握lowmemorykiller總體涉及也方便以后查看。隨后我再會按照三層從上往下的分析lowmemmorykiller的代碼。

查看大圖

上層:ActivityManagerService更新adj

AMS.updateConfiguration

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public void updateConfiguration(Configuration values){
    synchronized (this) {
...
        if (mWindowManager != null) {
            mProcessList.applyDisplaySize(mWindowManager);
        }
...
    }
}

updateConfiguration是AMS對外提供的binder接口,調用后可以更新窗口配置

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

void applyDisplaySize(WindowManagerService wm){
    if (!mHaveDisplaySize) {
        Point p = new Point();
        // 獲取窗口顯示的寬高
        wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p);
        if (p.x != 0 && p.y != 0) {
            updateOomLevels(p.x, p.y, true);
            mHaveDisplaySize = true;
        }
    }
}
private void updateOomLevels(int displayWidth, int displayHeight, boolean write){
    // mTotalMemMb是指當前設備內存大小,以MB為單位
    // 計算內存比例
    float scaleMem = ((float)(mTotalMemMb-350))/(700-350);

    // 計算屏幕大小
    int minSize = 480*800;  // 384000
    int maxSize = 1280*800; // 1024000 230400 870400 .264
    float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
    if (false) {
        Slog.i("XXXXXX", "scaleMem=" + scaleMem);
        Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth
                + " dh=" + displayHeight);
    }

    // 選擇較大的比例,最小為0最大為1
    float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
    if (scale < 0) scale = 0;
    else if (scale > 1) scale = 1;
    int minfree_adj = Resources.getSystem().getInteger(
            com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
    int minfree_abs = Resources.getSystem().getInteger(
            com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
    if (false) {
        Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs);
    }

    final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;

    for (int i=0; i<mOomAdj.length; i++) {
        int low = mOomMinFreeLow[i];
        int high = mOomMinFreeHigh[i];
        if (is64bit) {
            // Increase the high min-free levels for cached processes for 64-bit
            // 64位的機器high值會適當的提高,最終空進程、緩存進程被殺的內存閾值也會被提高
            if (i == 4) high = (high*3)/2;
            else if (i == 5) high = (high*7)/4;
        }
        mOomMinFree[i] = (int)(low + ((high-low)*scale));
    }

...

    if (write) {
        // 通過socket將計算完畢的信息傳輸給lwkd守護進程
        ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
        buf.putInt(LMK_TARGET);
        for (int i=0; i<mOomAdj.length; i++) {
            // 最終寫入到minfree文件的數組單位為page的個數
            buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
          // 寫入到adj文件的數組為固定的adj數組
            buf.putInt(mOomAdj[i]);
        }

        writeLmkd(buf);
        SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
    }
    // GB: 2048,3072,4096,6144,7168,8192
    // HC: 8192,10240,12288,14336,16384,20480
}
名稱
FOREGROUND_APP_ADJ 0
VISIBLE_APP_ADJ 1
PERCEPTIBLE_APP_ADJ 2
BACKUP_APP_ADJ 3
CACHED_APP_MIN_ADJ 9
CACHED_APP_MAX_ADJ 15

這張表為mOomAdj數組,最終會被寫入到sys節點的adj文件

AMS.applyOomAdjLocked

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

private final boolean applyOomAdjLocked(ProcessRecord app,boolean doingAll, long now,
                                                 long nowElapsed) {
...

    if (app.curAdj != app.setAdj) {
        ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
    }
...

當計算后的adj和之前的不一樣,就會通過setOomAdj更新lmk的adj數值

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

public static final void setOomAdj(int pid, int uid, int amt){
    if (amt == UNKNOWN_ADJ)
        return;

    long start = SystemClock.elapsedRealtime();
    ByteBuffer buf = ByteBuffer.allocate(4 * 4);
    buf.putInt(LMK_PROCPRIO);
    buf.putInt(pid);
    buf.putInt(uid);
    buf.putInt(amt);
    writeLmkd(buf);
    long now = SystemClock.elapsedRealtime();
    if ((now-start) > 250) {
        Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid
                + " = " + amt);
    }
}

setOomAdj會將AMS已經計算好的adj值通過socket發送到lmkd

AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

private final void handleAppDiedLocked(ProcessRecord app,
                                       boolean restarting, boolean allowRestart) {
    int pid = app.pid;
    boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
    if (!kept && !restarting) {
        removeLruProcessLocked(app);
        if (pid > 0) {
            ProcessList.remove(pid);
        }
    }
...

死亡回調后,如果進程不再重啟,那么會先移除lruProcess的記錄,隨后會調用該進程的ProcessList.remove方法

private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
                                                     boolean restarting, boolean allowRestart, int index) {
...
    if (restart && !app.isolated) {
        // We have components that still need to be running in the
        // process, so re-launch it.
        if (index < 0) {
            ProcessList.remove(app.pid);
        }
        addProcessNameLocked(app);
        startProcessLocked(app, "restart", app.processName);
        return true;
    } 
    return false;
}

通過trim會調用應用的cleanUp機制,如果應用會重啟那么會調用該進程的ProcessList.remove方法

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

public static final void remove(int pid){
    ByteBuffer buf = ByteBuffer.allocate(4 * 2);
    buf.putInt(LMK_PROCREMOVE);
    buf.putInt(pid);
    writeLmkd(buf);
}

各協議所攜帶的參數

lmk協議 所需參數
LMK_TARGET mOomMinFree, mOomAdj兩個數組
LMK_PROCPRIO pid, uid, amt (adj)
LMK_PROCREMOVE pid

ProcessList.writeLmkd

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

private static void writeLmkd(ByteBuffer buf){
    for (int i = 0; i < 3; i++) {
        if (sLmkdSocket == null) {
                    // 首先嘗試打開socket,如果沒有打開就睡眠1s,最多會嘗試打開3次,如果沒有打開就不會再做嘗試
                if (openLmkdSocket() == false) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ie) {
                    }
                    continue;
                }
        }

        try {
            // 寫入數據到對端的socket
            sLmkdOutputStream.write(buf.array(), 0, buf.position());
            return;
        } catch (IOException ex) {
            Slog.w(TAG, "Error writing to lowmemorykiller socket");

            try {
                sLmkdSocket.close();
            } catch (IOException ex2) {
            }

            sLmkdSocket = null;
        }
    }
}

writeLmkd的輸入參數是ByteBuffer,用來保存需要傳輸給lmkd的數據

private static boolean openLmkdSocket(){
    try {
        // 嘗試連接socket
        sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
        sLmkdSocket.connect(
            new LocalSocketAddress("lmkd",
                    LocalSocketAddress.Namespace.RESERVED));
       // 獲取輸出流,用于向對端寫入數據
        sLmkdOutputStream = sLmkdSocket.getOutputStream();
    } catch (IOException ex) {
        Slog.w(TAG, "lowmemorykiller daemon socket open failed");
        sLmkdSocket = null;
        return false;
    }

    return true;
}

以上的代碼便是AMS通過socket來將計算后的adj, minfree閾值寫入到lwkd的實現方法

中層:lmkd守護線程,向節點寫入數據

int main(int argc __unused, char **argv __unused){
    struct sched_param param = {
            .sched_priority = 1,
    };

    mlockall(MCL_FUTURE);
    sched_setscheduler(0, SCHED_FIFO, &param);
    // 首先完成初始化操作
    if (!init())
        // 隨后進入主循環,等待AMS發送socket請求
        mainloop();

    ALOGI("exiting");
    return 0;
}
static int init(void){
    // 初始化epoll事件
    struct epoll_event epev;
    int i;
    int ret;

...

    // 創建一個epoll并獲取fd
    epollfd = epoll_create(MAX_EPOLL_EVENTS);

    ctrl_lfd = android_get_control_socket("lmkd");

    ret = listen(ctrl_lfd, 1);

    epev.events = EPOLLIN;
    // 當監聽到socket連接事件后會調用ctrl_connect_handler方法
    epev.data.ptr = (void *)ctrl_connect_handler;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1) {
        ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno);
        return -1;
    }
    maxevents++;

    use_inkernel_interface = !access(INKERNEL_MINFREE_PATH, W_OK);

    if (use_inkernel_interface) {
        ALOGI("Using in-kernel low memory killer interface");
    } else {
        ret = init_mp(MEMPRESSURE_WATCH_LEVEL, (void *)?_event);
        if (ret)
            ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");
    }

    for (i = 0; i <= ADJTOSLOT(OOM_ADJUST_MAX); i++) {
        procadjslot_list[i].next = &procadjslot_list[i];
        procadjslot_list[i].prev = &procadjslot_list[i];
    }

    return 0;
}
static void ctrl_connect_handler(uint32_t events __unused){
    struct sockaddr addr;
    socklen_t alen;
    struct epoll_event epev;

    alen = sizeof(addr);
    // 作為服務端接受socket信息
    ctrl_dfd = accept(ctrl_lfd, &addr, &alen);

    ALOGI("ActivityManager connected");
    maxevents++;
    epev.events = EPOLLIN;
    // 當接收到socket消息后會調用ctrl_data_handler方法
    epev.data.ptr = (void *)ctrl_data_handler;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_dfd, &epev) == -1) {
        ALOGE("epoll_ctl for data connection socket failed; errno=%d", errno);
        ctrl_data_close();
        return;
    }
}
static void ctrl_data_handler(uint32_t events){
    // socket消息類型為斷開
    if (events & EPOLLHUP) {
        ALOGI("ActivityManager disconnected");
        if (!ctrl_dfd_reopened)
            ctrl_data_close();
    // socket消息類型為接受
    } else if (events & EPOLLIN) {
        ctrl_command_handler();
    }
}
static void ctrl_command_handler(void){
    int ibuf[CTRL_PACKET_MAX / sizeof(int)];
    int len;
    int cmd = -1;
    int nargs;
    int targets;

    // 讀取socket管道信息
    len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX);

    // 獲取buffer中的命令協議
    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;
    }

    return;

wronglen:
    ALOGE("Wrong control socket read length cmd=%d len=%d", cmd, len);
}

LMK_TARGET -> cmd_target

static void cmd_target(int ntargets, int *params){
    int i;

    // 取出minfree, adj數組
    for (i = 0; i < ntargets; i++) {
        lowmem_minfree[i] = ntohl(*params++);
        lowmem_adj[i] = ntohl(*params++);
    }

    lowmem_targets_size = ntargets;

    if (use_inkernel_interface) {
        char minfreestr[128];
        char killpriostr[128];

        // 將數組信息解析為字符串
        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", lowmem_minfree[i]);
            strlcat(minfreestr, val, sizeof(minfreestr));
            snprintf(val, sizeof(val), "%d", lowmem_adj[i]);
            strlcat(killpriostr, val, sizeof(killpriostr));
        }

        // 隨后寫入到固定的文件節點
        writefilestring(INKERNEL_MINFREE_PATH, minfreestr);
        writefilestring(INKERNEL_ADJ_PATH, killpriostr);
    }
}

LMK_TARGET的作用在于更新了minifree與adj的閾值

LMK_PROCPRIO -> cmd_procprio

static void cmd_procprio(int pid, int uid, int oomadj){
    struct proc *procp;
    char path[80];
    char val[20];

    snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid);
    // 將adj轉換為oom_score_adj,規則是除了MAX之外,公式:adj * 1000 / -17
    snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj));
    writefilestring(path, val);

    if (use_inkernel_interface)
        return;

    // 查找進程
    procp = pid_lookup(pid);
    // 如果不存在,則創建一個新的節點,為它分配內存然后寫入數據
    if (!procp) {
            procp = malloc(sizeof(struct proc));
            if (!procp) {
                // Oh, the irony. May need to rebuild our state.
                return;
            }

            procp->pid = pid;
            procp->uid = uid;
            procp->oomadj = oomadj;
            proc_insert(procp);
    } else {
        // 如果存在則改變oomadj的數據
        proc_unslot(procp);
        procp->oomadj = oomadj;
        proc_slot(procp);
    }
}

LMK_PROCPRIO的作用在于更新了oom_score_adj的數據

LMK_PROCREMOVE -> cmd_procremove

static void cmd_procremove(int pid){
    if (use_inkernel_interface)
        return;

    pid_remove(pid);
    kill_lasttime = 0;
}

LMK_PROCREMOVE的作用只是移除了進程proc節點

底層:lowmemorykiller,低內存時觸發進程查殺

linux/blob/master/drivers/staging/android/lowmemorykiller.c

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

static struct shrinker lowmem_shrinker = {
    .shrink = lowmem_shrink,
    .seeks = DEFAULT_SEEKS * 16
};

初始化lowmemorykiller,注冊cache shrinker,當空閑內存頁面不足時,內核進程kswpd會調用注冊的shrink回調函數來回收頁面,在Android上,lowmemorykiller,是通過minifree與adj數組來確認需要殺死的低優先級進程,以此來釋放內存

例如,一組minifree與adj的閾值數據如下:

minifree:

15360,19200,23040,26880,34415,43737

adj:

0,58,117,176,529,1000

當內存低于43737x4KB時,會殺死adj大于1000的進程,當內存低于34415x4KB時,會殺死adj大于529的進程

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
    struct task_struct *p;
    struct task_struct *selected = NULL;
    int rem = 0;
    int tasksize;
    int i;
    int min_adj = OOM_ADJUST_MAX + 1;
    int selected_tasksize = 0;
    int selected_oom_adj;
    int array_size = ARRAY_SIZE(lowmem_adj);
    // 獲取當前可用內存頁數
    int other_free = global_page_state(NR_FREE_PAGES);
    int other_file = global_page_state(NR_FILE_PAGES);

    if (lowmem_adj_size < array_size)
        array_size = lowmem_adj_size;
    if (lowmem_minfree_size < array_size)
        array_size = lowmem_minfree_size;
    for (i = 0; i < array_size; i++) {
        if (other_free < lowmem_minfree[i] &&
            other_file < lowmem_minfree[i]) {
           // 獲取需要被清除的adj閾值
            min_adj = lowmem_adj[i];
            break;
        }
    }

    rem = global_page_state(NR_ACTIVE_ANON) +
        global_page_state(NR_ACTIVE_FILE) +
        global_page_state(NR_INACTIVE_ANON) +
        global_page_state(NR_INACTIVE_FILE);

    selected_oom_adj = min_adj;

    read_lock(&tasklist_lock);
    // 遍歷每個進程
    for_each_process(p) {
        struct mm_struct *mm;
        int oom_adj;

        task_lock(p);
        mm = p->mm;
        if (!mm) {
            task_unlock(p);
            continue;
        }
        oom_adj = mm->oom_adj;
        if (oom_adj < min_adj) {
            task_unlock(p);
            continue;
        }
        tasksize = get_mm_rss(mm);
        task_unlock(p);
        if (tasksize <= 0)
            continue;
        if (selected) {
            // 當adj小于閾值時,不殺進程
            if (oom_adj < selected_oom_adj)
                continue;
            if (oom_adj == selected_oom_adj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;
        selected_oom_adj = oom_adj;
        lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
                 p->pid, p->comm, oom_adj, tasksize);
    }
    // 殺死所選的低優先級進程
    if (selected) {
        lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",
                 selected->pid, selected->comm,
                 selected_oom_adj, selected_tasksize);
        force_sig(SIGKILL, selected);
        rem -= selected_tasksize;
    }
    lowmem_print(4, "lowmem_shrink %d, %x, return %d\n",
             nr_to_scan, gfp_mask, rem);
    read_unlock(&tasklist_lock);
    return rem;
}

這段lowmemorykiller內核的代碼很簡單,思路就是遍歷所有進程并比較進程優先級與優先級閾值,并殺死優先級低于閾值的進程

總結

ActivitiyManagerService ProcessList lmk_cmd ctrl_command_handler 作用
updateConfiguration updateOomLevels LMK_TARGET cmd_target 更新minifree與adj閾值到sys節點
applyOomAdjLocked setOomAdj LMK_PROCPRIO cmd_procprio 更新oom_score_adj到proc節點
cleanUpApplicationRecordLocked
handleAppDiedLocked
remove LMK_PROCREMOVE cmd_procremove

簡而言之,AMS通過在framework層調用三類不同的方法來向lmkd守護進程傳輸進程與內存閾值數據,lmkd負責向節點寫入進程優先級數據,lowmemorykiller.c則運行在內核中,當設備的內存低于某個閾值時就會觸發相應的策略殺死較低優先級的進程。

 

來自:http://navyblue.top/2018/01/21/從源碼角度看LowMemoryKiller/

 

 本文由用戶 JohnnieLand 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!