Android 系统启动 - init 进程

前言

我们之前在 Android 系统启动流程简析 中提到:Android 系统内核启动之后,除了初始化各种软硬件环境,加载驱动程序,挂载根文件系统等操作之外,最重要的一个操作就是在系统中找到可执行程序 init ,启动 init 进程。

下面我们对 init 进程启动源码进行简析。但在简析之前,需要我们预先了解一些前置知识。

前置知识

  • 本文 Android 源码版本:android-6.0.1_r81

  • 关键路径:如下表所示:

File Path
init 进程 /system/core/init/init.cpp
init.rc 脚本 /system/core/rootdir/init.rc
readme.txt(Android Init Language) /system/core/init/readme.txt
  • init.rc 文件格式:init 进程在启动过程中,会启动许多关键的系统服务,如果在源码中一项项启动会显得很繁琐,而且不易扩展,因此 Android 引入了 init.rcinit.rc 是一个配置脚本,其内部使用一种名为 Android Init Language 的语言编写而成,详细内容可在 /system/core/init/readme.txt 中查看。

 这里我们就简单介绍下 init.rc 的一些主要配置语法:

 ☛ init.rc 内部主要包含五种类型语句:ActionServiceCommandOptionsImport。其中,主要的配置选项为:ActionService,Trigger 和 Command 是对 Action 的补充,Options 是对 Service 的补充。Import 用于导入其他 .rc 文件并进行解析。下面简单介绍下 ActionService 的配置语法:

Action:以 on 开头,trigger 是判断条件,command 是具体执行一些操作,当满足 trigger 条件时,执行这些 command

on  [&& ]* //设置触发器  
        //触发器触发之后要完成的操作
       
       

Service:服务是 init 进程启动的程序、当服务退出时 init 进程会视情况重启服务。其语法以 service 开头,name 是指定这个服务的名称,pathname 表示这个服务的执行文件路径,argument 表示传递给执行文件的参数,option 表示这个服务的一些配置。

service   [  ]*   
       

 ☛ .rc 文件是由一个个 Section 组成。action 加上 trigger 以及一些 command 组成一个Section;service 加上一些 option,也组成一个Section ;

:在 Android 7.0 以前,init 进程只解析根目录下的 init.rc 文件,但是随着版本的迭代,init.rc 越来越臃肿,所以在 7.0 以后,init.rc 一些业务被分拆到 /system/etc/init,/vendor/etc/init,/odm/etc/init 三个目录下。

init 进程启动流程 简析

init 进程的入口函数为:main,其位于:/system/core/init/init.cpp,具体内容如下:


