Android源碼閱讀:lmkd

一、lmkd進程啟動和初始化過程

lmkd由init進程啟動,在系統(tǒng)中作為一個單獨的進程存在,啟動時直接運行l(wèi)mkd.cpp中的main函數(shù)。
main函數(shù)中,邏輯較清楚,更新參數(shù),創(chuàng)建logger,之后在if中進行init,之后在mainloop()中循環(huán)等待。

int main(int argc, char **argv) {
    update_props();
    ctx = create_android_logger(KILLINFO_LOG_TAG);
    if (!init()) {
        //...
        mainloop();
    }
    android_log_destroy(&ctx);
}

1、update_props() 參數(shù)更新

update_props函數(shù)中主要是使用GET_LMK_PROPERTY從屬性中獲取各個參數(shù)配置,例如從參數(shù)中獲取low 、medium、critical三種壓力等級下,可以kill的adj等級。

static void update_props() {
    /* By default disable low level vmpressure events */
    level_oomadj[VMPRESS_LEVEL_LOW] =
        GET_LMK_PROPERTY(int32, "low", OOM_SCORE_ADJ_MAX + 1);
    level_oomadj[VMPRESS_LEVEL_MEDIUM] =
        GET_LMK_PROPERTY(int32, "medium", 800);
    level_oomadj[VMPRESS_LEVEL_CRITICAL] =
        GET_LMK_PROPERTY(int32, "critical", 0);
    // ...
}

GET_LMK_PROPERTY是一個宏定義,用來讀取ro.lmk參數(shù)

#define GET_LMK_PROPERTY(type, name, def) \
    property_get_##type("persist.device_config.lmkd_native." name, \
        property_get_##type("ro.lmk." name, def))

2、init() 初始化過程

1.2.1 創(chuàng)建epoll監(jiān)聽

init中比較重要的一步是創(chuàng)建epoll監(jiān)聽,這里有宏定義MAX_EPOLL_EVENTS是9,也就是epoll監(jiān)聽了9個event

/* max supported number of data connections (AMS, init, tests) */
/* 支持的最大數(shù)據(jù)連接數(shù)(AMS、init、測試) */
#define MAX_DATA_CONN 3

/*
 * 1 ctrl listen socket, 3 ctrl data socket, 3 memory pressure levels,
 * 1 lmk events + 1 fd to wait for process death + 1 fd to receive kill failure notifications
 * 
 * 1個控制監(jiān)聽socket,3個控制數(shù)據(jù)通信socket,3個內(nèi)存壓力等級,1個lmk時間,1個監(jiān)控進程死亡,1個接收kill失敗通知
 */
#define MAX_EPOLL_EVENTS (1 + MAX_DATA_CONN + VMPRESS_LEVEL_COUNT + 1 + 1 + 1)

static int epollfd;

static int init(void) {
    
    // ...

    epollfd = epoll_create(MAX_EPOLL_EVENTS);
    if (epollfd == -1) {
        ALOGE("epoll_create failed (errno=%d)", errno);
        return -1;
    }

    // ...
}

例如ams會作為socket客戶端,通過 /dev/socket/lmkd 與 lmkd 進行socket通信,將進程的adj通知到lmkd,并由lmkd寫入"/proc/[pid]/oom_score_adj"路徑。

1.2.2 初始化lmkd觸發(fā)方式

接下來init函數(shù)需要決定lmkd的觸發(fā)方式,早期的lmk使用內(nèi)核驅(qū)動的方式,這里通過access確認舊的節(jié)點是否還存在(kernel 4.12已廢棄)。
不支持的話就是執(zhí)行 init_monitors()

    has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
    use_inkernel_interface = has_inkernel_module && !enable_userspace_lmk;

    if (use_inkernel_interface) {
        // 大多內(nèi)核已不支持
    } else {
        if (!init_monitors()) {
            return -1;
        }
    }

