안드로이드/안드로이드 프레임워크

안드로이드 앱 프레임워크 학습(ActivityManager) 3

냥냥냥냥냥냥 2024. 3. 10. 19:22

안드로이드 앱 프레임워크 학습(ActivityManager) 2 (tistory.com)

 

안드로이드 앱 프레임워크 학습(ActivityManager) 2

안드로이드 앱 프레임워크 학습(ActivityManager) 1 (tistory.com) 안드로이드 앱 프레임워크 학습(ActivityManager) 1 이전에 StartActivity 호출 순서를 블로그로 써본적이 있었는데 (자세한 건 아래 참조 부탁드

nyaang.tistory.com

지난 시간엔 안드로이드 프레임워크에서 process 관리 부분에 대해 간단히 알아 봤습니다

 

이번에는 안드로이드에서 lmkd (Low Memory Killer Demon)이 뭔지 어떤 동작을 하는지 알아보려고 합니다

android docs를 보면 lmkd의 설명을 아래와 같이 해놓았습니다

더보기

Android 로우 메모리 킬러 데몬(lmkd) 프로세스는 실행 중인 Android 시스템의 메모리 상태를 모니터링하고, 시스템 성능을 만족할 만한 수준으로 유지하기 위해 가장 필요성이 적은 프로세스를 종료하여 높은 메모리 압력에 반응합니다.

필요성을 나타내는 값이 OOM_SCORE이고 MIN, MAX값은 아래와 같습니다

// lmkd.cpp
/* OOM score values used by both kernel and framework */
#define OOM_SCORE_ADJ_MIN       (-1000)
#define OOM_SCORE_ADJ_MAX       1000

 

java 단에서는 lmkdConnection 내에서 LocalSocket을 이용하여 lmkd의 fd을 만들어서 연결하고

// ProcessList.java
    void init(ActivityManagerService service, ActiveUids activeUids,
            PlatformCompat platformCompat) {
            
            ...
            
        if (sKillHandler == null) {
            sKillThread = new ServiceThread(TAG + ":kill",
                    THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
            sKillThread.start();
            sKillHandler = new KillHandler(sKillThread.getLooper());
            sLmkdConnection = new LmkdConnection(sKillThread.getLooper().getQueue(),
                    new LmkdConnection.LmkdConnectionListener() {

native 단에서는 lmkd.rc를 통해서 부팅 후 lmkd의 demon이 시작되며 init에서 lmkd의 fd socket을 연결합니다

// lmkd::init
    ctrl_sock.sock = android_get_control_socket("lmkd");
    if (ctrl_sock.sock < 0) {
        ALOGE("get lmkd control socket failed");
        return -1;
    }

    ret = listen(ctrl_sock.sock, MAX_DATA_CONN);
    if (ret < 0) {
        ALOGE("lmkd control socket listen failed (errno=%d)", errno);
        return -1;
    }

 

먼저 그럼 java단 부터 sequence를 한 번 보겠습니다

lmkdConnection

 

native 단의 sequence는 아래와 같습니다

lmkd sequence

사실 lmkd 에서의 init에서 봐야할 사항이 조금 더 있습니다

use_inkernel_interface 값에 따라 분기가 되기 때문입니다

// lmkd::init
    has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
    use_inkernel_interface = has_inkernel_module;

    if (use_inkernel_interface) {
        ALOGI("Using in-kernel low memory killer interface");
        if (init_poll_kernel()) {
            epev.events = EPOLLIN;
            epev.data.ptr = (void*)&kernel_poll_hinfo;
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, kpoll_fd, &epev) != 0) {
                ALOGE("epoll_ctl for lmk events failed (errno=%d)", errno);
                close(kpoll_fd);
                kpoll_fd = -1;
            } else {
                maxevents++;
                /* let the others know it does support reporting kills */
                property_set("sys.lmk.reportkills", "1");
            }
        }
    } else {
        if (!init_monitors()) {
            return -1;
        }
        /* let the others know it does support reporting kills */
        property_set("sys.lmk.reportkills", "1");
    }
    
    
// lmkd::init_monitor
static bool init_monitors() {
    /* Try to use psi monitor first if kernel has it */
    use_psi_monitors = GET_LMK_PROPERTY(bool, "use_psi", true) &&
        init_psi_monitors();
    /* Fall back to vmpressure */
    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;
}

 

또한 psi monitor에서도 use_new_strategy 유무에 따라 달라집니다

init_psi_monitors는 init_monitors가 불릴 때 불려지는 함수이고, use_new_strategy boolean 값에 따라

init_mp_psi 내부에서 handler가 mp_event_psi , mp_event_common으로 나누어집니다 

// lmkd::init_psi_monitors
static bool init_psi_monitors() {
    /*
     * When PSI is used on low-ram devices or on high-end devices without memfree levels
     * use new kill strategy based on zone watermarks, free swap and thrashing stats.
     * Also use the new strategy if memcg has not been mounted in the v1 cgroups hiearchy since
     * the old strategy relies on memcg attributes that are available only in the v1 cgroups
     * hiearchy.
     */
    bool use_new_strategy =
        GET_LMK_PROPERTY(bool, "use_new_strategy", low_ram_device || !use_minfree_levels);
        
        ...
        
    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;
    }
    
 // lmkd::init_mp_psi   
    vmpressure_hinfo[level].handler = use_new_strategy ? mp_event_psi : mp_event_common;

안에서 좀 로직이 좀 다르긴 한데 handler가 mp_event_psi , mp_event_common 든 상관없이 결국은 find_and_kill_process를 타게 됩니다 

 

find_and_kill_process로직을 보면 OOM_SCORE_ADJ_MAX 부터 내려오면서 체크하고, kill_one_process를 호출합니다

// lmkd::find_and_kill_process
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;

    for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) { // MAX 부터 
        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.
             */
            choose_heaviest_task = true;
        }

        while (true) {
            procp = choose_heaviest_task ?
                proc_get_heaviest(i) : proc_adj_tail(i);

            if (!procp)
                break;

            killed_size = kill_one_process(procp, min_score_adj, ki, mi, wi, tm, pd); // kill

// lmkd::kill_one_process
static int kill_one_process(struct proc* procp, int min_oom_score, struct kill_info *ki,
                            union meminfo *mi, struct wakeup_info *wi, struct timespec *tm,
                            struct psi_data *pd) {     
                            
                            ...
                            
    kill_result = reaper.kill({ pidfd, pid, uid }, false);

    trace_kill_end();

    if (kill_result) {
        stop_wait_for_proc_kill(false);
        ALOGE("kill(%d): errno=%d", pid, errno);
        /* Delete process record even when we fail to kill so that we don't get stuck on it */
        goto out;
    }
    
    ...
    
out:
    /*
     * WARNING: After pid_remove() procp is freed and can't be used!
     * Therefore placed at the end of the function.
     */
    pid_remove(pid);
    return result;
}

kill_one_process를 보면 reaper.kill 을 호출해주고 이 부분이 실질적인 process kill 부분으로 보입니다

 

// reaper.cpp
int Reaper::kill(const struct target_proc& target, bool synchronous) {
    /* CAP_KILL required */
    if (target.pidfd < 0) {
        return ::kill(target.pid, SIGKILL);
    }

    if (!synchronous && async_kill(target)) {
        // we assume the kill will be successful and if it fails we will be notified
        return 0;
    }

    int result = pidfd_send_signal(target.pidfd, SIGKILL, NULL, 0);
    if (result) {
        return result;
    }

    return is_reaping_supported() ? process_mrelease(target.pidfd, 0) : 0;
}

 

감사합니다