Android init 进程各种 stage 是怎么回事?

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 运行的顺序如下:

  1. FirstStageMain:挂载各种文件系统,初始化一些文件目录
  2. SetupSelinux:selinux 相关
  3. 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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容