第24章 init进程与rc文件:系统启动的“总导演”
各位同学,今天我们来聊聊Android系统启动的“第一把火”——init进程。说实话,我当年刚接触Android Framework时,觉得init就是个简单的启动程序,直到有一次我在调试一个开机黑屏的问题时,花了整整三天才发现是init.rc里一个服务的启动顺序写错了。从那以后,我再也不敢小看这个“总导演”了。
24.1 init进程的启动流程
init进程是Linux内核启动后创建的第一个用户空间进程,PID固定为1。它的任务说白了就四个字:拉起一切。
内核启动完成后,会调用/init可执行文件。这个文件位于根文件系统,是Android系统启动的起点。init进程的入口函数是main(),位于system/core/init/init.cpp。
核心流程:
- 挂载必要的文件系统(如tmpfs、devpts、proc、sysfs)
- 创建/dev下的设备节点
- 初始化日志系统(logd)
- 解析init.rc脚本
- 启动属性服务(property_service)
- 初始化SELinux
- 进入事件循环,处理子进程信号和属性变化
我个人习惯把init进程比作一个“管家”——它先把房子打扫干净(挂载文件系统),然后拿出家规(init.rc),再安排各个仆人(服务)干活,最后坐在门口等着处理各种突发事件。
24.2 init.rc脚本解析
init.rc是init进程的“剧本”,它告诉init进程什么时候该做什么事。这个文件采用Android特有的Action语言,语法简洁但功能强大。
先看一个典型的init.rc片段:
on early-init
# 设置init进程的优先级
write /proc/1/oom_adj -1000
on init
# 创建必要的目录
mkdir /dev/socket 0775 root root
mkdir /dev/block 0750 root root
# 设置系统属性
setprop ro.build.date.utc 1234567890
on boot
# 启动核心服务
class_start core
class_start main
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart zygote
onrestart restart media
这里有几个关键概念:
| 概念 | 说明 |
|---|---|
| Action(动作) | 由触发条件和命令列表组成,如on boot表示系统启动时执行 |
| Service(服务) | 定义需要启动的可执行程序及其参数、权限 |
| Command(命令) | 具体的操作,如mkdir、chmod、setprop |
| Trigger(触发器) | 触发条件,如early-init、init、boot、property:xxx |
我曾经遇到过一个坑:某个厂商在init.rc里把class_start core写成了class_start main,结果servicemanager没启动,整个系统卡在开机动画。嗯,这种错误排查起来真的很痛苦。
24.3 服务启动机制
init进程通过service关键字定义的服务,会在合适的时机被启动。服务的启动方式有两种:
- 按类启动:通过
class_start <class_name>启动同一类的所有服务 - 按名启动:通过
start <service_name>启动单个服务
服务启动后,init进程会监控它的状态。如果服务异常退出,init会根据配置决定是否重启:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart restart media
onrestart restart netd
注意这里的onrestart关键字——当zygote重启时,会同时重启media和netd服务。为什么?因为zygote是Java世界的“根”,它挂了意味着所有Java服务都得重来。我在项目里就见过因为zygote崩溃导致media服务也挂掉的情况,幸好有onrestart机制自动恢复。
避坑指南:我曾经在定制系统时,把一个服务的critical属性去掉了,结果这个服务反复崩溃重启,系统却没有任何提示。后来才发现,critical属性表示“如果这个服务挂了4次以上,系统就重启进入recovery模式”。对于核心服务,一定要加上critical。
24.4 属性系统(property_service)
属性系统是Android特有的“全局变量”机制。它运行在init进程中,通过socket提供读写接口。你可以把它想象成一个键值对数据库,所有进程都能访问。
属性系统的核心代码在system/core/init/property_service.cpp。它的工作流程是:
- init进程启动时,创建一个socket(
/dev/socket/property_service) - 其他进程通过这个socket发送读写请求
- 属性服务处理请求,更新内存中的属性表
- 如果属性以
ctl.开头,则触发控制命令(如启动/停止服务)
常用的属性操作命令:
# 读取属性
adb shell getprop ro.build.version.sdk
# 设置属性(需要root权限)
adb shell setprop debug.sf.nobootanimation 1
# 监听属性变化
adb shell getprop -p | grep ro.build
你想想看,为什么属性系统要放在init进程里?因为init是第一个进程,它拥有最完整的权限。如果放在其他进程,万一那个进程挂了,属性系统就瘫痪了。
注意:属性值有长度限制——键最长32字节,值最长92字节。我在项目中曾试图存储一个长字符串,结果被截断了,排查了半天才发现是属性系统的限制。
24.5 SELinux初始化
SELinux(Security-Enhanced Linux)是Android安全模型的基石。它采用强制访问控制(MAC),即使进程有root权限,也不能为所欲为。
init进程对SELinux的初始化流程:
- 读取SELinux策略文件(
/sepolicy或/system/etc/selinux/) - 调用
selinux_android_load_policy()加载策略 - 设置安全上下文(security context)
- 将init进程自身设置为
kernel域 - 后续启动的所有服务都会根据策略分配安全上下文
SELinux的策略文件是编译好的二进制文件,由sepolicy工具生成。每个服务在init.rc中可以通过seclabel指定安全上下文:
service surfaceflinger /system/bin/surfaceflinger
class core
user system
group graphics drmrpc
seclabel u:r:surfaceflinger:s0
如果SELinux策略配置错误,服务启动时会被拒绝访问。我记得有一次,一个第三方应用无法访问GPS,我查了三天日志,最后发现是SELinux策略里没给这个应用分配gps权限。用avc日志排查真的很考验耐心。
24.6 知识体系总览
下面这张图展示了init进程与rc文件的整体架构:
从这张图可以看出,init进程是Android系统的“总导演”,它通过解析init.rc脚本来决定启动哪些服务,通过属性系统提供全局配置,通过SELinux确保安全。这四个部分环环相扣,缺一不可。
24.7 实战经验总结
最后,分享几个我在项目中踩过的坑:
- init.rc语法错误:少写一个空格或换行符,init进程会直接报错退出。建议用
init.rc的语法检查工具验证。 - 服务启动顺序:依赖关系一定要理清。比如servicemanager必须在其他服务之前启动,否则Binder通信会失败。
- 属性系统性能:频繁设置属性会导致性能问题,因为每次设置都会触发socket通信和权限检查。
- SELinux策略:在开发阶段可以临时设置为permissive模式(
setenforce 0),但正式发布必须强制enforcing模式。
好了,关于init进程和rc文件的内容就讲到这里。这部分知识是Android Framework的基石,理解了它,你就能明白系统是怎么“活”起来的。