很多资料把 Android init 解释成"系统启动入口"。这句话方向没错,却隐藏了两个问题:
css
它只是一个 main(),还是一个会长期存在的进程?
它和 netd、vold、SurfaceFlinger 这些 Native Service 有什么本质区别?
先把 init 放回正确层级:
Android init 是 Kernel 启动的首个用户空间进程,也就是 PID 1。它先建立后续代码能够运行的基础环境,再解析 init 配置、执行启动动作、创建并监督关键子进程。它本身是 Native 进程,但不是由另一个 init service 启动的普通 Native Service。
"普通 Native Service"不是 AOSP 的统一类型名。本文用它泛指由 init 按 rc 配置拉起、负责某项具体系统能力的 Native daemon/service,例如 vold、netd 和 SurfaceFlinger。
本文只解决 init 的身份和职责边界;完整 rc 语法、属性服务内部协议、ueventd、SELinux 策略编译和 first-stage mount 细节都留给后续或独立专题。
源码基线: Android 16 QPR2
android-16.0.0_r4,system/corecommite9d1fa3705d7fbd0ac1c942bc4c276161bfbfed1。
一、先拆掉第一个误解:不是 init "抢到"了 PID 1
Linux Kernel 建立第一个用户空间任务时会选择 init 程序并执行。Android 的启动镜像与命令行让这个入口落到 /init;因此这个进程获得 PID 1。
因果关系是:
csharp
Kernel 把 init 作为首个用户空间程序启动
↓
它成为 PID 1
而不是:
csharp
某个普通进程先启动
↓
发现自己叫 init
↓
申请成为 PID 1
进程名不赋予 PID 1 身份。理论上 Kernel 可以执行另一个程序作为 PID 1;Android 的用户空间设计、配置和恢复机制则共同约定由 Android init 承担这个角色。
二、PID 1 特殊在哪里?
把 init 只当成"开机时执行一次的 main()"会漏掉它的长期责任。
1. 它是 Android 用户空间进程树的根
init 直接或间接创建后续大量进程:
swift
init (PID 1)
├─ ueventd
├─ servicemanager
├─ surfaceflinger
├─ netd
├─ vold
└─ app_process64 → Zygote
├─ system_server
└─ App processes
这里的树表示父子关系,不表示所有进程都由 init 逐个直接 fork。比如 system_server 和普通 App 都来自 Zygote;但 Zygote 本身由 init 的 service 机制启动。
2. 它要回收退出的子进程
子进程退出后会留下退出状态,父进程必须 wait 才能完成回收。PID 1 还会接管没有可用父进程的孤儿后代,因此 init 不能只负责"点火"然后退出。
Android 16 second-stage init 把 ReapAnyOutstandingChildren 设为 epoll 的首要回调,再安装 SIGCHLD 处理路径;这样能在处理新的控制请求前先更新已经退出的服务状态。对应源码可见 SecondStageMain() 与 Service::Reap()。
3. 它不能像普通服务一样随意退出
普通守护进程退出后,init 可以按配置重启它;PID 1 自己退出则意味着用户空间根进程消失,Linux 不会把它当成普通服务崩溃处理。
这也是"init 监督其他进程"与"谁监督 init"不对称的原因。Android 可以通过 watchdog、fatal 路径和重启机制处理 init 发现的灾难,但不能再用一层普通 init service 包住 PID 1。
Linux 对 PID 1 的部分默认信号处理也有特殊规则;Android init 仍会主动安装 SIGCHLD 与关机等处理。本文不展开 Linux 信号细节,只保留身份边界:
PID 1 不是"权限更高的普通 daemon",而是 Linux 用户空间生命周期的根角色。
三、Android init 不是只执行一次:它分成三个连续阶段
Android 16 的 init/main.cpp 根据参数进入三个阶段:
kotlin
if (argv[1] == "selinux_setup") {
return SetupSelinux(argv);
}
if (argv[1] == "second_stage") {
return SecondStageMain(argc, argv);
}
return FirstStageMain(argc, argv);
这是整理后的最小骨架。完整关系是:

