第一次接触安卓系统启动流程时,我被init.rc这个神秘文件深深吸引。它就像乐高积木的说明书,告诉系统如何一步步搭建起完整的运行环境。在安卓11中,init.rc的解析机制变得更加精细,特别是对Action和Service这两种核心section的处理方式。
init.rc文件本质上是一种领域特定语言(DSL),它用特定的语法规则描述系统启动时需要执行的操作。当内核启动第一个用户空间进程init后,这个进程会立即开始解析init.rc文件。有趣的是,这个解析过程不是简单的逐行读取,而是采用了类似编译器的词法分析和语法分析技术。
我曾在调试一个启动问题时发现,init进程对rc文件的解析采用了分层处理策略。首先会解析/system/etc/init/hw/init.rc这个主文件,然后按照特定顺序扫描/system/etc/init、/vendor/etc/init等目录中的附加rc文件。这种设计使得不同层级的开发者可以灵活地扩展启动流程,而不会污染核心配置。
Action section以"on"关键字开头,后面跟着触发条件。解析器遇到这样的结构时,会创建一个ActionParser实例。我通过代码调试发现,ParseSection方法会提取trigger条件,而ParseLineSection则负责收集该Action下的所有命令。
举个例子,当解析到这样的片段时:
bash复制on late-init
exec /system/bin/do_something
setprop service.ready 1
解析器会先记录"late-init"这个触发条件,然后把两条命令分别映射到对应的内置函数。这里有个细节:命令不会立即执行,而是存储在内存中等待触发条件满足。
Service section的解析则完全不同。它以"service"开头,主要定义后台守护进程的启动方式。在调试一个服务启动问题时,我注意到ServiceParser会额外处理很多选项参数,比如:
bash复制service myservice /system/bin/myservice
class main
user system
group system
这些选项会影响进程的启动方式、权限控制等。与Action不同,Service定义在解析完成后就会注册到服务列表,但实际启动时机还是由class等属性决定。
init进程维护着一个先进先出的事件队列(event_queue),这是控制执行时序的核心。当遇到像QueueEventTrigger("late-init")这样的调用时,"late-init"事件会被加入队列。我曾在日志中看到这样的事件流:
每个事件出队时,init会扫描所有Action,找出匹配的trigger执行对应命令。
ActionManager是管理所有Action的中心枢纽,它主要做三件事:
在分析启动卡顿时,我发现如果某个Action执行时间过长,会阻塞后续事件的处理,这正是因为current_executing_actions_采用了串行处理方式。
late-init是系统启动后期的关键阶段,它的触发通常与属性值相关。通过调试我发现,在安卓11中,late-init的触发往往依赖于多个条件:
bash复制on property:sys.boot_completed=1 && property:service.ready=1
trigger late-init
这种多条件触发机制使得系统可以更精确地控制启动流程。
在late-init阶段,系统通常会执行以下类型的任务:
我曾遇到一个案例:某个厂商定制服务需要在所有基础服务就绪后才启动,这正是通过late-init的Action来实现的。
调试init.rc相关问题,有几个关键日志标签:
init: <Action名> processing:显示Action开始执行init: Command '<cmd>' action...:具体命令执行init: Service '<name>' exited:服务异常退出建议在调试时过滤这些标签,可以快速定位问题。
在实践中,我遇到过几种典型问题:
有个特别隐蔽的问题:某次我发现late-init的Action没有执行,最后发现是因为属性触发器中的属性名拼写错误。
虽然init本身是单线程的,但可以通过以下方式优化:
async命令启动后台任务使用bootchart工具可以可视化启动过程。我常用这个工具分析各阶段的耗时,找出瓶颈点。例如,曾发现某个厂商服务在late-init阶段占用了过多时间,通过延迟其启动显著提升了用户体验。
在支持动态分区的设备上,挂载时序特别重要。我处理过这样一个案例:data分区需要在late-init阶段根据实际存储类型(emmc/ufs)动态调整挂载参数。解决方案是:
bash复制on late-init
# 根据硬件类型设置不同挂载选项
if is_emmc:
mount_all /vendor/etc/fstab.emmc
else:
mount_all /vendor/etc/fstab.ufs
这个案例展示了如何利用Action的条件执行特性处理硬件差异。
另一个常见问题是服务启动顺序。例如,某次调试发现网络服务启动失败,是因为它依赖的配置文件服务启动太晚。解决方法是在服务定义中添加:
bash复制service config_service /system/bin/config_service
class early
user root
service network_service /system/bin/network_service
class main
user system
disabled
onrestart restart config_service
通过class和onrestart等选项,可以精细控制服务间的依赖关系。