system\core\init\init.cpp----LoadBootScripts()解析init.rc(1)
关于 Android 中 init.rc 文件的位置
这是一个非常重要的问题。如果
/system/etc/init/hw/init.rc
目录或文件不存在,Android 系统会按照特定的优先级和回退机制在其他位置查找 init.rc 文件。标准查找顺序
根据 Android 系统设计,init 进程会按以下顺序查找 init.rc 文件:
首选位置:
/system/etc/init/hw/init.rc
这是现代 Android 版本(约 Android 7.0+)的标准位置
对应 AOSP 源码中的
system/core/rootdir/init.rc
传统位置:
/init.rc
(根目录下的 init.rc)
这是更早期 Android 版本使用的传统位置
在某些定制 ROM 或嵌入式设备中可能仍然使用
好的,这是一段来自 Android 系统初始化进程 init
的代码,主要负责在系统启动时加载和解析各种初始化脚本(.rc
文件)。下面我将用中文为你逐部分详细解释。
函数概述
c++
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list)
函数名:
LoadBootScripts
(加载启动脚本)作用: 这是 Android 启动过程中非常关键的一步,它负责读取系统各个分区中的
.rc
配置文件,并将这些文件中定义的动作 (Actions) 和服务 (Services) 注册到管理器中,后续init
进程会根据这些配置来执行命令、启动守护进程和服务。参数:
action_manager
: 动作管理器的引用,解析到的动作(例如:on boot
,on early-init
)会添加到这里。service_list
: 服务列表的引用,解析到的服务(例如:service zygote /system/bin/app_process ...
)会添加到这里。
代码逐行详解
1. 创建解析器 (Parser)
c++
Parser parser = CreateParser(action_manager, service_list);
这行代码调用
CreateParser
函数创建了一个配置解析器 (parser
)。这个解析器内部预先定义好了如何解析
init.rc
语言的语法(如on
,service
,import
,trigger
等关键字),并将解析结果直接填充到传入的action_manager
和service_list
中。
2. 检查自定义启动脚本属性
c++
std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) {// 正常启动流程... } else {parser.ParseConfig(bootscript); }
ro.boot.init_rc
是一个 Android 系统属性,通常由 bootloader 在启动内核时通过命令行 (cmdline
) 传入。这段代码的逻辑是:
如果该属性为空:走正常的、默认的启动脚本加载流程(即后面的
if
块)。如果该属性不为空:意味着提供了一个自定义的启动脚本路径,系统将只解析这个指定的脚本,而跳过所有默认的配置。这在调试或特殊启动模式下非常有用。
3. 默认启动流程 (if 块内的内容)
当没有自定义脚本时,执行默认加载顺序。
c++
parser.ParseConfig("/system/etc/init/hw/init.rc");
这是最核心、最基础的启动脚本。它定义了整个 Android 系统启动最根本的步骤和服务,例如
zygote
(Android 应用进程的孵化器)、servicemanager
(Binder 通信的核心服务)等通常都在这里定义。
c++
if (!parser.ParseConfig("/system/etc/init")) {late_import_paths.emplace_back("/system/etc/init"); }
这行代码尝试解析
/system/etc/init/
目录下的所有.rc
文件。if (!parser.ParseConfig(...))
的逻辑:如果解析成功(目录存在且有文件),函数返回
true
,!true
为false
,条件不成立。如果解析失败(例如目录不存在或没有文件),函数返回
false
,!false
为true
,条件成立。
条件成立后会执行:
late_import_paths.emplace_back("/system/etc/init");
这意味着将
/system/etc/init
这个路径加入到一个叫late_import_paths
(延迟导入路径)的列表中。为什么需要延迟导入? 这是因为在 Android 启动的早期阶段,
/system
分区可能还没有被挂载为可写(rw),导致无法读取其中的配置。系统会先继续执行其他操作,等到合适的时机(例如fs
触发器)再回过头来尝试加载这些之前失败路径下的配置。
接下来的几行代码逻辑完全相同,只是针对不同的分区:
c++
parser.ParseConfig("/system_ext/etc/init");
解析 System Extension 分区的配置。OEM/厂商可以在这里放置一些扩展功能配置,这些配置介于 AOSP 通用系统和自家定制功能之间。
c++
if (!parser.ParseConfig("/product/etc/init")) {late_import_paths.emplace_back("/product/etc/init"); }
解析 Product 分区的配置。用于配置与特定产品型号相关的功能。
c++
if (!parser.ParseConfig("/odm/etc/init")) {late_import_paths.emplace_back("/odm/etc/init"); }
解析 ODM 分区的配置。ODM 是为不同 OEM 提供硬件适配的厂商,这里存放与特定硬件板级相关的配置。
c++
if (!parser.ParseConfig("/vendor/etc/init")) {late_import_paths.emplace_back("/vendor/etc/init"); }
解析 Vendor 分区的配置。这是 OEM/厂商放置其私有定制、硬件相关服务和功能的主要地方。
4. 关于延迟导入 (late_import) 的注释
c++
// late_import is available only in Q and earlier release. As we don't // have system_ext in those versions, skip late_import for system_ext.
这段注释说明:
late_import
这个机制只在 Android Q (10) 及更早的版本中有效。因为在 Android Q 时还没有
system_ext
分区,所以代码里对system_ext
的解析 (parser.ParseConfig("/system_ext/etc/init");
) 没有使用延迟导入逻辑,直接解析,能成则成,不成就算了。
总结
这段代码清晰地展示了 Android 启动脚本的加载策略和优先级:
最高优先级: 由
ro.boot.init_rc
属性指定的自定义脚本(用于调试和特殊模式)。默认优先级(从高到低):
核心:
/system/etc/init/hw/init.rc
(必选)系统:
/system/etc/init/
(可延迟)系统扩展:
/system_ext/etc/init/
(直接解析,不延迟)产品:
/product/etc/init/
(可延迟)硬件适配:
/odm/etc/init/
(可延迟)厂商:
/vendor/etc/init/
(可延迟)
这种分层结构使得 AOSP、芯片厂商(ODM)、设备制造商(OEM)都可以在不修改核心文件 init.rc
的前提下,在自己的分区添加和修改启动行为,实现了高度的模块化和可定制性。延迟导入机制则保证了即使某些分区尚未准备好,也不会阻塞启动流程。