注意初始化監(jiān)控器這里,有4個看起來很像的函數(shù),分別是init_monitors()、init_psi_monitors()、init_mp_psi()、init_psi_monitor(),注意區(qū)分。
看代碼,在init_monitors()函數(shù)中,要確認使用PSI觸發(fā)還是vmpressure觸發(fā);
在“ro.lmk. use_psi”屬性為true的情況下,調(diào)用 init_psi_monitors 初始化PSI監(jiān)控器,失敗才會使用init_mp_common初始化vmpressure監(jiān)控器,這里可以看出lmkd還是傾向于優(yōu)先使用PSI觸發(fā)。

static bool init_monitors() {
    /* 在內(nèi)核支持的情況下,盡量使用PSI監(jiān)控器 */
    use_psi_monitors = GET_LMK_PROPERTY(bool, "use_psi", true) &&
        init_psi_monitors();

    /* PSI監(jiān)控器初始化失敗,回退到vmpressure觸發(fā) */
    if (!use_psi_monitors &&
        (!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 false;
    }
    if (use_psi_monitors) {
        ALOGI("Using psi monitors for memory pressure detection");
    } else {
        ALOGI("Using vmpressure for memory pressure detection");
    }
    return true;
}

1) PSI觸發(fā)

接下來看調(diào)用 init_psi_monitors() 初始化PSI監(jiān)控器,在明確設(shè)置屬性use_new_strategy為true的情況下,或低內(nèi)存設(shè)備,或明確use_minfree_levels為false的情況下,都是傾向于使用“新的策略”。這里新的策略其實指的是在PSI觸發(fā)之后,是根據(jù)free page的情況(水線)去查殺進程,還是根據(jù)不同PSI壓力去查殺進程,前者就是舊策略,后者為新策略;個人認為這里用“新舊”去區(qū)分非常不優(yōu)雅。
注意這里新舊的策略,是依據(jù)PSI壓力殺進程還是依據(jù)水線殺進程,但都不影響這里是設(shè)置的是PSI監(jiān)控器,即觸發(fā)仍然還是用PSI觸發(fā)的,是殺進程的方式存在不同。

static bool init_psi_monitors() {

    bool use_new_strategy =
        GET_LMK_PROPERTY(bool, "use_new_strategy", low_ram_device || !use_minfree_levels);

    /* 在默認 PSI 模式下,使用系統(tǒng)屬性覆蓋 psi stall閾值 */
    if (use_new_strategy) {
        /* Do not use low pressure level */
        psi_thresholds[VMPRESS_LEVEL_LOW].threshold_ms = 0;
        psi_thresholds[VMPRESS_LEVEL_MEDIUM].threshold_ms = psi_partial_stall_ms;
        psi_thresholds[VMPRESS_LEVEL_CRITICAL].threshold_ms = psi_complete_stall_ms;
    }

    if (!init_mp_psi(VMPRESS_LEVEL_LOW, use_new_strategy)) {
        return false;
    }
    if (!init_mp_psi(VMPRESS_LEVEL_MEDIUM, use_new_strategy)) {
        destroy_mp_psi(VMPRESS_LEVEL_LOW);
        return false;
    }
    if (!init_mp_psi(VMPRESS_LEVEL_CRITICAL, use_new_strategy)) {
        destroy_mp_psi(VMPRESS_LEVEL_MEDIUM);
        destroy_mp_psi(VMPRESS_LEVEL_LOW);
        return false;
    }
    return true;
}

決定好新舊策略后,接下來調(diào)用init_mp_psi來初始化各個等級的PSI事件。
init_mp_psi有兩個參數(shù),第一個是壓力等級,第二個新舊策略的標(biāo)志位。注意第一個參數(shù)的命名是“vmpressure_level”,盡管是“vmpressure”,但實際這里用PSI觸發(fā),是根據(jù)PSI來判斷內(nèi)存壓力等級的,和前面說的vmpressure判斷內(nèi)存壓力等級并非同一個“vmpressure”,這是第二個我認為代碼非常不優(yōu)雅的地方,容易引起歧義。vmpressure全稱是虛擬內(nèi)存壓力,難道設(shè)計者的想法中,PSI所產(chǎn)生的stall ms也是一種虛擬的內(nèi)存壓力?