int main(int argc, char** argv) {
    // 如果不是 ueventd,则进入
    if (!strcmp(basename(argv[0]), "ueventd")) {
        /* 该函数定义在在system/core/init/ueventd.cpp
         * init 进程会创建子进程 ueventd,uevented 主要负责设备节点文件的创建,
         * 创建方式有两种:冷插拔(Cold Plug) 和 热插拔(Hot Plug)
         */
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {
        /* 该函数定义在/system/core/init/watchdogd.cpp,主要用于启动“看门狗”
         *
         * “看门狗”本身是一个定时器电路,内部会不断的进行计时(或计数)操作,
         * 计算机系统和”看门狗”有两个引脚相连接,正常运行时每隔一段时间就会
         * 通过其中一个引脚向”看门狗”发送信号,”看门狗”接收到信号后会将计时器
         * 清零并重新开始计时,而一旦系统出现问题,进入死循环或任何阻塞状态,
         * 不能及时发送信号让”看门狗”的计时器清零,当计时结束时,
         * ”看门狗”就会通过另一个引脚向系统发送“复位信号”,让系统重启。 
         */
        return watchdogd_main(argc, argv);
    }

    // Clear the umask.
    umask(0);

    // 注册环境变量PATH
    // _PATH_DEFPATH 定义在 bionic/libc/include/paths.h 中
    add_environment("PATH", _PATH_DEFPATH);

    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);

    // Get the basic filesystem setup we need put together in the initramdisk
    // on / and then we'll let the rc file figure out the rest.
    if (is_first_stage) {
        // 挂载 tmpfs 到 /dev 目录
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        // 创建文件夹 /dev/pts
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        mount("proc", "/proc", "proc", 0, NULL);
        mount("sysfs", "/sys", "sysfs", 0, NULL);
    }

    // We must have some place other than / to create the device nodes for
    // kmsg and null, otherwise we won't be able to remount / read-only
    // later on. Now that tmpfs is mounted on /dev, we can actually talk
    // to the outside world.
    open_devnull_stdio();
    // 初始化内核log,位于节点/dev/kmsg
    klog_init();
    // 设置 log 级别
    klog_set_level(KLOG_NOTICE_LEVEL);

    NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");

    if (!is_first_stage) {
        // Indicate that booting is in progress to background fw loaders, etc.
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

        // 定义在 /system/core/init/property_service.cpp
        // 初始化属性服务
        property_init();

        // If arguments are passed both on the command line and in DT,
        // properties set in DT always have priority over the command-line ones.
        process_kernel_dt(); // 处理DT属性
        process_kernel_cmdline(); // 处理命令行属性

        // Propogate the kernel variables to internal variables
        // used by init as well as the current required properties.
        export_kernel_boot_props();// 处理其他的一些属性
    }

    // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
    // 初始化 selinux 安全策略
    selinux_initialize(is_first_stage);

    // If we're in the kernel domain, re-exec init to transition to the init domain now
    // that the SELinux policy has been loaded.
    if (is_first_stage) {
        if (restorecon("/init") == -1) {
            ERROR("restorecon failed: %s\n", strerror(errno));
            security_failure();
        }
        char* path = argv[0];
        char* args[] = { path, const_cast("--second-stage"), nullptr };
        // 调用 execv 重新执行 main 函数,进入 second-stage 阶段
        if (execv(path, args) == -1) {
            ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
            security_failure();
        }
    }

    // These directories were necessarily created before initial policy load
    // and therefore need their security context restored to the proper value.
    // This must happen before /dev is populated by ueventd.
    INFO("Running restorecon...\n");
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon_recursive("/sys");

    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %s\n", strerror(errno));
        exit(1);
    }

    signal_handler_init();

    // 加载default.prop文件
    property_load_boot_defaults();
    // 启动属性服务,开启一个socket监听系统属性的设置
    start_property_service();

    // 解析 init.rc
    init_parse_config_file("/init.rc");

    // 执行rc文件中触发器为 on early-init 的语句
    action_for_each_trigger("early-init", action_add_queue_tail);

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    // 等待冷插拔设备初始化完成
    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    // 设备组合件的初始化操作
    queue_builtin_action(keychord_init_action, "keychord_init");
    // 屏幕显示 Android 静态 Logo
    queue_builtin_action(console_init_action, "console_init");

    // Trigger all the boot actions to get us started.
    // 触发rc文件中触发器为on init的语句
    action_for_each_trigger("init", action_add_queue_tail);

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    // Don't mount filesystems or start core system services in charger mode.
    char bootmode[PROP_VALUE_MAX];
    // 当处于充电模式时,将 charger 加入执行队列;否则将 late-init 加入队列
    if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
        action_for_each_trigger("charger", action_add_queue_tail);
    } else {
        action_for_each_trigger("late-init", action_add_queue_tail);
    }

    // Run all property triggers based on current state of the properties.
    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

    while (true) {
        if (!waiting_for_exec) {
            execute_one_command();
            restart_processes();
        }

        int timeout = -1;
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }

        if (!action_queue_empty() || cur_action) {
            timeout = 0;
        }

        bootchart_sample(&timeout);

        epoll_event ev;
        // 循环等待事件发生
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
        if (nr == -1) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }

    return 0;
}

从源码中看到,init 进程分为两个阶段:first-stagesecond-stage
第一个阶段 first-stage 主要做的事为:挂载一些必要的文件系统:tmpfs,devpts,proc,sysfs等,然后初始化 selinux 安全策略,在 selinux 初始化完成后就立即重新执行main函数,进入第二阶段 second-stage,也就是将 init 进程从内核态转化为用户态执行。

first-stagesecond-stage 都会做一些同样的工作,比如设置环境变量,设置日志,设置不同阶段的 selinux 策略。对于 second-stage 来说,其主要做的就是:初始化和启动属性服务,解析 init.rc 文件,并按顺序先后执行触发器: on early-init -> init -> late-init。

属性服务 的作用是让 init 进程可以对其他程序进行权限控制。在 Android 中,对属性的设置都统一交由 init 进程管理,其他进程不能直接修改属性,而必须通过属性服务申请。其实属性服务的是一个跨进程通讯,使用的通讯机制是 socket。这里我们就不深入代码讲解了,感兴趣的请自行查看。

对于 init.rc 文件的具体解析过程,我们这里也不做讲解。但简单提一下,init_parse_config_file("/init.rc"); 该函数源码位于:/system/core/init/init_parser.cpp ,主要做的就是对 init.rc 的文件内容进行解析,将解析出的 services 和 actions 等添加到运行队列中,等待 trigger 触发器的触发运行。

我们重点讲下 Zygote 的启动点。

启动 Zygote 进程

我们知道,Zygote 的启动是在 init 进程解析 init.rc 后启动的,那我们先来看下 init.rc 文件的内容:

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
import /init.trace.rc

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_score_adj -1000
···

这里看到 Zygote 的启动脚本被 import 了进来:import /init.${ro.zygote}.rc, ${ro.zygote} 会被替换成 ro.zyogte 的属性值,这个是由不同的硬件厂商自己定制的, 有四个值,zygote32、zygote64、zygote32_64、zygote64_32 ,也就是说可能有四种 .rc 文件。

这里我们选用 64 位处理器的版本来分析:/system/core/rootdir/init.zygote64.rc

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

service 选项用于通知 init 进程启动 zygote 程序,zygote 的执行程序路径为:/system/bin/app_process64,init 进程启动 zygote 时,传递的参数为: -Xzygote /system/bin --zygote --start-system-server。

