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 等阶段。

init 进程启动流程与动作队列 Linux Kernel 启动 /init init 主进程 解析 rc / 属性 / SELinux 动作队列调度 init.rc 解析器 Action / Service Command 列表 属性系统 property_service 共享内存 + 权限 动作队列(Action Queue)— 串行执行 early-init 早期初始化 init 基础初始化 late-init 触发后续阶段 post-fs-data 数据分区挂载 boot 系统服务启动 服务启动(Service) servicemanager zygote surfaceflinger class main / core / hal 按序启动 SELinux 初始化 加载 policy setcon / restorecon enforcing 策略加载后,服务在受限域中运行 属性读写

动作队列的关键价值在于确定性:无论有多少 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 的排查思路

假设设备开机后长期停留在开机动画,可按以下顺序排查:

  1. 抓取 adb shell getprop sys.boot_completed,若为空,说明 boot 阶段未完成。
  2. 查看 adb logcat -b events | grep boot,确认 boot_progress 事件停在哪一步。
  3. 检查 adb shell dmesg | grep avc,排除 SELinux 拒绝导致服务启动失败。
  4. 核对 init.rc 中目标服务的 class 与触发器,确认其是否被正确 start。
  5. 用 adb shell ps -A | grep zygote 确认关键进程是否存活。

调试技巧:可通过 adb shell setprop ctl.restart zygote 单独重启某个服务,观察其是否因依赖属性或 SELinux 策略而反复崩溃。结合 logcat -b all 与 dmesg 交叉定位,通常能快速区分是 rc 配置错误、属性未就绪,还是策略拦截。

综上,init 进程以 init.rc 为蓝图,以动作队列为执行骨架,以属性系统为跨进程通信总线,以 SELinux 为安全边界,共同完成了 Android 用户空间从零到可用的启动过程。掌握这四条主线,就能在遇到启动异常时迅速定位到具体阶段与具体模块。