init.rc主配置
上一篇内置命令下回答了单条命令何时生效;本文把视野扩大到整份 init.rc,但不做“从头到尾翻译配置”的清单。教材目标是建立一条可以回到源码验证的主线:这份文件如何被安装和选中,early-init 为什么能使用哪些资源,late-init 如何把阶段变成队列,post-fs-data 的等待点如何形成真正屏障,以及 zygote-start、boot 和 sys.boot_completed 为什么不是同一个完成信号。
过去的资料常把 cgroup 挂载、Zygote 启动和设备配置都归到 root init.rc。当前实现已经把 cgroup 准备放进 C++ 初始队列,根配置还依赖设备 rc、vendor rc、fstab、APEX 和属性服务。因此阅读本文时,要把“根文件中出现的命令”“C++ 在文件之前排队的动作”和“其他分区追加的 Action”分开。
1. 文件入口
1.1 安装位置
源码文件:system/core/rootdir/Android.bp
相关函数/类型:prebuilt_etc { name: "init.rc" }
prebuilt_etc {
name: "init.rc",
src: "init.rc",
sub_dir: "init/hw",
required: [
"platform-bootclasspath",
"init.boringssl.zygote64.rc",
"init.boringssl.zygote64_32.rc",
"init-perfetto.rc",
],
}这段构建规则证明了两个边界。第一,源码文件的安装目标是 init/hw 子目录,运行时由 init.cpp 读取 /system/etc/init/hw/init.rc;不能因为源文件位于 rootdir/ 就把它写成运行时 /init.rc。第二,zygote、BoringSSL 自检和 Perfetto 的 rc 文件是构建依赖,主文件的 import 还会根据属性选择硬件和 zygote 变体。
1.2 首部导入
源码文件:system/core/rootdir/init.rc
import /init.environ.rc
import /system/etc/init/hw/init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /system/etc/init/hw/init.usb.configfs.rc
import /system/etc/init/hw/init.${ro.zygote}.rc这些 import 是 Parser 的输入,不是 shell 的 source。${ro.hardware} 和 ${ro.zygote} 在命令执行前由 init 的属性展开规则处理;导入失败和目录导入还有各自的 parser 语义。更重要的是,主文件导入的设备 rc 与 LoadBootScripts() 后续扫描的分区目录不是一回事:前者在配置文本中显式出现,后者由 C++ 决定扫描路径。
1.3 C++选择器
源码文件:system/core/init/init.cpp
相关函数/类型:LoadBootScripts
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
Parser parser = CreateParser(action_manager, service_list);
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/system/etc/init/hw/init.rc");
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
parser.ParseConfig("/system_ext/etc/init");
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
} else {
parser.ParseConfig(bootscript);
}
}ro.boot.init_rc 非空时,默认主文件路径被替换成该属性指定的脚本;为空时才执行标准路径和分区目录扫描。对某些目录的 ParseConfig() 失败会把路径放入 late_import_paths,后续由 mount_all 的 import_late() 在分区可见后再次导入。由此可见,“文件已安装”不等于“其中所有 Action 已经进入 actions_”。
2. 早期准备
2.1 Cgroup先行
相关源码:
system/core/init/init.cppSecondStageMain
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueEventTrigger("early-init");
am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction,
"ConnectEarlyStageSnapuserd");
am.QueueBuiltinAction(wait_for_coldboot_done_action,
"wait_for_coldboot_done");
am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");根配置的注释说 cgroup 会在 early-init 前挂载,对应的 C++ 调用顺序是:SetupCgroupsAction 先排入队列,然后才是 early-init 事件。init.rc 中后续对 /dev/memcg、cpuctl 或服务 class 的操作,依赖的是这个前置动作,而不是由 early-init 自己首次挂载全部 cgroup。
wait_for_coldboot_done_action 也很关键:start ueventd 只发起服务启动,coldboot 完成由独立的等待动作确认。后续需要设备节点的 action 不能只看 start ueventd 的返回值。
2.2 早期事件
源码文件: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
restorecon /adb_keys
restorecon /postinstall
mkdir /dev/memcg/apps/ 0755 system system
mkdir /dev/memcg/system 0550 system system
mkdir /dev/net 0755 root root
symlink ../tun /dev/net/tun
setrlimit nice 40 40
setrlimit nofile 32768 524288
setrlimit memlock 65536 65536这个 Action 不是单纯的“启动服务阶段”。它同时修改内核接口、恢复文件标签、建立供 lmkd/zygote 使用的 cgroup 目录、创建 /dev/net/tun 兼容链接,并设置 init 继承的资源限制。每一行的消费者不同:sysctl 由内核读取,restorecon 由 SELinux file-context 规则消费,目录和 rlimit 则成为后续服务启动的输入。
2.3 APEX屏障
源码文件:system/core/rootdir/init.rc
相关函数/类型:on early-init
mkdir /linkerconfig/bootstrap 0755
mkdir /linkerconfig/default 0755
write /linkerconfig/bootstrap/ld.config.txt \#
write /linkerconfig/default/ld.config.txt \#
chmod 644 /linkerconfig/bootstrap/ld.config.txt
chmod 644 /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_configstart ueventd 是异步服务启动;exec_start init_dev_config 和 exec_start apexd-bootstrap 则是同步执行服务。这里的顺序有源码含义:APEX bootstrap 以 exec_start 运行,确保提供关键库的 APEX 在后续进程启动前已经可见;perform_apex_config 再把配置动作接入 init 的后续状态。不能把这一段概括成“early-init 启动 apexd”,因为同步/异步差异会改变后续 Action 的可见资源。
图中的两条路径不是并行完成承诺:start ueventd 的消费者是服务状态和 coldboot 等待动作;exec_start apexd-bootstrap 在当前 Action 中形成同步屏障。读者排查早期卡顿时,应先按最后一条 processing action 区分是 exec、property waiter 还是普通 builtin。
3. 初始化段
3.1 设备接口
源码文件:system/core/rootdir/init.rc
相关函数/类型:on init
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/socket/ot-daemon 0770 thread_network thread_network
mkdir /dev/cpuctl/foreground
mkdir /dev/cpuctl/background
mkdir /dev/cpuctl/top-app
mkdir /dev/cpuctl/system
mkdir /dev/cpuctl/system-backgroundon init 先建立后续 daemon 能依赖的设备接口和控制组目录,再进入 binderfs、fusectl、存储视图等更大段落。这里的 copy 是把 boot 参数输入写入内核设备文件,不是普通配置文件复制;symlink 为使用标准 fd 路径的程序提供兼容入口。
3.2 Binder与存储
源码文件:system/core/rootdir/init.rc
相关函数/类型:on init
mount configfs none /config nodev noexec nosuid
mkdir /dev/binderfs
mount binder binder /dev/binderfs stats=global
chmod 0755 /dev/binderfs
mount fusectl none /sys/fs/fuse/connections
symlink /dev/binderfs/binder /dev/binder
symlink /dev/binderfs/hwbinder /dev/hwbinder
symlink /dev/binderfs/vndbinder /dev/vndbinder
mkdir /mnt/user 0755 root root
mkdir /mnt/user/0 0755 root root
mkdir /mnt/runtime 0700 root root
mkdir /mnt/runtime/default 0755 root root这些命令把 kernel filesystem、Binder device nodes 和多用户存储视图准备成服务可消费的资源。它们不等价于“Binder 服务已经注册”:servicemanager 进程的启动在同一 on init Action 后段,但 Binder 注册仍由服务自身完成。
3.3 核心服务
源码文件:system/core/rootdir/init.rc
相关函数/类型:on init
start logd
start lmkd
start servicemanager
start hwservicemanager
start vndservicemanager
mkdir /mnt/vm 0755 root system
mount tmpfs tmpfs /mnt/vm nosuid nodev noexec rwstart 的 owner 是 ServiceList,消费者是对应 Service 的 fork/exec 和后续进程状态;它不等待 Binder service registration。这个差异与上一篇 class_start 的结论一致,但本篇把它放回 root 配置的真实顺序:logd 和 lmkd 在其它 daemon 之前启动,是为了让后续进程拥有日志和内存压力消费者。
4. 阶段队列
4.1 late-init配置
源码文件:system/core/rootdir/init.rc
相关函数/类型:on late-init
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 boot这十条语句不是十次同步函数调用。do_trigger() 只把事件放进 ActionManager::event_queue_;当前 late-init Action 结束后,主循环才开始按 FIFO 取出 early-fs。事件名是调度标签,不是目标 Action 已完成的承诺。
4.2 消费边界
源码文件:system/core/init/action_manager.cpp
相关函数/类型:ExecuteOneCommand
while (current_executing_actions_.empty() && !event_queue_.empty()) {
for (const auto& action : actions_) {
if (std::visit([&action](const auto& event) {
return action->CheckEvent(event);
}, event_queue_.front())) {
current_executing_actions_.emplace(action.get());
}
}
event_queue_.pop();
}
if (current_executing_actions_.empty()) return;
auto action = current_executing_actions_.front();
action->ExecuteOneCommand(current_command_);匹配发生时,ActionManager 扫描已经解析的 Action;同一事件可匹配多个 Action,随后每轮只执行当前 Action 的一个 command。若某个 Action 在执行中 import 新 rc,新 Action 不会被已经取出的同一个 event 追溯匹配;后续 event 才可能看见它。这个边界由 init_test.cpp 的 lazy-load 测试直接验证。
4.3 属性触发
源码文件:system/core/init/init.cpp
相关函数/类型:queue_property_triggers_action
static Result<void> queue_property_triggers_action(const BuiltinArguments&) {
ActionManager::GetInstance().QueueBuiltinAction(
property_enable_triggers_action,
"enable_property_trigger");
ActionManager::GetInstance().QueueAllPropertyActions();
return {};
}初始队列还会安排这个 builtin action。它先追加“启用 property trigger”的一次性动作,再追加空的 PropertyChange,用于按当前属性状态检查 property actions。因此 on property:... 并不是从 init 进程启动瞬间就无条件生效;它有一个由 C++ 显式安排的启用与全量检查边界。
5. 文件阶段
5.1 early-fs
源码文件:system/core/rootdir/init.rc
相关函数/类型:on early-fs
on early-fs
start vold根配置只给出 vold 启动;设备或产品 rc 可以添加同名 early-fs Action。start vold 返回只表示启动请求已交给 ServiceList,后面的 fstab、加密和 userdata 结果仍由 mount_all、vold 属性及等待点共同决定。
5.2 post-fs
源码文件: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 --cross-filesystems /sys/kernel/debug
restorecon_recursive /metadata
mkdir /metadata/vold
mkdir /metadata/apex 0700 root systempost-fs 的关键不是“mount 已经结束”这句口号,而是可以观察到它消费早期挂载结果:rootfs remount、storage bind、跨文件系统 restorecon 和 metadata 目录创建都要求对应路径已经出现。exec 还会同步等待 checkpoint 命令;因此一条外部程序失败和一条普通 restorecon 失败的主循环影响不同。
5.3 late-fs
源码文件: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 同时承载文件权限、早期 HAL 启动和 property 条件 Action。两个无条件 on late-fs 段按解析顺序成为不同 Action;属性条件段只在事件消费时检查,不会因为之后属性变化而重新绑定到旧的 late-fs 事件。
5.4 设备扩展
标准根文件没有固定的 on fs 段,设备 rc 才是 fstab 的常见 owner。比如设备配置可以提供:
相关源码:
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这是 goldfish 设备树中的真实配置,不是 system/core/rootdir/init.rc 的内容。分析真实设备时必须同时检查 init.${ro.hardware}.rc、/vendor/etc/init 和 fstab;只读 root init.rc 不能说明设备实际挂载了哪些分区,也不能把 ranchu 的 fstab 路径外推到实体设备。
6. Data屏障
6.1 前段动作
源码文件: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/property 0700 root root encryption=Require
load_persist_props
trigger load_persist_props_action这一段包含三类 owner:checkpoint/vold 相关外部命令、/data 文件系统元数据、property/APEX 后续状态。load_persist_props 负责发起属性加载并建立等待;紧随其后的 trigger load_persist_props_action 是兼容 rc 的事件,不应被误读成“属性加载从这条 trigger 才开始”。
6.2 等待点
源码文件:system/core/rootdir/init.rc
相关函数/类型:on post-fs-data
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 会让 init 主循环暂停普通 Action,直到属性回调匹配;exec_start 则等待 oneshot service 的执行状态。两者都表现为“下一条命令没有立刻执行”,但 owner 不同:前者是 PropWaiterState,后者是 Service/子进程回收。排查卡顿时不能只看最后一条 rc 命令名。
6.3 无回滚
源码文件:system/core/init/action.cpp
相关函数/类型:Action::ExecuteCommand
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 ||
android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
LOG(INFO) << "Command '" << command.BuildCommandString()
<< "' action=" << BuildTriggersString() << " ("
<< filename_ << ":" << command.line() << ") took "
<< duration.count() << "ms and "
<< (result.ok() ? "succeeded"
: "failed: " + result.error().message());
}
}Action::ExecuteCommand() 记录错误和耗时,但不会因为 Result<void> 失败而回滚前面已执行的命令,也不会自动跳过 Action 的下一条 command。只有 builtin 自己实现的清理(例如 loop mount 失败时 detach)或更高层 fatal/recovery 路径,才有局部恢复语义。
图中的循环是等待状态,不是 rc 语法循环。属性变化由 PropertyChanged() 同时送入属性 Action 队列并检查 waiter;满足条件后才唤醒主线程。
7. Zygote启动
7.1 独立事件
源码文件:system/core/rootdir/init.rc
相关函数/类型:on zygote-start
on zygote-start
wait_for_prop odsign.verification.done 1
start statsd
start zygote
start zygote_secondaryAndroid 17 的根配置没有把 Zygote 简化成 on boot 中的 class_start main。它先等待 odsign verification,再直接启动两个 zygote service。start zygote 的完成边界仍只是 Service 启动请求;system_server 的 fork 和 framework ready 属于 Zygote/Java 侧消费者。
7.2 Boot阶段
源码文件:system/core/rootdir/init.rc
相关函数/类型:on boot
on boot
ifup lo
hostname localhost
domainname localdomain
setprop net.tcp_def_init_rwnd 60
class_start hal
class_start core
on boot && property:ro.debuggable=1
write /dev/sys/fs/by-name/userdata/iostat_period_ms 1000
write /dev/sys/fs/by-name/userdata/iostat_enable 1
on boot && property:ro.config.low_ram=true
write /proc/sys/vm/dirty_expire_centisecs 200
write /proc/sys/vm/dirty_background_ratio 5boot 是 root 配置的一个事件,不是“用户已经看到桌面”的标志。它配置网络、内核参数、HAL/core class,并按属性匹配 debug 或低内存分支。sys.boot_completed=1 是更晚由 framework 设置的 property,必须走 property trigger。
7.3 主服务
源码文件:system/core/rootdir/init.rc
相关函数/类型:on nonencrypted
on nonencrypted
class_start main
class_start late_start
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_refnonencrypted 是 mount_all 根据 fs_mgr 返回码排入的历史事件名;file-encrypted 或 metadata-encrypted 且 vold 已准备完成时也会使用它。反过来,sys.boot_completed=1 的消费者是属性 Action,执行 bootchart 清理和 per-boot 目录操作。两个事件都可能出现在“启动后期”,但 owner、输入和证明范围完全不同。
下面的两个 Action 片段分别展示事件触发的 class 启动和 framework 后期的 property 消费,不能把它们拼成一条同步调用链。
8. 分支路径
8.1 充电模式
相关源码:
system/core/init/init.cppSecondStageMainsystem/core/rootdir/init.rcon charger
if (bootmode == BootMode::CHARGER_MODE) {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}C++ 先决定进入哪个事件;rc 再为该事件提供匹配的 Action。下面的 on charger 是事件消费者,sys.boot_from_charger_mode 则是从充电模式回到正常启动时的第二个入口。
on charger
class_start charger
on property:sys.boot_from_charger_mode=1
class_stop charger
trigger late-initcharger 模式初始不进入完整文件系统启动链。healthd 后续通过 property 触发 late-init,恢复路径是从 late-init 继续,而不是重新执行整个 SecondStageMain。这个区别决定了 charger 阶段已创建资源能否被复用,也决定了排障时不能把“没有 early-init 日志”直接判为 init 崩溃。
8.2 GSI边界
GSI 状态在脚本运行前由 C++ 写入属性,mount_all 的 recovery 分支还会拒绝在 GSI 中执行 wipe。标准 root init.rc 的 Action 不能外推为所有 GSI/vendor 行为;真实设备需要同时核对 GSI 属性、设备 fstab 和 vendor rc。
8.3 设备差异
LoadBootScripts() 会根据 ro.boot.init_rc 选择脚本,也会记录尚未可读分区的 late_import_paths。因此缺少某个 on fs、zygote 变体或 vendor service 时,优先检查“哪个脚本被选中、哪个目录在当时可读”,不要先假设 root init.rc 缺少命令。
9. 测试验证
9.1 Action顺序
源码文件:system/core/init/init_test.cpp
相关函数/类型:EventTriggerOrder
测试输入是一段临时 rc:两个无条件 on boot,中间一个带 property:ro.hardware=*;三个替代 builtin 按执行序号断言 0、1、2。它证明同一事件匹配 Action 后保持解析顺序,不证明设备分区最终 import 顺序。
9.2 Import顺序
源码文件:system/core/init/init_test.cpp
相关函数/类型:EventTriggerOrderMultipleFiles
测试构造一个直接 import 文件、一个目录 import 文件、目录内 a.rc 对另一个文件的递归 import 和 b.rc,最终断言六个 command 的执行顺序。它证明目录排序与递归导入会进入同一 Parser/ActionManager 体系;它不覆盖真实 /system_ext、/vendor 的权限和挂载时机。
9.3 延迟导入
相关源码:
system/core/init/init_test.cppLazilyLoadedActionsCantBeTriggeredByTheSameTriggerLazilyLoadedActionsCanBeTriggeredByTheNextTrigger
一个测试在 on boot 中加载新的 on boot,断言当前 boot 不追溯执行;另一个加载 on next 后执行 trigger next,断言新 Action 在下一个事件执行。这正是 mount_all --late、APEX rc 和分区 late import 需要明确触发时机的原因。
9.4 现场检查
在 userdebug/eng 设备上可以只读观察:
adb shell 'logcat -b all -d -s init:I | grep "processing action"'
adb shell getprop init.svc.ueventd
adb shell getprop init.svc.zygote
adb shell getprop init.svc.servicemanager
adb shell getprop ro.crypto.state
adb shell getprop sys.boot_completed
adb shell mountprocessing action 的文件和行号能告诉你实际命中的 Action;init.svc.* 只表示 Service 状态;mount 只观察内核挂载表;sys.boot_completed 只证明 framework 写入了该属性。它们不能单独证明所有 init.rc 命令成功,也不能证明 system_server 业务 ready。
10. 触发追踪
建议用一次“Zygote 没有启动”的问题完成闭环:
- 从
init.cpp::SecondStageMain()判断当前是否走charger或late-init; - 从
LoadBootScripts()判断实际 root script 和 zygote rc 路径; - 在
init.rc追踪late-init → post-fs-data → zygote-start; - 检查
wait_for_prop odsign.verification.done 1的 producer,而不是只重复start zygote; - 查看
init.svc.zygote、processing action和 property 日志; - 对照
init_test.cpp,明确 host 测试覆盖的调用顺序,以及仍需结合设备配置和运行状态判断的部分。
最终要保留三条边界:root init.rc 是配置输入,不是完整设备启动图;event 名称是调度标签,不是消费者完成信号;普通 command error 没有全局 rollback。沿着 owner、consumer 和生效时机阅读,才能把一份很长的 rc 文件变成可验证的源码教材。