class 的作用是用于批量管理 service。因此这里class main表示 zygote 服务属于类main。后续启动 main 的时候,就会启动 zygote 服务。

前面我们说过,init 进程在解析完 init.rc 文件后,会依次执行触发器:on early-init -> init -> late-init。我们再来看下 init.rc 文件内容:

on late-init
···
on nonencrypted
    class_start main // zygote 属于类 main
    class_start late_start
···

也就是说在 late-init 触发时,就会启动类 main 管理的服务,也就是会启动 zygote。

class_start 对应的处理方法是 do_class_start,该方法定义在system\core\init\builtins.cpp 中:

int do_class_start(int nargs, char **args){
        /* Starting a class does not start services
         * which are explicitly disabled.  They must
         * be started individually.
         */
    service_for_each_class(args[1], service_start_if_not_disabled);
    return 0;
}

该方法内部又会调用 service_for_each_class
/system/core/init/init_parser.cpp

void service_for_each_class(const char *classname,
                            void (*func)(struct service *svc))
{
    struct listnode *node;
    struct service *svc;
    list_for_each(node, &service_list) {
        svc = node_to_item(node, struct service, slist);
        if (!strcmp(svc->classname, classname)) {
            func(svc);
        }
    }
}

该方法遍历 service_list 链表,每找到一个 classnamemain 的 service,就调用 service_start_if_not_disabled 来进一步处理,service_start_if_not_disabled 方法源码如下:
/system/core/init/builtins.cpp

static void service_start_if_not_disabled(struct service *svc)
{
    if (!(svc->flags & SVC_DISABLED)) {
        service_start(svc, NULL);
    } else {
        svc->flags |= SVC_DISABLED_START;
    }
}

可以看到,最终是调用 service_start 来启动服务。service_start 源码如下:
/system/core/init/init.cpp

void service_start(struct service *svc, const char *dynamic_args)
{
    ···
    // 如果Service已经运行,则不启动
    if (svc->flags & SVC_RUNNING) {
        return;
    }
    ···
    struct stat s;
    // 判断Service要启动的执行文件是否存在
    if (stat(svc->args[0], &s) != 0) {
        ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
        svc->flags |= SVC_DISABLED;
        return;
    }
    ···
    NOTICE("Starting service '%s'...\n", svc->name);

    // fork 函数创建子进程
    pid_t pid = fork();
    if (pid == 0) { // 运行在子进程当中
        struct socketinfo *si;
        struct svcenvinfo *ei;
        char tmp[32];
        int fd, sz;

        umask(077);
        ··· 
        if (!dynamic_args) {
            // 加载可执行文件
            if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
                ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
            }
        ···
        _exit(127);
    }
    ···
}

该函数会通过调用 fork 函数来创建子进程,并在子进程中调用 execve 来加载可执行文件,对于 zygote 服务来说,也就是会执行 /system/bin/app_process64。那接下来该可执行文件的 main 函数就会被调用:
framework/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{
    ···
    // Process command line arguments
    // ignore argv[0]
    argc--;
    argv++;

    // --zygote : Start in zygote mode
    // --start-system-server : Start the system server.
    // --application : Start in application (stand alone, non zygote) mode.
    // --nice-name : The nice name for this process.
    ··· 
    // Parse runtime arguments.  Stop at first unrecognized option.
    ···
    // 解析参数
    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }
    ···
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}

该函数会先对参数进行解析,对照 init.zygote64.rc 传递的参数:-Xzygote /system/bin --zygote --start-system-server,可以解析得出zygote=true,startSystemServer =true。于是最终会通过 AppRuntime.start 方法启动 zygote。

到此,init 进程就成功启动了 zygote 进程。

总结

init 进程启动时,总共经历两个阶段:内核态用户态

内核态 阶段下,init 进程主要做挂载一些必要的设备节点(tmpfs,devpts,proc,sysfs····),初始化 selinux 安全策略等操作,然后切换到 用户态(通过传递参数 --second-stage,并调用 execv 进行重启切换);

用户态 阶段下,init 进程最主要的一个操作就是解析 init.rc 文件,并按照顺序先后执行触发器:on early-init -> init -> late-init。

init 进程每遇到一个服务(service)时,就会通过 fork 出一个子进程来启动该服务。

zygote 在 init.rc 中是作为一个服务(service)存在,当 init.rc 文件被解析完成后,init 进程最终会在执行触发器 late-init 的过程中启动 zygote 服务(zygote 服务进程就是 init 进程 fork 出来的一个子进程,在该进程最后,会通过 AppRuntime.start 真正启动 zygote)。

参考

  • Android6.0系统启动流程分析一:init进程

  • Android6.0系统启动流程分析二:zygote进程

  • Android 8.0 系统启动流程之init进程--第一阶段(四)

  • Android 8.0 系统启动流程之init.rc语法规则(六)

  • Android系统启动-Init篇

你可能感兴趣的:(Android 系统启动 - init 进程)