启动阶段
本文面向已经读过 Init主循环 的读者。前文说明 ActionManager 如何逐命令消费事件;本文把这套机制放回 Android 17 的真实 init.rc,回答 early-init、init、late-init、post-fs-data、zygote-start 和 boot 究竟如何连接。
这里的“阶段”不是 PID 1 内部的枚举,也不是执行失败后可以整体回滚的事务。它们是事件名:C++ 或 rc builtin 把事件放进队列,所有匹配的 on <event> action 按解析顺序执行。本文会区分“事件已入队”“action 已匹配”“命令已执行”“服务已运行”四个时间点,并解释 charger、recovery、Microdroid、设备 rc 和属性条件如何改变主线。
本文不逐行解释数百条目录权限,也不替代后续 rc Parser、builtin 和服务专题。读完后,读者应能从 SecondStageMain() 还原标准启动事件链,找到每个阶段的 owner、consumer 与屏障,判断 Zygote、HAL、main class 分别在哪个事件启动,并用源码测试和设备日志验证阶段顺序。
1. 阶段本质
1.1 事件模型
ActionManager 的 event_queue_ 保存 EventTrigger、PropertyChange 或 BuiltinAction。early-init 等名称只是 EventTrigger 中的字符串,不存在统一的 BootStage 状态字段。
源码文件:system/core/init/action_manager.h
std::queue<std::variant<EventTrigger, PropertyChange, BuiltinAction>>
event_queue_ GUARDED_BY(event_queue_lock_);
std::queue<const Action*> current_executing_actions_;
std::size_t current_command_ = 0;阶段状态分散在文件系统、property、服务 flags 和 action queue 中。因此看到 processing action (post-fs-data),只能证明对应 action 开始消费,不能证明 /data 上的全部工作、APEX 激活或后续服务已经完成。
1.2 匹配时机
事件 action 的匹配条件由 Action::CheckEvent() 决定。事件名必须相等;若 action 还带 property 条件,则在事件被匹配的当下读取其他 property。
源码文件:system/core/init/action.cpp
bool Action::CheckEvent(const EventTrigger& event_trigger) const {
return event_trigger == event_trigger_ && CheckPropertyTriggers();
}
bool Action::CheckEvent(const PropertyChange& property_change) const {
const auto& [name, value] = property_change;
return event_trigger_.empty() && CheckPropertyTriggers(name, value);
}这给出一个重要区别:
| 写法 | 触发方式 | 条件检查时机 |
|---|---|---|
on boot | boot 事件 | 事件消费时 |
on boot && property:x=y | boot 事件 | boot 消费时读取 x |
on property:x=y | property 变化或全量扫描 | property 事件消费时 |
组合事件 action 不会因为 x 日后变成 y 而补执行;它需要下一次同名事件。纯 property action 才能由后续属性变化触发。
2. 脚本顺序
2.1 入口文件
标准路径先解析 /system/etc/init/hw/init.rc,再依次解析 system、system_ext、vendor、odm 和 product 的 init 目录。ro.boot.init_rc 非空时则改用指定脚本。
源码文件:system/core/init/init.cpp
相关函数/类型:LoadBootScripts
if (bootscript.empty()) {
parser.ParseConfig("/system/etc/init/hw/init.rc");
parser.ParseConfig("/system/etc/init");
parser.ParseConfig("/system_ext/etc/init");
parser.ParseConfig("/vendor/etc/init");
parser.ParseConfig("/odm/etc/init");
parser.ParseConfig("/product/etc/init");
} else {
parser.ParseConfig(bootscript);
}目录内普通文件先排序再解析,所以同一目录的 action 顺序稳定。不同分区仍按上面的调用顺序进入 actions_。
2.2 Import顺序
import 行并不在解析到该行时立刻递归。ImportParser 先记录路径,到当前文件 EOF 的 EndFile() 再依次 ParseConfig()。
源码文件:system/core/init/import_parser.cpp
void ImportParser::EndFile() {
auto current_imports = std::move(imports_);
imports_.clear();
for (const auto& [import, line_num] : current_imports) {
parser_->ParseConfig(import);
}
}因此主 init.rc 中已经完成提交的 action 位于其 imported 文件 action 之前。一个事件匹配多个文件时,ActionManager 遍历 actions_ 的顺序就是后续执行顺序;这也是设备厂商不能只看某个 rc 文件局部位置判断全局先后的原因。
3. 初始队列
3.1 C++入口
SecondStageMain() 在进入主循环前创建初始队列。Android 17 的真实顺序中,cgroup builtin 位于 early-init 之前;coldboot 等待、mmap、keychord 初始化位于 early-init 与 init 之间。
源码文件:system/core/init/init.cpp
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueEventTrigger("early-init");
am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction,
"ConnectEarlyStageSnapuserd");
am.QueueBuiltinAction(wait_for_coldboot_done_action,
"wait_for_coldboot_done");
if (!IsMicrodroid()) {
am.QueueBuiltinAction(CheckTradeInModeStatus,
"CheckTradeInModeStatus");
}
am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
Keychords keychords;
am.QueueBuiltinAction(
[&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
for (const auto& svc : ServiceList::GetInstance()) {
keychords.Register(svc->keycodes());
}
keychords.Start(&epoll, HandleKeychord);
return {};
},
"KeychordInit");
am.QueueEventTrigger("init");
am.QueueBuiltinAction(SetCopyRollbackLogsAction, "CopyRollbackLogs");这纠正了“early-init 挂载 cgroup”的旧理解:rc 注释明确写着 cgroup 在 early-init 之前由 /etc/cgroups.json 对应的 builtin 建立,early-init 只消费已经存在的层级。
3.2 模式分流
等待可选 UDC 线程后,只有 charger mode 改走 charger;normal 和 recovery 都进入 late-init。不过 recovery 构建加载的是 recovery rc 内容,不能把 system rootdir/init.rc 的命令原样套用。
源码文件:system/core/init/init.cpp
if (bootmode == BootMode::CHARGER_MODE) {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
am.QueueBuiltinAction(queue_property_triggers_action,
"queue_property_triggers");queue_property_triggers_action 已经在初始队列里,但它实际执行时还会追加两个 builtin;这会影响启动早期 pure property action 的生效时机,后文单独展开。
4. EarlyInit
4.1 前置资源
进入 on early-init 前,第二阶段已经建立属性区、SELinux label、Epoll、signal handler、property service、mount namespace,并执行 SetupCgroupsAction。所以 early-init 不是“系统什么都没有”的起点,而是“可执行 rc builtin 的最早公共事件”。
4.2 核心动作
Android 17 根 init.rc 的 early-init 处理启动观测、内核接口、linkerconfig、ueventd、tracefs 和 bootstrap APEX。下面保留能建立依赖关系的真实片段。
源码文件:system/core/rootdir/init.rc
相关函数/类型:on early-init
on early-init
mkdir /dev/bootchart 0755 root root
bootchart start
write /proc/sys/kernel/sysrq 0
write /proc/sys/kernel/modprobe \n
mkdir /linkerconfig/bootstrap 0755
mkdir /linkerconfig/default 0755
write /linkerconfig/bootstrap/ld.config.txt \#
write /linkerconfig/default/ld.config.txt \#
mount none /linkerconfig/bootstrap /linkerconfig bind rec
start ueventd
mount tracefs tracefs /sys/kernel/tracing gid=3012
exec_start init_dev_config
restorecon /metadata
exec_start apexd-bootstrap
perform_apex_configexec_start 是同步屏障:服务退出前主循环暂停普通 action。因此 apexd-bootstrap 完成后,后续 early-init 命令和其他阶段才继续。start ueventd 则是异步启动;真正的 coldboot 完成由 C++ 队列中的 wait_for_coldboot_done_action 另行等待。
4.3 条件Action
同一事件还能匹配:
源码文件:system/core/rootdir/init.rc
on early-init && property:ro.boot.bootchart.enabled=""
rmdir /dev/bootchart属性为空时才删除 bootchart 目录。该判断发生在 early-init 事件消费时;以后再清空属性不会补执行这条 action。
5. Init事件
5.1 基础结构
on init 建立标准 fd symlink、cpuctl 子目录、挂载点和大量权限。它消费 early-init、coldboot 等前置结果,但 /data 尚未进入 post-fs-data 语义。
源码文件:system/core/rootdir/init.rc
on init
sysclktz 0
copy /proc/cmdline /dev/urandom
copy /proc/bootconfig /dev/urandom
symlink /proc/self/fd/0 /dev/stdin
symlink /proc/self/fd/1 /dev/stdout
symlink /proc/self/fd/2 /dev/stderr
mkdir /dev/cpuctl/foreground
mkdir /dev/cpuctl/background
mkdir /dev/cpuctl/top-app
mkdir /dev/cpuctl/system这里的 mkdir 没有事务包装。中间某条权限命令失败时,已创建的目录继续存在,后续命令仍执行。
5.2 核心服务
init 事件后段先启动日志和低内存服务,再启动三类 servicemanager。
源码文件:system/core/rootdir/init.rc
on init
start logd
write /proc/sys/vm/watermark_boost_factor 0
start lmkd
start servicemanager
start hwservicemanager
start vndservicemanager
mkdir /mnt/vm 0755 root system
mount tmpfs tmpfs /mnt/vm nosuid nodev noexec rwstart 返回成功表示 Service::Start() 已发起进程启动,不表示服务已注册 Binder 接口或完成业务初始化。后续消费者若需要完成信号,必须依赖 socket、property、binder 查询或显式 wait,而不能只依赖事件名。
6. LateInit调度
6.1 Trigger链
根 init.rc 的 late-init 几乎不直接配置资源,而是定义文件系统到 framework 前的事件顺序。
源码文件:system/core/rootdir/init.rc
on late-init
trigger early-fs
trigger fs
trigger post-fs
trigger late-fs
trigger post-fs-data
trigger load-bpf-programs
trigger zygote-start
trigger firmware_mounts_complete
trigger early-boot
trigger bootdo_trigger() 的实现只有入队:
源码文件:system/core/init/builtins.cpp
static Result<void> do_trigger(const BuiltinArguments& args) {
ActionManager::GetInstance().QueueEventTrigger(args[1]);
return {};
}每条 trigger 命令执行时把事件追加到 FIFO 队尾。它不递归调用目标 action,也不等待目标 action 完成。
6.2 队列顺序
ActionManager 只有在 current_executing_actions_ 为空时才消费下一个 event。因此 late-init 的所有匹配 action 都完成后,才会开始处理它追加的 early-fs。目标阶段的所有 action 又会在下一个事件前完成。
这是一种协作式阶段顺序,不是 C++ 栈上的函数嵌套。队列中可以穿插更早已存在的 builtin,目标 action 内也可以追加新事件。
7. 文件系统链
7.1 EarlyFs
根配置的 early-fs 只启动 vold,为 metadata、userdata checkpoint 和后续加密挂载准备消费者。
源码文件:system/core/rootdir/init.rc
相关函数/类型:on early-fs
on early-fs
start vold设备 rc 通常在同一事件执行自身前置操作。所有匹配 action 的先后由解析顺序决定,而不是“root action 永远完成设备逻辑”。
7.2 Fs
标准根 init.rc 没有 on fs action;实际挂载通常由设备或产品 rc 提供。例如 goldfish 使用:
相关源码:
device/generic/goldfish/init/init.ranchu.rcon fson late-fs
on fs
mount_all /vendor/etc/fstab.ranchu --early
on late-fs
mount_all /vendor/etc/fstab.ranchu --late所以 fs 是扩展点,不是 root 文件中固定的一组 mount 命令。分析具体设备必须追踪其 init.${ro.hardware}.rc、vendor init 目录和 fstab。
7.3 PostFs
post-fs 表示早期 mount action 已完成,根配置开始只读 remount、storage bind、metadata 目录与安全标签处理。
源码文件:system/core/rootdir/init.rc
相关函数/类型:on post-fs
on post-fs
exec - system system -- /system/bin/vdc checkpoint markBootAttempt
mount rootfs rootfs / remount bind ro nodev
mount none /mnt/user/0 /storage bind rec
mount none none /storage slave rec
restorecon_recursive /metadata
mkdir /metadata/vold
mkdir /metadata/apex 0700 root system7.4 LateFs
late-fs 为带 latemount 的 fstab 项和解锁 userdata 前所需 HAL 预留。根配置启动 early_hal class,并根据内核版本配置 bootreceiver tracing。
源码文件:system/core/rootdir/init.rc
相关函数/类型:on late-fs
on late-fs
chmod 0755 /sys/kernel/tracing
chmod 0755 /sys/kernel/debug/tracing
class_start early_hal
on late-fs && property:ro.kernel.version=4.19
setprop bootreceiver.enable 0
on late-fs
setprop bootreceiver.enable ${bootreceiver.enable:-1}同一个 late-fs 事件会按 action 解析顺序执行多个段;带 property 条件的段在事件到达时决定是否加入执行队列。
8. Data屏障
8.1 进入条件
late-init 在 late-fs 后无条件入队 post-fs-data,但真正命令能否推进取决于 /data、vold、KeyMint、APEX 和 property 的运行结果。事件名本身不验证挂载状态。
8.2 前段资源
post-fs-data 先处理 checkpoint、/data 权限、加密 key、tombstone、keystore 和 persistent property。
源码文件:system/core/rootdir/init.rc
相关函数/类型:on post-fs-data
on post-fs-data
exec - system system -- /system/bin/vdc checkpoint prepareCheckpoint
chown system system /data
chmod 0771 /data
restorecon /data
installkey /data
mkdir /data/tombstones 0775 system system encryption=Require
start tombstoned
enter_default_mount_ns
mkdir /data/misc/keystore 0700 keystore keystore
setprop keystore.boot_level 30
mkdir /data/property 0700 root root encryption=Require
load_persist_props
trigger load_persist_props_actionload_persist_props_action 是兼容 vendor rc 的事件;它不代表 persistent property 此刻才开始加载,注释明确说明实际加载已由前一条 load_persist_props 完成。
8.3 等待点
post-fs-data 中存在多种真正屏障:
源码文件:system/core/rootdir/init.rc
wait_for_prop apexd.status activated
wait_for_prop keystore.module_hash.sent true
perform_apex_config
exec_start mainline_aconfigd_init
exec_start derive_sdk
exec_start derive_classpath
exec_start art_boot
start odsign
wait_for_prop odsign.key.done 1wait_for_prop 设置全局 property waiter,主循环暂停普通 action;exec_start 设置 SVC_EXEC,直到子进程被 reap 才解除。这些命令把“队列顺序”升级成“结果依赖”,也是 post-fs-data 比普通目录创建更容易卡住的原因。
8.4 不可回滚
若某条普通 builtin 返回错误,Action::ExecuteCommand() 记录失败与耗时,但返回类型是 void,下一轮仍推进 current_command_。
源码文件:system/core/init/action.cpp
void Action::ExecuteCommand(const Command& command) const {
android::base::Timer t;
auto result = command.InvokeFunc(subcontext_);
auto duration = t.duration();
if (!result.has_value() || duration > 50ms || /* debug logging */) {
LOG(INFO) << "Command '" << command.BuildCommandString()
<< "' action=" << BuildTriggersString()
<< " took " << duration.count() << "ms and "
<< (result.ok() ? "succeeded"
: "failed: " + result.error().message());
}
}因此阶段没有统一 rollback。已创建目录、已设置属性、已启动服务会保留;后续命令是否还能成功取决于各自前置条件。fatal、shutdown、property wait 和 exec wait 是另外的控制路径,不能与普通 command error 混为一谈。
9. Zygote与Boot
9.1 ZygoteStart
Android 17 不在 on boot 中通过 class_start main 启动 Zygote。zygote-start 在 post-fs-data、BPF 之后单独等待 odsign verification,再直接启动两个 zygote service。
源码文件:system/core/rootdir/init.rc
on zygote-start
wait_for_prop odsign.verification.done 1
start statsd
start zygote
start zygote_secondaryZygote 的 owner 是 ServiceList 中的 service 对象;事件完成只说明 start 命令已调用,不证明 system_server 已 fork 或 framework 已 ready。
9.2 Boot事件
boot 事件负责网络、内核参数、设备权限和服务 class。根配置后段启动 binderized HAL 和 core class。
源码文件:system/core/rootdir/init.rc
on boot
ifup lo
hostname localhost
domainname localdomain
setprop net.tcp_def_init_rwnd 60
class_start hal
class_start coreon boot && property:ro.config.low_ram=true、debuggable 等条件 action 与普通 boot action 一同匹配。它们不是 boot 完成通知。
9.3 MainClass
main 和 late_start class 由 nonencrypted 事件启动:
源码文件:system/core/rootdir/init.rc
on nonencrypted
class_start main
class_start late_startnonencrypted 由 mount_all 的 fs_mgr 返回码决定。未加密、file-encrypted 或 metadata-encrypted 且 vold 已完成准备时,queue_fs_event() 都会追加该事件;需要 recovery 时则请求 wipe/reboot。
源码文件:system/core/init/builtins.cpp
if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
SetProperty("ro.crypto.state", "unsupported");
ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
} else if (code == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED ||
code == FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED ||
code == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
SetProperty("ro.crypto.state", "encrypted");
ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
}这说明“nonencrypted”是历史事件名,不等于设备真的未加密。消费者应看 ro.crypto.state 和 fs_mgr 结果,而不是从事件名推断存储模式。
10. 属性补链
10.1 延迟启用
启动前已经入队 queue_property_triggers_action。它执行时并不立即修改开关,而是继续追加两个事件:先 enable,再用空 PropertyChange 全量检查当前属性。
源码文件:system/core/init/init.cpp
static Result<void> queue_property_triggers_action(const BuiltinArguments&) {
ActionManager::GetInstance().QueueBuiltinAction(
property_enable_triggers_action,
"enable_property_trigger");
ActionManager::GetInstance().QueueAllPropertyActions();
return {};
}10.2 队列交错
初始 queue_property_triggers_action 排在 late-init 之后;late-init 执行时把 early-fs 到 boot 追加在它后面。随后 queue action 被消费,又把 enable 和全量 property scan 追加到这些阶段之后。
图表达的是标准初始队列关系,不是所有动态事件的绝对顺序。阶段中的 trigger、mount_all 和 property callback 还会继续向队尾追加事件。
10.3 BootCompleted
sys.boot_completed=1 是 framework 后期设置的 property,不是 init 的 boot 事件。property trigger 启用后,它匹配:
源码文件:system/core/rootdir/init.rc
on property:sys.boot_completed=1
bootchart stop
exec - system system -- /bin/rm -rf /data/per_boot
mkdir /data/per_boot 0710 system system encryption=Require key=per_boot_ref因此 processing action (boot) 与 sys.boot_completed=1 之间可能相隔大量 framework 工作。二者不能用同一个“启动完成”概念描述。
11. 分支路径
11.1 Charger
charger mode 初始不入队 late-init,只执行:
源码文件:system/core/rootdir/init.rc
on charger
class_start chargerhealthd 决定转入完整启动时设置 sys.boot_from_charger_mode=1,对应 property action 停止 charger class 并触发 late-init。
源码文件:system/core/rootdir/init.rc
on property:sys.boot_from_charger_mode=1
class_stop charger
trigger late-init恢复不是重新执行 early-init 和 init,而是从 late-init 文件系统链继续;因此 charger 阶段之前已建立的资源必须可复用。
11.2 Recovery
GetBootMode() 可以返回 recovery,但初始分支只特殊处理 charger,所以 recovery 也入队 late-init。实际 action 来自 recovery ramdisk 安装的 rc,而不是本文引用的 system 根配置;验证 recovery 必须切换到 bootable/recovery/etc/init.rc。
11.3 Microdroid
C++ 队列在 Microdroid 下跳过部分设备检查,实际脚本也来自 minidroid/Microdroid 配置。本文的标准 root init.rc 命令不能外推到 Microdroid;可复用的是事件队列与匹配机制。
12. 失败定位
阶段问题应按层次判断:
| 现象 | 优先检查 | 尚不能证明 |
|---|---|---|
没有 processing action | 事件是否入队、脚本是否已解析、条件是否匹配 | builtin 是否成功 |
| 有 action 无后续阶段 | 当前命令、property waiter、exec service | 目标事件未入队 |
| command failed | 参数、权限、SELinux、文件状态 | 整个阶段已停止 |
| service restarting | Service::Reap() 和 restart deadline | start 从未调用 |
| zygote running 无界面 | system_server、boot animation、AMS/WMS | init 阶段未完成 |
| boot action 已执行 | HAL/core class start 已发起 | sys.boot_completed=1 |
取消/中断路径也不是阶段 rollback。sys.powerctl 会让主循环优先进入 shutdown;正在执行的 builtin 仍需返回。关机处理会修改 action queue,但已产生的文件系统副作用不会自动撤销。
13. 启动测试
13.1 Action顺序
init_test.cpp 的 EventTriggerOrder 构造三个 on boot action,中间一个带 property:ro.hardware=*,三个 builtin 分别断言执行序号为 0、1、2。输入只有一次 QueueEventTrigger("boot")。
它证明同一事件的匹配 action 按解析进入 actions_ 的顺序执行,且满足的组合 property 条件不会另建并行队列;它不证明跨分区的最终文件清单,因为测试只解析一段临时 rc。
13.2 延迟加载
LazilyLoadedActionsCantBeTriggeredByTheSameTrigger 在 on boot 中动态加载一个同样定义 on boot 的文件,断言新 action 不会被当前 boot 事件追溯匹配。LazilyLoadedActionsCanBeTriggeredByTheNextTrigger 则加载 on next,再执行 trigger next,断言新 action 可以被下一事件消费。
两组测试共同证明 action 集合在某个事件匹配时形成边界:当前事件不会回头扫描之后新增的同名 action,但未来事件会看到新增内容。这也是 APEX/late import rc 设计必须明确触发时机的原因。
13.3 设备边界
这些 host 测试使用自定义 builtin map,并循环执行直到 queue 清空;它们不启动 vold、apexd 或 Zygote,也不覆盖设备 fstab。真实启动依赖仍要由固定设备配置和运行日志验证。
14. 现场验证
设备上可同时观察事件、服务与最终 property:
adb shell 'logcat -b all -d -s init:I | grep "processing action"'
adb shell 'getprop | grep "^\[ro.boottime.event\."'
adb shell 'getprop init.svc.zygote'
adb shell 'getprop init.svc.servicemanager'
adb shell 'getprop sys.boot_completed'
adb shell 'getprop ro.crypto.state'若构建未启用 event timestamp,ro.boottime.event.* 为空不代表 action 未执行。应以 processing action (<trigger>) from (<file>:<line>) 找到真实消费者,再回源码检查同名 action 的解析顺序。
复现本文源码关系可按下面顺序搜索:
rg -n "QueueEventTrigger|queue_property_triggers_action" system/core/init/init.cpp
rg -n "^on (early-init|init|late-init|post-fs-data|zygote-start|boot)" \
system/core/rootdir/init.rc
rg -n "do_trigger|queue_fs_event|do_mount_all" system/core/init/builtins.cpp
rg -n "CheckEvent|ExecuteOneCommand|ParseConfigDir" system/core/init面对某个阶段卡住,先记录最后一个 action 的文件和行号,再区分它是普通失败、wait_for_prop、exec_start 还是 service restart。随后检查目标事件是在初始 C++ 队列、late-init trigger、mount_all 返回码还是 property callback 中产生。完成这条反向追踪,才能知道问题属于 rc 顺序、设备配置还是消费者状态。
本文没有展开 rc tokenizer、mount_all 的 fs_mgr 算法、class 内服务排序和 framework 设置 boot completed 的路径。下一篇将沿 init 启动的几个关键 daemon,说明 servicemanager、surfaceflinger、Zygote 与 system_server 如何跨越这些事件建立依赖;本文保留的是阶段事件本身的真实队列与屏障。