bash
FirstStageMain
↓ exec /system/bin/init selinux_setup
SetupSelinux
↓ exec /system/bin/init second_stage
SecondStageMain
↓ 长期事件循环
关键边界:这里的 exec 不是创建新进程
fork 创建新进程;exec 用新程序替换当前进程的代码与地址空间映像。
因此:
ini
第一阶段 init PID = 1
SELinux setup PID = 1
第二阶段 init PID = 1
PID 没变,执行的程序映像和参数变了。
Android 16 first-stage init 最后执行:
arduino
execv("/system/bin/init", {"/system/bin/init", "selinux_setup", nullptr});
源码位置:first_stage_init.cpp。SELinux 阶段完成后又用 second_stage 参数执行同一入口,见 selinux.cpp。
所以不能写成:
first-stage init 启动了另一个 second-stage init 进程。
更准确的是:
PID 1 通过连续
exec切换程序映像,从 first stage 进入 SELinux setup,再进入长期运行的 second stage。
四、为什么要拆阶段?因为后面的代码一开始还不可用
开机最早期,完整的 /system、/vendor、动态分区、设备节点和 SELinux 环境可能还没有准备好。此时不能直接假设普通 Android 用户空间已经存在。
第一阶段:只准备"加载完整系统所需的最小条件"
AOSP init/README.md 把早期序列拆成 first-stage init、SELinux setup 和 second-stage init。第一阶段的稳定职责包括:
bash
挂载 /dev、/proc 等早期基础文件系统;
完成 first-stage mount;
让包含系统代码的分区可访问;
在需要时切换根文件系统;
为下一阶段保留必要资源。
具体 /init 是 ramdisk 中的静态程序、还是指向 /system/bin/init,取决于设备启动布局。不要把某一种设备打包形式写成所有 Android 设备的唯一实现。
SELinux setup:让后续用户空间在正确安全策略下运行
这一阶段加载策略、恢复 init 自身安全上下文,然后 exec 到 second stage。它不是"开机以后再慢慢补安全",而是在大量普通服务创建前建立安全边界。
第二阶段:进入 Android init 的长期工作模式
第二阶段才开始完整建立:
csharp
property service;
ActionManager 与 ServiceList;
init.rc 及各分区配置解析;
early-init、init、late-init 等触发序列;
子进程回收与服务重启;
控制消息、属性变化和关机事件;
epoll 主事件循环。
Android 16 SecondStageMain() 加载启动配置后,依次排入内建 action 和 early-init、init、late-init 等事件;随后循环执行一个 command、计算下次唤醒时间并进入 epoll 等待,见 init.cpp。
五、为什么 init 不是普通 Native Service?
init 和 SurfaceFlinger 都是 C/C++ 用户态进程,但"都用 Native 写"不代表系统角色相同。
| 维度 | Android init | 普通 Native Service |
|---|---|---|
| 谁启动 | Kernel 作为首个用户空间程序启动 | 通常由 init 根据 rc 启动 |
| PID | 固定承担 PID 1 角色 | 普通动态 PID |
| 配置依赖 | 先存在,之后才能解析 rc | 可以由 rc service 描述 |
| 主要责任 | 基础环境、触发器、进程创建与监督 | 提供某项具体系统能力 |
| 退出处理 | 不能由另一层普通 init 自动重启 | 可由 init 按规则重启 |
| 生命周期 | 从用户空间起点持续到关机 | 按服务配置启停 |
把 init 写进自己的 rc 会形成循环:
csharp
要解析 rc,必须先有 init;
要按 rc 启动 init,又必须先解析 rc。
所以 init 是配置执行器和进程监督者,不是被自己管理的一个配置单元。
六、init 为什么既是启动器,又是监督者?
如果 init 只负责 fork 一次就不再管理,关键进程退出后,资源清理、状态更新、重启限速和失败升级都没有统一责任方。Android init 因此用同一个 Service 对象保存进程的创建配置与退出策略;Service::Reap() 回收子进程后更新 init.svc.<name>,再决定停住、排队重启或进入更强恢复路径。
这一节只说明 init 为什么必须长期监督。oneshot、disabled、重启调度与完整 SIGCHLD → Reap 状态链,以第二篇:init.rc 从配置到进程为唯一事实源。
七、init 的主循环不是无休止地扫配置文件
第二阶段完成解析后,不会周期性重读全部 rc。配置先变成 Action、Service 等对象;运行时由事件、属性变化、控制消息和 SIGCHLD 驱动 Action 排队,主循环逐条执行 Command,没有立即工作时进入 epoll 等待。
因此 Android init 更像事件驱动的用户空间总调度器,而不是按顺序执行完就退出的 shell 脚本解释器。完整队列模型留给下一篇。
不过这个类比有边界:init 不是通用任务编排平台,也不负责 Framework 对象的生命周期。它管理的是用户空间基础动作和进程单元。
八、看到 init 进程,能证明什么?
在设备上可以先观察:
csharp
adb shell ps -A -o USER,PID,PPID,NAME | grep -E '(^| )init$'
adb shell getprop init.svc.zygote
adb shell getprop ro.boottime.init.first_stage
adb shell getprop ro.boottime.init.selinux
Windows PowerShell 可以把 grep 换成 Select-String,但要注意:adb shell 内部命令和主机侧管道属于两个环境。
证据边界如下:
| 观察 | 能够证明 | 不能证明 |
|---|---|---|
| PID 1 名为 init | Android init 进程仍存在 | 所有 boot action 已完成 |
init.svc.zygote=running |
init 记录的 zygote service 当前处于 running | Zygote 已成功 fork system_server |
| first-stage / selinux boottime 有值 | init 已记录对应阶段耗时 | 后续系统服务已经 ready |
init 日志出现 starting service 'zygote' |
zygote 启动命令已进入 Service::Start() |
exec app_process 和 Java Runtime 一定成功 |
如果只看到 init 存在,最多能证明用户空间根进程还活着。要证明 Zygote、system_server 和 Framework 能力,必须继续寻找下一阶段证据。
九、init 的职责边界停在哪里?
可以把答案压缩成六点:
markdown
1. init 是 Native 可执行程序,也是运行该程序的 PID 1 进程名称;
2. 它由 Kernel 作为首个 Android 用户空间程序启动;
3. first stage、SELinux setup、second stage 通过 exec 连续发生,PID 仍为 1;
4. 前两段建立完整用户空间能够运行的基础条件;
5. second stage 解析配置并进入事件循环;
6. 它根据配置创建、回收和监督后续关键进程。
本文还没有回答:
init.rc中几行文本,怎样变成可执行 Action 和会被监督的进程?
要跨过这个断点,就要把 rc 从"看起来像脚本"还原成内存对象、事件队列和进程生命周期:init.rc 不是普通脚本,service、action 和 property 怎样驱动启动?