static bool init_mp_psi(enum vmpressure_level level, bool use_new_strategy) {
    int fd;

    /* Do not register a handler if threshold_ms is not set */
    if (!psi_thresholds[level].threshold_ms) {
        return true;
    }

    fd = init_psi_monitor(psi_thresholds[level].stall_type,
        psi_thresholds[level].threshold_ms * US_PER_MS,
        PSI_WINDOW_SIZE_MS * US_PER_MS);

    if (fd < 0) {
        return false;
    }

    vmpressure_hinfo[level].handler = use_new_strategy ? mp_event_psi : mp_event_common;
    vmpressure_hinfo[level].data = level;
    if (register_psi_monitor(epollfd, fd, &vmpressure_hinfo[level]) < 0) {
        destroy_psi_monitor(fd);
        return false;
    }
    maxevents++;
    mpevfd[level] = fd;

    return true;
}


注意這里的init_psi_monitor和前面的init_psi_monitors做區(qū)分,init_psi_monitor是定義在system/memory/lmkd/libpsi/psi.cpp中的,他的作用是根據(jù)stall類型、閾值、窗口大小,獲取epoll監(jiān)聽的句柄。
然后最重要的就是vmpressure_hinfo[level].handler,其根據(jù)是否使用新策略,決定了在這個壓力等級事件發(fā)生時,要調(diào)用的是mp_event_psi還是mp_event_common。也就是使用新策略的情況下,當(dāng)這個壓力事件到來時,會調(diào)用mp_event_psi。
后面register_psi_monitor則是epoll監(jiān)聽壓力事件。
至此可以認為init_psi_monitors()也就是PSI監(jiān)控器初始化完成,各個壓力事件發(fā)生時,會調(diào)用mp_event_psi。

2) vmpressure觸發(fā)

由于現(xiàn)在大部分Android機型均使用PSI觸發(fā),vmpressure觸發(fā)這部分暫略過。

init中除了init_monitors()還有其他一些初始化過程,也先略過。

二、PSI觸發(fā)后的新策略(mp_event_psi)

mp_event_psi函數(shù)可以大致分為三個部分,第一部分做一些參數(shù)和狀態(tài)的計算,第二部分根據(jù)得出的狀態(tài)確定查殺原因(kill_reason),第三部分選擇進程進行一輪查殺。

1、參數(shù)和狀態(tài)的計算

2.1.1 一些static變量

首先是這個函數(shù)中有一些static變量,在多次進入這個函數(shù)時,這些static變量持續(xù)記錄狀態(tài)。

    static int64_t init_ws_refault; // 記錄 殺進程后 初始的 workingset_refault
    static int64_t prev_workingset_refault; // 記錄上一輪的 workingset_refault
    static int64_t base_file_lru;      // 記錄初始時的 文件頁緩存大小
    static int64_t init_pgscan_kswapd; // 記錄初始時的 kswap回收量
    static int64_t init_pgscan_direct; // 記錄初始時的 直接回收量
    static bool killing; // 如果有進程被殺會被置為true
    static int thrashing_limit = thrashing_limit_pct; // 抖動的閾值,一開始由參數(shù)中獲取
    static struct zone_watermarks watermarks;
    static struct timespec wmark_update_tm;
    static struct wakeup_info wi;
    static struct timespec thrashing_reset_tm;
    static int64_t prev_thrash_growth = 0;
    static bool check_filecache = false;
    static int max_thrashing = 0;


