Android init 進(jìn)程各種 stage 是怎么回事?

1. 背景

android14-release init 進(jìn)程的 main 函數(shù)的簡化版本如下:

// [android14-release]/system/core/init/main.cpp
init main(int argc, char *argv[]) {
    if (argc > 0) {
        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }

    return FirstStageMain(argc, argv);
}

2. 各種 stage 說明

init process.png

這里先給一個結(jié)論。如上圖所示,各種 stage 運行的順序如下:

  1. FirstStageMain:掛載各種文件系統(tǒng),初始化一些文件目錄
  2. SetupSelinux:selinux 相關(guān)
  3. SecondStageMain:我們熟知的init.rc 的解析執(zhí)行、孤兒進(jìn)程的處理。

3. 各個階段是怎么觸發(fā)的?

3.1 init 進(jìn)程的啟動和 FirstStageMain 的執(zhí)行

// [android14-6.1-lts]/init/main.c
static int __ref kernel_init(void *unused) {
    ...

    /*
     * We try each of these until one succeeds.
     *
     * The Bourne shell can be used instead of init if we are
     * trying to recover a really broken machine.
     */
    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
        panic("Requested init %s failed (error %d).",
              execute_command, ret);
    }

    if (CONFIG_DEFAULT_INIT[0] != '\\0') {
        ret = run_init_process(CONFIG_DEFAULT_INIT);
        if (ret)
            pr_err("Default init %s failed (error %d)\\n",
                   CONFIG_DEFAULT_INIT, ret);
        else
            return 0;
    }

    if (!try_to_run_init_process("/sbin/init") ||
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||
        !try_to_run_init_process("/bin/sh"))
            return 0;

    panic("No working init found.  Try passing init= option to kernel. "
          "See Linux Documentation/admin-guide/init.rst for guidance.");
}

  • 一般情況下,如果在編譯時配置了 CONFIG_DEFAULT_INIT,那么這就是 init 進(jìn)程的路徑,執(zhí)行它。
  • 嘗試各種可能的 init 進(jìn)程的路徑,如果找到了一個,那就返回成功(0)。

可以看到,內(nèi)核在啟動 init 的時候是不帶參數(shù)的。這樣一來,init 的 main 函數(shù)將會執(zhí)行 FirstStageMain。

3.2 FirstStageMain 的末尾將觸發(fā) SetupSelinux 的執(zhí)行

FirstStageMain 函數(shù)的末尾,我們可以找到這樣一些代碼:

// [android14-release]/system/core/init/first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
    ....

    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    close(fd);
    execv(path, const_cast<char**>(args));

    // execv() only returns if an error happened, in which case we
    // panic and never fall through this conditional.
    PLOG(FATAL) << "execv(\\"" << path << "\\") failed";
    return 1;
}

值得注意的是,打開 /dev/kmsg 時設(shè)置的 O_CLOEXEC (close-on-exec)是不必要的:

  • dup2 在復(fù)制文件描述符的時候,會清除 fd 標(biāo)志(即清除掉 O_CLOEXEC
  • execv 之前我們就關(guān)閉了 fd,不需要依賴 close-on-exec 這個標(biāo)志來自動關(guān)閉 fd。

execv 之后,內(nèi)核會扔掉舊的內(nèi)存空間、重新加載 init 程序并傳遞這里設(shè)置的 "selinux_setup" 給 init 的 main 函數(shù)。也就是說,接下來會執(zhí)行 SetupSelinux。

3.3 SetupSelinux 的末尾將觸發(fā) SecondStageMain 的執(zhí)行

FirstStageMain 里面的實現(xiàn)是類似的,很好理解:

int SetupSelinux(char** argv) {
    ...

    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args));
    return 1;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容