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 運行的順序如下:
-
FirstStageMain:掛載各種文件系統(tǒng),初始化一些文件目錄 -
SetupSelinux:selinux 相關(guān) -
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;
}