2.1.2 一些臨時變量

    union meminfo mi;  // 從 /proc/meminfo 解析
    union vmstat vs;   // 從 /proc/vmstat 解析
    struct psi_data psi_data;
    struct timespec curr_tm; // 每輪開始時記錄時間
    int64_t thrashing = 0;
    bool swap_is_low = false;
    enum vmpressure_level level = (enum vmpressure_level)data;
    enum kill_reasons kill_reason = NONE;
    bool cycle_after_kill = false; // 如果上一輪有進程被殺,這一輪會被置為true
    enum reclaim_state reclaim = NO_RECLAIM;
    enum zone_watermark wmark = WMARK_NONE;
    char kill_desc[LINE_MAX];
    bool cut_thrashing_limit = false;
    int min_score_adj = 0;
    int swap_util = 0;
    int64_t swap_low_threshold;
    long since_thrashing_reset_ms;
    int64_t workingset_refault_file;
    bool critical_stall = false;

2.1.3 一些狀態(tài)的判斷

這部分代碼較多,比較重要的是通過vmstat_parsememinfo_parse讀取信息,判斷thrashing、水線、swap狀態(tài)等,便于下一步確認查殺原因。

    if (clock_gettime(CLOCK_MONOTONIC_COARSE, &curr_tm) != 0) {
        ALOGE("Failed to get current time");
        return;
    }

    record_wakeup_time(&curr_tm, events ? Event : Polling, &wi);

    bool kill_pending = is_kill_pending();
    if (kill_pending && (kill_timeout_ms == 0 ||
        get_time_diff_ms(&last_kill_tm, &curr_tm) < static_cast<long>(kill_timeout_ms))) {
        /* Skip while still killing a process */
        wi.skipped_wakeups++;
        goto no_kill;
    }
    /*
     * Process is dead or kill timeout is over, stop waiting. This has no effect if pidfds are
     * supported and death notification already caused waiting to stop.
     * 進程死亡或者kill超時結(jié)束,停止等待。 如果支持 pidfd 并且死亡通知已導(dǎo)致等待停止,則此操作無效。
     */
    stop_wait_for_proc_kill(!kill_pending);

    // vmstat解析
    if (vmstat_parse(&vs) < 0) {
        ALOGE("Failed to parse vmstat!");
        return;
    }

    /* 從5.9開始內(nèi)核workingset_refault vmstat字段被重命名為workingset_refault_file */
    workingset_refault_file = vs.field.workingset_refault ? : vs.field.workingset_refault_file;

    // meminfo解析
    if (meminfo_parse(&mi) < 0) {
        ALOGE("Failed to parse meminfo!");
        return;
    }

    /* Reset states after process got killed */
    /* 殺進程后重置一些狀態(tài) */
    if (killing) {
        killing = false;
        cycle_after_kill = true;
        /* Reset file-backed pagecache size and refault amounts after a kill */
        base_file_lru = vs.field.nr_inactive_file + vs.field.nr_active_file; // 重置 文件頁 緩存大小
        init_ws_refault = workingset_refault_file;                           // 重置 refault量
        thrashing_reset_tm = curr_tm; // thrashing重置時間設(shè)置為當(dāng)前時間
        prev_thrash_growth = 0;       // thrashing重置為0
    }

    /* Check free swap levels */
    /* 確認swap狀態(tài): swap_is_low */
    if (swap_free_low_percentage) { // 由屬性中獲取
        swap_low_threshold = mi.field.total_swap * swap_free_low_percentage / 100;
        swap_is_low = get_free_swap(&mi) < swap_low_threshold;  // free swap低于 XX%,認為swap較低
    } else {
        swap_low_threshold = 0;
    }

    /* Identify reclaim state */
    /* 確認回收狀態(tài): reclaim */
    if (vs.field.pgscan_direct != init_pgscan_direct) { // pgscan_direct發(fā)生了變化,說明發(fā)生了【DIRECT_RECLAIM】
        init_pgscan_direct = vs.field.pgscan_direct;
        init_pgscan_kswapd = vs.field.pgscan_kswapd;
        reclaim = DIRECT_RECLAIM;
    } else if (vs.field.pgscan_kswapd != init_pgscan_kswapd) { // kswapd回收量發(fā)生變化,發(fā)生了【KSWAPD_RECLAIM】
        init_pgscan_kswapd = vs.field.pgscan_kswapd;
        reclaim = KSWAPD_RECLAIM;
    } else if (workingset_refault_file == prev_workingset_refault) {
        /*
         * Device is not thrashing and not reclaiming, bail out early until we see these stats
         * changing
         * 設(shè)備沒有抖動也沒有回收,該輪不查殺,直到我們看到這些統(tǒng)計數(shù)據(jù)發(fā)生變化
         */
        goto no_kill;
    }

    prev_workingset_refault = workingset_refault_file;

    /*
    * It's possible we fail to find an eligible process to kill (ex. no process is
    * above oom_adj_min). When this happens, we should retry to find a new process
    * for a kill whenever a new eligible process is available.
    * 有可能我們找不到合適的進程來終止(例如,沒有進程高于 oom_adj_min)。
    * 這種情況下,只要有新的符合條件的進程可用,我們就應(yīng)該重試尋找新的進程進行kill。
    * 
    * This is especially important for a slow growing refault case. 
    * 這對于增長緩慢的缺頁場景尤為重要。
    * 
    * While retrying, we should keep monitoring new thrashing counter 
    * as someone could release the memory to mitigate the thrashing. 
    * 在重試時,我們應(yīng)該繼續(xù)監(jiān)視新的抖動計數(shù)器(thrashing counter),因為有人可以釋放內(nèi)存來減輕抖動。
    * 
    * Thus, when thrashing reset window comes, 
    * we decay the prev thrashing counter by window counts. 
    * 因此,當(dāng)抖動重置窗口到來時,我們通過窗口計數(shù)衰減前一個抖動計數(shù)器。
    * 
    * If the counter is still greater than thrashing limit,
    * we preserve the current prev_thrash counter so we will retry kill again. 
    * 如果計數(shù)器仍然大于抖動限制,我們將保留當(dāng)前的 prev_thrash 計數(shù)器,以便我們再次重試 kill。
    * 
    * Otherwise, we reset the prev_thrash counter so we will stop retrying.
    * 否則,我們重置 prev_thrash 計數(shù)器以停止重試。
    */

    // 從 thrashing重置 到 現(xiàn)在 的時間差,注意如果上一輪查殺過,時間會被重置,時間差=0
    since_thrashing_reset_ms = get_time_diff_ms(&thrashing_reset_tm, &curr_tm);
    if (since_thrashing_reset_ms > THRASHING_RESET_INTERVAL_MS) {
        long windows_passed;
        /* Calculate prev_thrash_growth if we crossed THRASHING_RESET_INTERVAL_MS */
        /* 在超過thrashing reset間隔時間的情況下,計算上一次thrash增長 */
        prev_thrash_growth = (workingset_refault_file - init_ws_refault) * 100
                            / (base_file_lru + 1);          // 新增缺頁數(shù)量 / 總文件頁數(shù)量 * 100
        // 代表超過了多少個thrashing reset窗口
        windows_passed = (since_thrashing_reset_ms / THRASHING_RESET_INTERVAL_MS);

        /*
         * Decay prev_thrashing unless over-the-limit thrashing was registered in the window we
         * just crossed, which means there were no eligible processes to kill. We preserve the
         * counter in that case to ensure a kill if a new eligible process appears.
         * 
         * 減少 prev_thrashing 除非 在我們剛剛越過的窗口中 注冊了超過限制的抖動,這意味著沒有符合條件的進程可以殺死。 
         * 在這種情況下,我們保留計數(shù)器以確保在出現(xiàn)新的合格進程時終止。
         */
        // 不太懂
        if (windows_passed > 1 || prev_thrash_growth < thrashing_limit) {
            prev_thrash_growth >>= windows_passed;
        }

        /* Record file-backed pagecache size when crossing THRASHING_RESET_INTERVAL_MS */
        /* 超過THRASHING_RESET_INTERVAL_MS時,記錄 文件頁數(shù)量 */
        // 實際看這里是重置了 文件頁大小、refault數(shù)量、抖動重置時間、抖動閾值
        base_file_lru = vs.field.nr_inactive_file + vs.field.nr_active_file;
        init_ws_refault = workingset_refault_file;
        thrashing_reset_tm = curr_tm;        // thrashing重置
        thrashing_limit = thrashing_limit_pct;
    } else {
        /* Calculate what % of the file-backed pagecache refaulted so far */
        // 上一輪發(fā)生過查殺,或thrashing剛重置沒多久,就計算到目前為止,文件頁緩存發(fā)生缺頁的百分比
        thrashing = (workingset_refault_file - init_ws_refault) * 100 / (base_file_lru + 1);
    }
    /* Add previous cycle's decayed thrashing amount */
    // 累加上一輪的thrashing衰減量
    thrashing += prev_thrash_growth;
    if (max_thrashing < thrashing) {
        max_thrashing = thrashing;
    }

    /*
     * Refresh watermarks once per min in case user updated one of the margins.
     * 每 60s 刷新一次水線
     *
     * TODO: b/140521024 replace this periodic update with an API for AMS to notify LMKD
     * that zone watermarks were changed by the system software.
     * TODO: 使用 AMS的API 替換 此定時更新,以通知 LMKD 水線已被系統(tǒng)軟件更改。
     */
    if (watermarks.high_wmark == 0 || get_time_diff_ms(&wmark_update_tm, &curr_tm) > 60000) {
        struct zoneinfo zi;

        // 進行一次zoninfo解析
        if (zoneinfo_parse(&zi) < 0) {
            ALOGE("Failed to parse zoneinfo!");
            return;
        }

        // 計算zone水線,看這個函數(shù)把各個zone的水線都加了起來,存到watermarks里
        calc_zone_watermarks(&zi, &watermarks);
        wmark_update_tm = curr_tm;
    }

    /* Find out which watermark is breached if any */
    // 確認到了哪個水線等級
    wmark = get_lowest_watermark(&mi, &watermarks);

    /* 從/proc/pressure/memory解析 psi 數(shù)據(jù),確認是否達到 critical 等級 */
    if (!psi_parse_mem(&psi_data)) {
        critical_stall = psi_data.mem_stats[PSI_FULL].avg10 > (float)stall_limit_critical;
    }



