332 Android init 进程:用户态的第一个进程
系列导航:330-启动总览 | 331-Bootloader | 333-Zygote
上上篇我们讲了 Bootloader 如何加载内核。上篇我们讲了内核如何解压、初始化硬件并跳转执行用户态代码。
现在我们来到 Android 启动链中一个承上启下的关键角色——init。
init 是 Linux 内核启动后第一个运行的用户态进程(PID 1)。在 Android 里,它远比标准 Linux 的 systemd/sysvinit 简单,但做了几件高度定制化的事:
- 解析并执行
init.rc(配置语言) - 管理属性服务(property service)
- 加载 SELinux 安全策略
- 孵化 Zygote
init 是什么
在 Linux 中,PID 1 是所有进程的祖先。内核在完成初始化后,会尝试运行 /sbin/init(或内核命令行指定的 init 程序)。如果找不到,内核会 panic。
Android 的 init 是 system/core/init/ 目录下的一个独立项目,用 C++ 编写,直接被编译成可执行文件:
# Android 设备上的 init
/system/bin/init
# 或者
/sbin/init它的源代码结构:
system/core/init/
├── init.cpp # 主入口,解析命令行、调用 Parser
├── parser.cpp # init.rc 解析器
├── ast.cpp # 抽象语法树(Section/Trigger/Service)
├── builtins.cpp # 内置命令实现(mount、mkdir、write...)
├── property_service.cpp # 属性服务的服务端实现
├── selinux.cpp # SELinux 策略加载
├── devices.cpp # 设备节点管理(ueventd)
├── keychords.cpp # 键盘 chord 检测(解锁界面)
└── init.rc # 主配置文件init.rc 是什么
init.rc 是 init 的配置文件,用一种声明式的领域语言(DSL)描述了启动过程中的所有行为。
Linux 的 init 系统(systemd)用的是单元文件(INI 格式),Android 发明了自己的格式——一种基于 trigger 的配置语言。
init.rc 的基本结构
# 第一部分:导入其他配置文件
import /init.usb.configfs.rc
import /init.${ro.hardware}.rc # 设备特定配置
import /init.trace.rc
import /init.kernel.modules.rc
# 第二部分:on 触发器(事件驱动的服务启动)
on late-init
# 触发时机:late_initcall,内核已经准备好
trigger late-init
on late-init
# 挂载文件系统
mount tmpfs tmpfs /sys/fs/cgroup nosuid nodev noexec
chmod 0775 /sys/fs/cgroup
mkdir /sys/fs/cgroup/freezer 0755 root root
mount cgroup cgroup /sys/fs/cgroup/freezer freezer
# 恢复用户数据(如果需要)
exec_background /system/bin/e2fsck -f -y /dev/block/bootdevice/by-name/userdata
# 设置内核变量
write /proc/sys/kernel/panic 3
write /proc/sys/kernel/panic_on_oops 1
write /proc/sys/kernel/panic_on_warn 1
on boot
# 基础环境设置
# 恢复 SELinux 豁免规则
restorecon /sys/kernel/security
restorecon /sys/kernel/debug
# 启动属性服务
start property_writer
on property:sys.boot_completed=1
# boot_completed 后做最后清理
trigger post-fs-data
trigger early-bootService(服务)定义
Service 是 init 启动的进程。每个 Service 有自己的名字、程序路径、权限:
# 定义一个服务:servicemanager
service servicemanager /system/bin/servicemanager
# 运行在 cgroup 的什么位置
class core animation
# 用户和组
user system
group system readproc
# 关键服务:init 退出它也要退
critical
# SELinux 上下文
seclabel u:object_r:servicemanager_exec:s0
# 超时配置(如果 20 秒内没启动就重启)
timeoutperiod 20注意:init.rc 里的 service 并不是”守护进程”的意思——init 会负责启动这些进程,如果它们挂了,init 可以选择重启它们。
Trigger 机制
init.rc 使用 trigger 机制决定什么时候启动什么:
# 系统启动后触发
on boot
# boot trigger 触发后启动一些基础服务
start ueventd
start healthd
# 某个属性变化后触发
on property:ro.build.product=gpu
# 特定产品变体做一些额外配置
setprop debug.sf.mgpu 1
# late-init 后触发
on late-init
trigger other-late-initinit 的启动流程
内核执行 /sbin/init(PID 1)
│
▼
┌──────────────────────────────────────────────────────────────┐
│ init.cpp main() │
│ 1. 创建 mount namespace(CLONE_NEWNS) │
│ 2. 挂载基础文件系统(/proc, /sys, /dev) │
│ 3. 初始化日志(logcat) │
│ 4. 解析 init.rc │
│ 5. 触发 early-init trigger │
│ 6. 初始化属性服务 │
│ 7. 加载 SELinux 策略(sepolicy) │
│ 8. 触发 late-init trigger │
│ 9. 进入 service 循环(fork/exec/restart) │
└──────────────────────────────────────────────────────────────┘步骤 1-2:Mount Namespace
init 一开始就创建了 mount namespace。这意味着 init 挂载的文件系统对其他进程不可见(除非共享挂载)。
// init.cpp 简化逻辑
namespace pid1_ns {};
clone(CLONE_NEWNS | CLONE_NEWPID, ...);
// 之后的 mount 只影响 pid1_ns
mount("tmpfs", "/dev", "tmpfs", ...);步骤 3-4:解析 init.rc
init 用手写的递归下降解析器解析 init.rc:
init.rc 文本 → Lexer → AST(抽象语法树)→ Parser → Action/Service 对象AST 有三种节点类型:
- Section:顶层块(import、on、service)
- Trigger:on 块里的触发条件(boot、late-init、property:*)
- BuiltinCommand:内置命令(exec、mount、write、chmod)
步骤 5-6:属性服务
init 同时是属性服务的服务端。属性服务是一个共享内存区域,所有进程可以读写系统属性。
// property_service.cpp
// 共享内存 key = "android_prop_service"
// 原子读写,避免竞争条件
// 属性来源(优先级从高到低):
// 1. cmdline(内核传参)
// 2. /data/local.prop(调试用,已被 Google 禁用)
// 3. property 空间(init 设置的默认值)
// 4. init.rc property trigger 触发的步骤 7:SELinux 策略加载
Android 5.0+ 强制开启 SELinux。init 在触发 late-init 之前加载 sepolicy:
// selinux.cpp
SelinuxInitialize("selinux_prop");
// 加载 /system/etc/selinux/plat_sepolicy.cil(or .bin)
// 恢复每个文件的 SELinux 上下文
restorecon("/sys/kernel/security", RESTORECON_RECURSE);步骤 8:Service 循环
init 进入了主循环——一个类似事件循环的结构:
for (;;) {
// 1. 等待下一个属性变化(epoll_wait)
// 2. 处理 property 触发的 action
// 3. 重启挂掉的关键服务(critical)
// 4. 重新触发触发器(trigger)
}init 的三大职责
1. 挂载文件系统
init 在 boot trigger 里挂载了 Android 需要的基础文件系统:
on boot
# /proc(进程信息)
mount proc proc /proc nosuid nodev noexec
# /sys(内核信息)
mount sysfs sysfs /sys nosuid nodev noexec
# /dev(设备节点)
mount tmpfs tmpfs /dev mode=0755
# /dev/pts(pty)
mkdir /dev/pts 0755 root system
# /dev/kmsg(内核日志)
touch /dev/kmsg
chmod 0600 /dev/kmsg
# /sys/fs/selinux(SELinux 文件系统)
mount selinuxfs selinuxfs /sys/fs/selinux2. 属性服务(Property Service)
Android 的系统属性是一个全局 key-value 存储,所有进程共享。例如:
adb shell getprop ro.build.display.id
# PQ3A.190801.002 eng.root.20190801.020758
adb shell getprop sys.boot_completed
# 1
adb shell setprop debug.myapp.feature 1
# 设置自定义属性属性服务通过 socket 通信(不像 Linux 的环境变量,每个进程都fork)。init 维护一块共享内存,每次读写都是原子的(通过 futex 原子锁)。
3. Zygote 启动
Zygote 是 init 启动的最重要的服务。它是所有 APP 进程的父进程。
# init.zygote64_32.rc(64位为主,32位作为次)
service zygote /system/bin/app_process64
class main
socket zygote stream 660 root system
socket usap_pool_min_streaming_native_pool stream 660 root system
onexec /system/bin/linker64 /system/bin/app_process64
priority 1000
priority -20
limit 16384 1000000 1000000 10 1000000
group root readproc reserved_disk
seclabel u:object_r:zygote_exec:s0深入解析 init.rc 配置语言
init.rc 不是标准的配置文件格式,它有自己的语法。
完整语法示例
# 注释
# 导入
import /init.power.rc
# Section: on trigger
on <trigger>
<builtin-command>
# Section: service
service <name> <pathname> [ <argument> ]*
<option>
# 选项(option)
user <username> # 以哪个用户运行
group <groupname> # 以哪个组运行
class <classname> # 属于哪个服务类
capabilities <caps> # Linux capabilities
seclabel <label> # SELinux 上下文
oneshot # 只运行一次,不重启
disabled # 默认不启动,需要 trigger 显式启动
socket <name> <type> <perm> <uid> <gid> # 创建 Unix Domain Socket
critical # 关键服务,退出会导致重启
timeoutperiod <secs> # 超时时间
onrestart <command> # 重启时执行的命令实际例子:servicemanager
service servicemanager /system/bin/servicemanager
user system
group system readproc
critical
seclabel u:object_r:servicemanager_exec:s0
timeoutperiod 20解析器的限制
init.rc 解析器是纯手写的,没有用 lex/yacc 或正则表达式。它只能解析 init.rc 语法,不能做复杂逻辑。这也意味着:init.rc 里不能写 if-else、循环、变量展开。
如果你在 init.rc 里写:
on boot
${myvar} # 不支持变量展开这会报语法错误。init.rc 只有 trigger 和 builtin command,没有变量概念。
属性服务的工作原理
Android 属性服务是一个共享内存环形缓冲区实现的服务:
┌──────────────────────────────────────────────────────────────┐
│ Shared Memory(ashmem) │
│ prop_info[] 数组(每个属性 92 字节) │
│ 通过 fcntl(F_SETLK) 锁保护 │
└──────────────────────────────────────────────────────────────┘
▲▽
┌──────────┴──────────┐
│ init 进程(服务端) │ 监听 property socket
│ /dev/socket/property │ 接收 setprop/getprop 请求
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ 其他进程(客户端) │
│ libcutils API │
└─────────────────────┘关键 API:
// 读取属性
property_get("ro.build.id", value, "default");
// 设置属性(会触发 property:xxx=yyy trigger)
property_set("sys.boot_completed", "1");
// 监听属性变化
property_add_change_listener("sys.boot.*", callback);SELinux 在 init 中的角色
SELinux 对 init 进程特别严格。init 本身运行在 u:object_r:init:s0 上下文,所有它启动的服务必须有对应的 type 定义。
# 查看当前进程的 SELinux 上下文
adb shell id -Z
# u:r:shell:so0 permissive
# 查看 init 进程的上下文
adb shell cat /proc/1/status | grep selinux
# 从 dmesg 可以看到 SELinux 日志
dmesg | grep SELinuxinit.rc 里的 seclabel 选项覆盖默认的 SELinux 类型:
service surfaceflinger /system/bin/surfaceflinger
class core animation
seclabel u:object_r:surfaceflinger_exec:s0如果 sepolicy 里没有定义这个 type,init 会在启动时拒绝执行。
init 进程的 Ueventd
init 还负责管理 /dev 目录下的设备节点。这是通过 ueventd 实现的——它监听 netlink socket,接收内核发出的 uevent(设备插拔、权限变更),自动创建或修改设备节点。
内核 uevent(通过 netlink)
↓
ueventd 接收
↓
根据 ueventd.rc 配置创建设备节点
↓
设置权限(Owner/Group/Mode)和标准 Linux init 的对比
| 维度 | 标准 Linux (systemd) | Android init |
|---|---|---|
| 配置文件 | Unit 文件(INI 格式) | init.rc (DSL) |
| 服务管理 | Unit + dependency | Trigger + service |
| 属性管理 | Env variables | Property service |
| 日志 | journald | logcat (ring buffer) |
| 安全策略 | AppArmor / SELinux | SELinux (强制) |
| Socket 激活 | Yes (socket unit) | No |
| 定时器 | timer unit | No |
核心区别:Android init 没有依赖解析能力,所有启动顺序靠 trigger 的嵌套顺序控制。这比 systemd 的依赖图简单,但灵活性差很多。
动手环节
1. 查看 init.rc
# 在设备上查看
adb shell cat /init.rc
# 或者查看设备特定的
adb shell cat /init.$(getprop ro.hardware).rc
# 查看 zygote 配置
adb shell cat /init.zygote64_32.rc2. 分析 init 日志
# 查看 init 进程的 logcat 输出
adb logcat -b events | grep init
adb logcat -b system | grep "sys.boot"
# 查看属性变化
adb shell dumpsys property
# 查看服务状态
adb shell service list3. 测试属性触发
# 设置一个属性,触发 init.rc 里的 trigger
adb shell setprop debug.init.test 1
# 然后查看 init.rc 是否有 on property:debug.init.test=1 的处理4. Fork 一个 wandos init
Wandos 的 init 实现比 Android 简单得多——它只是循环执行 builtin 命令。尝试 fork wandos,添加一个简化的 property service。
Wandos 对比
| 维度 | Android init | Wandos init |
|---|---|---|
| 配置语言 | init.rc (DSL) | JSON |
| 进程管理 | fork/exec/restart | simple spawn |
| 属性服务 | property service (socket) | global map |
| 安全 | SELinux | 无 |
| 日志 | logcat | stdout |
Wandos init 的目标是让学习者理解 init 的核心逻辑(解析配置→fork→exec→监控),而不是完整的 Android init 复杂性。
系列导航
- 330 Android 启动总览:整体链路、时间分布、boot_completed 机制
- 331 Android Bootloader:Boot ROM → Bootloader → Kernel
- 333 Android Zygote:Zygote 预加载、fork 模型、APP 进程诞生
附录:常见 init 错误排查
| 症状 | 原因 | 解决 |
|---|---|---|
init 卡在 Waiting for /dev/.coldboot |
ueventd 没有正确发出事件 | 检查内核 uevent 配置 |
SELinux policy deny 导致服务启动失败 |
seclabel 写错或 sepolicy 缺失 | 添加 type 或用 permissive 测试 |
属性 sys.boot_completed 永远是空 |
init.rc 没有执行到 late-init | 检查 logcat init 日志 |
| 服务启动后立即退出 | 程序路径错误或缺少 so | 用 adb shell run-as 调试 |
| property_set 报错 | 只读属性不能被覆盖 | 只能用 ro.* 属性(只读) |
作者:公众号「解码日记」,专注于操作系统、虚拟机、内核级技术深度解析。每周三更新。
评论