1. 背景
android14-release init 进程的 main 函数的简化版本如下:
// [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
这里先给一个结论。如上图所示,各种 stage 运行的顺序如下:
-
FirstStageMain
:挂载各种文件系统,初始化一些文件目录 -
SetupSelinux
:selinux 相关 -
SecondStageMain
:我们熟知的init.rc
的解析执行、孤儿进程的处理。
3. 各个阶段是怎么触发的?
3.1 init 进程的启动和 FirstStageMain 的执行
// [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 进程的路径,执行它。 - 尝试各种可能的 init 进程的路径,如果找到了一个,那就返回成功(
0
)。
可以看到,内核在启动 init 的时候是不带参数的。这样一来,init 的 main
函数将会执行 FirstStageMain
。
3.2 FirstStageMain
的末尾将触发 SetupSelinux
的执行
在 FirstStageMain
函数的末尾,我们可以找到这样一些代码:
// [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
时设置的 O_CLOEXEC
(close-on-exec)是不必要的:
-
dup2
在复制文件描述符的时候,会清除 fd 标志(即清除掉O_CLOEXEC
) - 在
execv
之前我们就关闭了fd
,不需要依赖 close-on-exec 这个标志来自动关闭 fd。
execv
之后,内核会扔掉旧的内存空间、重新加载 init 程序并传递这里设置的 "selinux_setup"
给 init 的 main
函数。也就是说,接下来会执行 SetupSelinux
。
3.3 SetupSelinux
的末尾将触发 SecondStageMain
的执行
跟 FirstStageMain
里面的实现是类似的,很好理解:
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;
}