2、確認查殺原因和最低adj

該部分主要是根據(jù)上一部分得出的狀態(tài),確認要進行查殺的原因,以及對最低可查殺adj等級(min_score_adj)做出修改,這部分源碼基本上全是if else if注釋比較詳細,kill_reasonkill_desc的賦值也比較直觀,高通等廠商也會對這部分代碼做較大的改動,因此暫不詳細標(biāo)注這部分內(nèi)容。如有l(wèi)mkd異常查殺等情況發(fā)生,可以根據(jù)lmkd日志中打印的kill reason,在這一部分找到對應(yīng)的源碼。
代碼示例:

    if (cycle_after_kill && wmark < WMARK_LOW) {
        /*
         * Prevent kills not freeing enough memory which might lead to OOM kill.
         * This might happen when a process is consuming memory faster than reclaim can
         * free even after a kill. Mostly happens when running memory stress tests.
         */
        kill_reason = PRESSURE_AFTER_KILL;
        strncpy(kill_desc, "min watermark is breached even after kill", sizeof(kill_desc));
    } else if (level == VMPRESS_LEVEL_CRITICAL && events != 0) {
        // ......
    } else if (swap_is_low && thrashing > thrashing_limit_pct) {
        // ......
    } else if (/*...*/)

3、進程查殺

