4. Android init进程
当内核完成自身初始化后,会尝试执行 /system/bin/init(在部分设备上为 /init),这就是用户空间的第一号进程。init 承担着 Android 启动链条中承上启下的关键角色:它解析 init.rc 脚本、按触发器启动系统服务、维护全局属性系统、初始化 SELinux 策略,并通过动作队列串行化所有启动动作。理解 init,就理解了 Android 用户空间启动的“总调度室”。
一、init.rc 的解析:从脚本到内存中的动作与服务
init 启动后首先读取 /system/etc/init/hw/init.rc,再按 import 指令递归加载 /system/etc/init、/vendor/etc/init、/odm/etc/init 等目录下的 *.rc 文件。解析器逐行识别三类核心语句:动作(action)、服务(service) 和 命令(command)。
# 典型 init.rc 片段
on early-init
write /proc/1/oom_score_adj -1000
mkdir /dev/socket 0755 root root
service zygote /system/bin/app_process -Xzygote /system/bin --zygote
class main
socket zygote stream 660 root system
onrestart restart audioserver
user root
group root readproc
on boot
setprop sys.boot_completed 0
start servicemanager
解析结果被组织为若干数据结构:每个 on 块对应一个 Action,内部保存触发器名称与命令列表;每个 service 块对应一个 Service,记录可执行路径、参数、socket、用户组、重启策略等。解析阶段并不立即执行命令,而是把动作挂到对应触发器的队列上,等待触发条件满足。
二、动作队列:串行化的启动时序
init 的核心执行模型是“单线程 + 动作队列”。所有动作按触发器排队,由 init 主循环依次取出执行,保证命令不会并发冲突。常见触发器顺序为:early-init → init → late-init,而 late-init 又会触发 early-fs、fs、post-fs、post-fs-data、early-boot、boot 等阶段。
动作队列的关键价值在于确定性:无论有多少 rc 文件、多少服务,init 都按固定顺序执行,避免文件系统尚未挂载就启动依赖数据的服务。例如 post-fs-data 必须在 /data 挂载并解密完成后才触发,而 boot 阶段才启动 zygote 等核心服务。
三、属性系统:跨进程的键值通信
属性系统是 Android 特有的全局键值存储,底层由 property_service 维护一块共享内存,所有进程通过 __system_property_get / __system_property_set 读写。属性名采用“点分命名空间”,如 ro.build.version.sdk、sys.boot_completed、persist.sys.locale。
| 属性前缀 | 含义 | 典型示例 | 权限特点 |
|---|---|---|---|
ro. |
只读属性,启动后不可修改 | ro.build.type |
仅 init 可写一次 |
sys. |
系统运行状态 | sys.boot_completed |
受 SELinux 与 property_contexts 约束 |
persist. |
持久化属性,写入 /data | persist.sys.timezone |
需持久化分区可写 |
ctl. |
控制服务启停 | ctl.start / ctl.stop |
仅特定域可写 |
属性写入并非“谁都能改”。init 通过 property_contexts 为每个属性名分配 SELinux 上下文,再结合 property_service 的权限表决定某进程能否设置。例如普通应用无法写 sys.boot_completed,否则会破坏启动状态判断。
四、SELinux 初始化:从 permissive 到 enforcing
init 在 early-init 阶段就会调用 selinux_initialize(),加载 /system/etc/selinux 与 /vendor/etc/selinux 下的策略文件。若内核命令行带 androidboot.selinux=permissive,则先以宽容模式运行,仅记录拒绝日志;否则直接进入 enforcing 模式。
# 查看当前 SELinux 状态
adb shell getenforce
# Enforcing
# 查看 init 相关拒绝日志
adb shell dmesg | grep avc
# avc: denied { set } for property=sys.boot_completed ...
SELinux 初始化完成后,init 会执行 restorecon 恢复文件上下文,并为后续启动的服务设置域转换。服务在 init.rc 中通过 seclabel 指定运行域,例如 zygote 运行在 zygote 域,surfaceflinger 运行在 surfaceflinger 域。若策略缺失,服务可能启动失败并留下 avc denied 日志。
常见误区:把 SELinux 当成“启动之后才生效”的模块。实际上 init 在解析 rc 之前就已加载策略,后续每个服务 fork/exec 时都会自动完成域转换。若在 post-fs-data 阶段出现权限拒绝,往往不是文件权限问题,而是 SELinux 上下文或策略未覆盖。
五、案例复盘:开机卡在 bootanimation 的排查思路
假设设备开机后长期停留在开机动画,可按以下顺序排查:
- 抓取
adb shell getprop sys.boot_completed,若为空,说明boot阶段未完成。 - 查看
adb logcat -b events | grep boot,确认boot_progress事件停在哪一步。 - 检查
adb shell dmesg | grep avc,排除 SELinux 拒绝导致服务启动失败。 - 核对
init.rc中目标服务的class与触发器,确认其是否被正确start。 - 用
adb shell ps -A | grep zygote确认关键进程是否存活。
调试技巧:可通过 adb shell setprop ctl.restart zygote 单独重启某个服务,观察其是否因依赖属性或 SELinux 策略而反复崩溃。结合 logcat -b all 与 dmesg 交叉定位,通常能快速区分是 rc 配置错误、属性未就绪,还是策略拦截。
综上,init 进程以 init.rc 为蓝图,以动作队列为执行骨架,以属性系统为跨进程通信总线,以 SELinux 为安全边界,共同完成了 Android 用户空间从零到可用的启动过程。掌握这四条主线,就能在遇到启动异常时迅速定位到具体阶段与具体模块。