Android系统开发进阶-init 进程启动流程

上一篇文章我们详细解析了 init.rc 文件的语法规范。同学们可以对照着去看看 Android/system/core/rootdir/init.rc 这个文件。我们会发现里面有很多 event trigger, 比如 on boot, on fs 等,然后系统的各种核心服务,基本上都是在这些 event trigger 里面启动的。以前一直搞不明白这些 event trigger 的先后执行顺序。这次把源码看了一遍过,终于搞明白了。本文不会从源码的角度去分析 init 的启动流程,只是把自己看过源码之后的理解, 整理成一张思维导图,相信同学们看过这张图之后应该能大概明白 init 都干了哪些活。这些活的先后顺序,这就足够了。
init 启动流程概要:
init 启动流程概要

从上图来看, init 的初始化可以分为三个阶段:

  • FirstStageMain
    这个阶段的工作主要是挂载一些虚拟文件系统,还有挂载两个系统最核心的分区 system, vendor。
    这两步都是为了后续的工作做准备。比如设置 selinux 需要从 system, vendor 分区中读取 sepolicy。 SecondStageMain 需要从 system, vendor 分区中读取 property 信息,需要启动系统核心服务。
  • SetupSelinux
    设置 Selinux 安全机制。
  • SecondStageMain
    启动 property 服务(从这里可以知道 property 服务是运行在 init 进程里面的),然后加载各分区下的 .rc 文件。然后把各种事件加入到事件队列。最后进入事件循环,按队列方式(先进先出)处理事件队列中的事件。

从上面的流程来看,其实 init 把大部分的初始化工作都放到 init.rc 中, 这样可以更灵活的适配各种平台的定制需求。

需要提一下的是各阶段的启动方式,第一阶段的启动是由 kernel 启动的,这里不讨论,第二,三阶段的启动比较有意思,都是通过 exec 来启动的:

1
2
3
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
1
2
3
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));

我也不知道为什么要通过 execv 启动一个新进程替换旧进程的方式来切换各阶段呢?这样相比直接一个函数调用来说有什么好处呢?知道的同学麻烦指导一下哈。

单独把 secondStage 部分的流程拎出来扩展:
secondStage

这张图主要是想表现一下各事件触发器的触发时序,各服务的启动时序。整理如下:
0. 初始化 biner 驱动相关

  1. early-init 事件
    启动了 ueventd, apexd-bootstrap 服务

  2. init 事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    on init
    # 创建各种系统运行所需的目录,更改各种目录的权限
    mkdir /mnt/media_rw 0750 root media_rw
    mkdir /mnt/user 0755 root root
    # Start logd before any other services run to ensure we capture all of their logs.
    start logd

    # Start essential services.
    start servicemanager
    start hwservicemanager
    start vndservicemanager
  3. late-init 事件
    把一堆的事件按顺序加入事件队列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    on late-init
    trigger early-fs
    trigger fs
    trigger post-fs
    trigger late-fs
    trigger post-fs-data
    trigger load_persist_props_action
    trigger zygote-start
    trigger firmware_mounts_complete
    trigger early-boot
    trigger boot
  4. early-fs 事件
    启动 vold 服务,负责外接磁盘的挂载。

  5. fs 事件
    Action 定义在 vendor/etc/init/init.ranchu.rc 中, 只有一个动作,根据 fstab 挂载所有分区。

    1
    mount_all /vendor/etc/fstab.ranchu

    在挂完文件系统后,代码中会把 nonencrypted 事件加入到事件队列。

  6. post-fs 事件
    重新挂载 / 根分区为只读分区,创建各种系统目录。

  7. late-fs 事件
    启动 early_hal 类型的所有服务,当然被 disable 的除外。

  8. post-fs-data 事件
    启动 bootchart, apexd, 等待 apexd 服务完成

  9. load_persist_props_action 事件
    加载 /data 分区下的 persist property

  10. zygote-start 事件
    Android 世界的鼻祖在这里启动了

  11. firmware_mounts_complete 事件
    这个不知道干啥用的了

  12. early-boot 事件
    目前没有使用

  13. boot 事件
    启动 hal, core 类型的所有服务,当然被 disable 的除外。

  14. nonencrypted 事件
    启动 main 类型的所有服务,当然被 disable 的除外。

以上,我们可以知道各种服务的启动顺序了:
最先启动的是 logd, 和三个 servicemanager 服务。
接下来按这个顺序来启动各种类型的服务:
early_hal –> hal –> core –> main