至此已經(jīng)確定了查殺原因和最低允許查殺的adj,調(diào)用find_and_kill_process函數(shù)進行查殺。

    if (kill_reason != NONE) {
        struct kill_info ki = {
            .kill_reason = kill_reason,
            .kill_desc = kill_desc,
            .thrashing = (int)thrashing,
            .max_thrashing = max_thrashing,
        };
    // ...
        int pages_freed = find_and_kill_process(min_score_adj, &ki, &mi, &wi, &curr_tm, &psi_data);
        if (pages_freed > 0) {
            killing = true;
            // ...
        }
    }

find_and_kill_process函數(shù)的作用是在大于等于min_score_adj的范圍內(nèi),選擇合適的進程進行查殺。

static int find_and_kill_process(int min_score_adj, struct kill_info *ki, union meminfo *mi,
                                 struct wakeup_info *wi, struct timespec *tm,
                                 struct psi_data *pd) {
    int i;
    int killed_size = 0;
    bool lmk_state_change_start = false;
    bool choose_heaviest_task = kill_heaviest_task;

    // 從 1000 開始循環(huán)
    for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) {
        struct proc *procp;

        if (!choose_heaviest_task && i <= PERCEPTIBLE_APP_ADJ) {
            /*
             * If we have to choose a perceptible process, choose the heaviest one to
             * hopefully minimize the number of victims.
             * 如果我們必須選擇一個可感知的進程(adj<=200),就選擇最嚴重的一個,來盡量避免查殺過多的進程。
             */
            choose_heaviest_task = true;
        }

        while (true) {
            procp = choose_heaviest_task ?
                proc_get_heaviest(i) : proc_adj_tail(i); // 在adj==i的進程中找"最嚴重的"或末尾的

            if (!procp)
                break;

            killed_size = kill_one_process(procp, min_score_adj, ki, mi, wi, tm, pd);
            if (killed_size >= 0) {
                break;
            }
        }

        // 有進程查殺發(fā)生時,不再繼續(xù)查殺更低adj的進程
        if (killed_size) {
            break;
        }
    }

    return killed_size;
}

