Init 进程启动分析一

424 阅读5分钟

下面为你通俗易懂且详尽地总结 Android 系统启动中 Init 进程的启动过程,带你深入了解这个用户空间首个进程的关键作用:

一、内核启动 Init 进程:从内核到用户空间的「交接」

当 Linux 内核完成初始化后,会通过 run_init_process 函数寻找并启动用户空间的第一个进程 ——init 进程(进程号永远为 1)。

  • 查找路径:内核会按顺序在以下路径寻找 init 可执行文件:

    1. 根文件系统中的 /init(Android 设备通常放在这里)。
    2. 其他备用路径:/sbin/init → /etc/init → /bin/init → /bin/sh
  • 执行逻辑:找到后直接执行,例如 AOSP 系统中 init 程序位于 /init,内核会加载并运行它。

二、Init 进程的入口:main 函数的「参数判断」

Init 进程的源码位于 system/core/init/main.cpp,其 main 函数会根据启动参数决定执行路径:

  1. 参数为 ueventd:启动内核事件守护进程(处理硬件热插拔事件)。
  2. 参数为 subcontext:处理子上下文环境(如特定服务的初始化)。
  3. 参数为 selinux_setup:进入 SELinux 设置阶段(SELinux 是 Android 的安全策略系统)。
  4. 参数为 second_stage:进入第二阶段初始化(后续详述)。
  5. 无参数:直接进入 第一阶段初始化(FirstStageMain)
    关键点:内核启动 init 时不带参数,因此默认执行 FirstStageMain

三、Init 第一阶段:FirstStageMain 的核心任务

第一阶段的目标是搭建最小系统环境,为后续服务启动做准备,主要分为以下步骤:

1. 设置崩溃重启机制(InstallRebootSignalHandlers)

  • 作用:监听致命信号(如 SIGABRT、SIGSEGV),一旦 init 崩溃,直接重启到 Bootloader(开发模式下),避免系统卡死。
  • 原理:通过 sigaction 注册信号处理函数,子进程崩溃时直接退出,父进程(init)崩溃时触发系统重启。

2. 挂载虚拟文件系统(核心操作)

Init 会挂载多个内存型文件系统,这些系统不占用磁盘空间,用于临时存储系统运行时数据:

  • tmpfs 挂载

    • /dev:存储硬件设备节点(如键盘、屏幕驱动)。
    • /mnt/apex:临时挂载点,用于存放厂商分区或动态库。
  • devpts 挂载

    • /dev/pts:管理虚拟终端(如 SSH 连接时的终端设备)。
  • proc 挂载

    • /proc:暴露内核进程信息(如 proc/cmdline 存储启动参数)。
  • sysfs 挂载

    • /sys:提供硬件设备的属性接口(如读取电池状态)。
  • selinuxfs 挂载

    • /sys/fs/selinux:存储 SELinux 策略文件。

类比:这些挂载相当于在内存中搭建了一个「临时仓库」,存放系统运行必需的「工具和清单」。

3. 重定向标准输入输出(SetStdioToDevNull)

  • 操作:将 stdin(输入)、stdout(输出)、stderr(错误)重定向到 /dev/null(黑洞设备)。
  • 原因:Init 作为后台进程,不需要用户交互,避免无关输出干扰系统日志。

4. 初始化内核日志(InitKernelLogging)

  • 问题:Init 启动时,Android 日志系统(Logcat)尚未就绪,如何记录日志?

  • 解决方案

    • 通过 KernelLogger 将日志写入 /dev/kmsg(内核消息缓冲区)。
    • 后续可通过 logcat -b kernel 查看这些早期日志。
  • 实现

    • 格式化日志内容(如 <级别>标签: 消息),通过 writev 系统调用写入内核缓冲区。

5. 挂载关键分区(DoFirstStageMount)

  • 目标:挂载 /system(系统分区)、/vendor(厂商分区)等核心分区。

  • 流程

    1. AVB 校验:通过 Android Verified Boot(AVB)验证分区完整性,防止篡改。
    2. 设备初始化:创建逻辑分区(如加密分区解密)。
    3. 实际挂载:将分区挂载到指定路径,供后续服务读取文件(如 SELinux 策略、系统二进制文件)。

6. 进入第二阶段:execv 重新执行 init

第一阶段最后,通过 execv 带参数 selinux_setup 重新执行 init 程序,进入第二阶段初始化

c

const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args)); // 切换到第二阶段

作用:第一阶段完成底层环境搭建,第二阶段负责启动系统服务、解析 init.rc 脚本等更高层任务。

四、关键知识点总结

  1. Init 进程的地位

    • 它是用户空间的「根进程」,所有应用和服务都由它启动。
    • 分为两阶段初始化:第一阶段构建最小系统环境,第二阶段启动核心服务。
  2. 虚拟文件系统的作用

    • tmpfs/devpts/proc/sysfs 是系统运行的基础设施,分别用于设备管理、进程信息、硬件属性等。
  3. 日志与安全机制

    • 早期日志通过内核缓冲区记录,确保可追溯。
    • AVB 校验和 SELinux 设置是 Android 系统安全的基石,防止恶意篡改和越权访问。
  4. 阶段切换的本质

    • 通过 execv 重新执行 init 程序并传递参数,实现代码逻辑的「分段执行」,避免单阶段过于臃肿。

五、类比说明

Init 进程的启动类似「搭建舞台」:

  • 第一阶段:先搬来舞台框架(挂载文件系统)、准备基础设备(创建设备节点)、拉上幕布(重定向输入输出),并测试音响(日志系统)。

  • 第二阶段:幕布拉开后,开始表演真正的节目 —— 启动演员(系统服务)、执行剧本(init.rc 脚本),最终呈现完整的系统功能。

通过这两个阶段,Init 进程逐步将 Android 系统从「内核控制」过渡到「用户空间主导」,为后续 Zygote、SystemServer 等进程的启动铺平道路。