從find_and_kill_process可以得之lmkd每次觸發(fā)查殺時,都是從adj1000的進程開始逐個篩選合適的進程查殺,發(fā)生查殺后退出。在“選擇合適的進程”的策略中,可以通過kill_heaviest_task系統(tǒng)屬性控制lmkd是用proc_get_heaviest還是proc_adj_tail做篩選,不過注意在查殺到adj小于等于200時,已經(jīng)到了必須查殺用戶可感知進程的地步,此時強制篩選“最嚴重”的進程。

  • 所謂“最嚴重的進程”,可以在proc_get_heaviest函數(shù)中看到是對進程讀取"/proc/[pid]/statm"路徑,獲取進程rss內(nèi)存占用,排序找出rss最大的進程;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 為了提高性能,Android系統(tǒng)中進程的匿名頁、文件頁按照一定的策略進行緩存,在內(nèi)存緊張的時候再進行回收。但內(nèi)存回...
    superme_閱讀 1,920評論 0 0
  • 概述 Linux系統(tǒng)當(dāng)可用內(nèi)存較低的時候oom killer機制會根據(jù)一定的規(guī)則去殺掉一些進程來釋放內(nèi)存,而And...
    泡面先生_Jack閱讀 3,415評論 1 3
  • 一、文章背景 最近在分析一個應(yīng)用進后臺經(jīng)常被殺死問題,問題發(fā)生在一個定制的系統(tǒng)中,存在多個內(nèi)存動態(tài)清理工具(類似手...
    TechMix閱讀 805評論 0 0
  • 2018/07/31 AOSP android-8.1.0_r31 Android盡可能多的緩存進程,當(dāng)用戶下次再...
    wbo4958閱讀 5,462評論 0 4
  • Android 的設(shè)計理念之一,便是應(yīng)用程序退出,但進程還會繼續(xù)存在系統(tǒng)以便再次啟動時提高響應(yīng)時間. 這樣的設(shè)計會...
    tiger桂閱讀 1,799評論 0 1