Appearance
02 init 进程与 Zygote:进程从哪里来
本页导读:整机所有用户态进程都是 init 的子孙,而所有能跑 Java 的进程都出自 zygote 的 fork。本页前半拆 init:rc 语言、property 服务、socket 激活;后半拆 zygote:预加载、fork COW、特化(specialize)与 App 进程孵化链。它是第 01 页时间线的「显微镜模式」,也是理解第 03、06 页的前置地基。基线 Android 16(API 36)。
一、init 是什么:PID 1 兼服务总管
init(system/core/init)身兼四职:
- 第一用户态进程:内核挂载 rootfs 后直接执行
/init,它负责补全挂载表、创建设备节点(ueventd)、准备 SELinux 环境; - rc 脚本解释器:解析
*.rc,按触发器(trigger)编排启动事件; - 服务保姆:
service声明的进程由它 fork+exec,崩溃按restart/critical策略拉起; - property 服务:
/dev/socket/property_service的属主,系统属性的唯一写入点。
启动分两阶段(Android 10+,原因:动态分区):first-stage 只处理挂载与切根;second-stage 才读完整 rc 链、拉常规服务。
二、rc 文件从哪里来、长什么样
加载链(second-stage,路径以 AOSP 设备配置为准):
text
/init.rc # rootdir,主触发链与早期动作(system/core/rootdir)
/init.${ro.boot.hardware}.rc # 板级分岔,同名可覆写前值
import /system/etc/init/*.rc # 平台服务自带(zygote.rc、apexd.rc…)
import /system_ext/etc/init/*.rc
import /vendor/etc/init/*.rc # Treble 后厂商服务入口
import /odm/etc/init/*.rc # ODM 补充
import /product/etc/init/*.rc一个真实感十足的例子(/system/etc/init/zygote.rc,主干构建期由 rootdir 模板安装,参数随版本变动,以 AOSP 对应版本为准):
text
service zygote /system/bin/app_process64 -Xzygote /system/bin --start-system-server
class zygote
socket zygote stream 660 root system
# 32 位设备另有一条 zygote_secondary,socket 名带 _secondary三、rc 语言速查表
| 语法 | 含义 | 常见用例 |
|---|---|---|
on <trigger> / trigger <name> | 定义/触发事件块 | on property:sys.boot_completed=1 |
service <name> <path> [args] | 声明一个被管进程 | surfaceflinger、audioserver |
class <c> | 归组,供 class_start/class_stop | class hal、class core、class main |
socket <n> <type> <perm> <uid> <gid> | socket 激活,fd 随 exec 传入 | zygote、tombstoned |
oneshot | 跑完不常驻、不自动重启 | exec system_ready、apexd-bootstrap |
critical | 连续崩溃触发重启/回滚 | surfaceflinger、zygote |
disabled | 不自动启动,等显式 start | charger 相关、按需服务 |
user/group/capabilities/seclabel | 降权与最小权限 | 几乎所有 vendor 服务 |
setprop/write/mkdir/symlink | 文件系统与属性小动作 | write /sys/... 1 调驱动参数 |
exec_background/exec_start | rc 里跑一次性命令 | 开机打 boottime 事件 |
restart_period/restart_interval | 与 critical 配合的重启窗口 | 平台稳定性策略 |
触发顺序主线:early-init → init → late-init → fs → post-fs → post-fs-data → boot-complete,中间穿插 property: 条件触发。
两个容易忽略的行为细节:
- init 同时是全体子孙的收割者(reaper):
SIGCHLD语义由 init 主循环wait接管,孤儿进程一律过继给 PID 1;服务子进程退出后按restart_period窗口计数,连炸两次且带critical可触发整机重启; - rc 冲突时后加载者胜:同一
on触发器下的 action 会合并,同名setprop/mkdir等命令按加载顺序执行,所以 vendor rc 可以微调平台默认值——这也是排查「某属性莫名其妙变了」时第一个要grep全盘 rc 的原因。
四、property 服务:全机共享的状态总线
- 存储区
/dev/__properties__是一段**只读映射 + 原子序号(serial)**的共享内存,所有进程 mmap 同一份,读属性零拷贝、无锁、无 IPC; - 写属性走 socket:客户端
__system_property_set→ init 属性服务 → 校验 SELinux property context(property_contexts按前缀授权)→ 更新 serial → 唤醒等待该属性的 trigger; - 命名空间约定即权限边界:
ro.*只读、persist.*落盘持久化、ctl.*专管服务启停、debug.*临时调试; - 与 rc 的联动是 init 编排的灵魂:
on property:vold.decrypt=1这类触发把「状态变化」变成「流程推进」; - 服务的启停本身就是属性:
ctl.start/ctl.stop/ctl.restart写入 property_service,init 消费后执行 start/stop ——所以任何能写该属性的进程都能操纵服务,property_contexts对ctl.前缀的授权格外严格; - 监听属性变化有两条路:Java 侧
SystemProperties.addChangeCallback(注册式)与 rc 的on property:触发;轮询 getprop 脚本是无奈之举,能不用就不用。
c
// system/core/init 属性服务主循环(示意,非逐行)
void PropertyService::Start() {
// 创建 /dev/socket/property_service,accept 后读 header
// 权限检查:selinux_check_property_contexts
// 写入:PropertySet(name, value) → serial++ → futex 唤醒
}五、socket 激活:先开门,再叫人
service 里写 socket zygote stream 660 root system 时,init 先 socket()+bind()(抽象命名空间,路径 /dev/socket/zygote),fork 子进程时把 fd 以 ANDROID_SOCKET_zygote=<fd> 环境变量传入,服务直接 fdopen 使用。收益:进程还没起来,门已经开着——第 06 页里 ATMS 向 zygote 发孵化请求时,从不会被「门还没开」卡住。tombstoned、statsd 等也是同一套路。
服务端接收侧的典型写法(以 Java 侧 zygote 为例):
java
// frameworks/base/core/java/com/android/internal/os/ZygoteServer 构造链路(示意)
// init 传入 ANDROID_SOCKET_zygote,Zygote 用它初始化本地 ServerSocket
int fd = Integer.getInteger("ANDROID_SOCKET_zygote");
LocalServerSocket server = new LocalServerSocket(fd); // 包裹已绑定的监听 fd验证 socket 是否就位:ls -Z /dev/socket/(SELinux 上下文与权限一起看),或 ss -xlp | grep zygote(表格内书写时竖线需转义)。
六、zygote 启动:app_process 的自我修养
init 通过 class_start zygote 拉起 /system/bin/app_process64(入口 frameworks/base/cmds/app_process/app_process.cpp),带 -Xzygote 后加载主类 com.android.internal.os.ZygoteInit:
preload():按清单批量加载类(/system/etc/preloaded-classes系列)、预读android.R常用资源与主题、注册 JNI;- 建 socket:主进程 64 位 socket
zygote,若设备有 32 位需求,zygote_secondary由第二条 service 提供(socket 名带_secondary); forkSystemServer()生 system_server 后,进入ZygoteServer.runSelectLoop()终身待命;- 收到请求按命令分流:主命令
fork(孵化,参数用ZygoteCommand结构封装)、exit等;参数解析在ZygoteConnection(core/java/com/android/internal/os/ZygoteConnection.java)。
七、预加载与 fork COW:省的是乘法账
zygote 启动时一次性付出 ~6000+ 类加载、常用资源解码的学费;之后每次 fork 只是复制页表——写时才复制(Copy-On-Write)。fork 一个新进程通常只要几毫秒到几十毫秒,对比冷启动 ART 从零解释执行要几百毫秒起步,这笔乘法账是 zygote 存在的唯一理由。
java
// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java(示意)
private static void preload(...) {
preloadResources(); // 预载 android.R 主题/颜色/Drawable
preloadClasses(); // 按 preloaded-classes 清单 Class.forName 预热
// 部分版本还有资源缓存/预热钩子,以 AOSP 为准
}代价也要讲清楚:预加载类与常驻资源让 zygote 基线占用几十 MB PSS 起步;fork 瞬间父子共享所有页,任何进程写堆才私有化——所以「zygote 预加载的类即使全 App 不用也一直占着物理内存」这句批评也是对的,取舍而已。
两个工程上的细化(以 AOSP 当前版本为准):
- 预加载清单按「脏页友好」拆成多份(只读页优先、易脏页后置),目标是 fork 后父子共享更久;
- 部分版本还提供 text-init 预热(提前触发
<clinit>的只读初始化),把易脏的静态状态延后到子进程里发生。
八、特化(specialize):fork 之后、跑业务之前
fork 出的孩子还穿着 zygote 的衣服,Zygote.forkAndSpecialize(native 在 frameworks/base/core/jni/com/android/internal/os/com_android_internal_os_Zygote.cpp)按请求参数逐项「换装」:
- uid/gid/caps:
setgid→setuid→降 capabilities(fork 后 root 身份必须马上交还); - 资源限制/oom_adj/nice/rlimit 按参数设置;
- SELinux:从
zygote域切到untrusted_app/platform_app等目标域(libselinux setcon 系列调用,封装在 native forkAndSpecialize 里;切不过去直接 fail,这是「安装即崩」的常见根因); - 关闭不该继承的 fd(zygote 持有的 socket、日志句柄按白名单保留);
- mount namespace 私有化、
seccomp过滤表应用; - 回 Java 世界:
ZygoteInit.runChild()→ 反射调用目标 main(普通 App 即ActivityThread.main())。
九、App 进程孵化链:从 want 到 main
text
Activity 启动/Service 绑定/广播投递(第 06 页)
→ ProcessList.startProcessLocked # services/core/.../am/ProcessList.java
→ Process.start # core/java/android/os/Process.java 内部 start
→ ZygoteProcess.startProcess # core/java/com/android/internal/os/ZygoteProcess.java
→ 经 /dev/socket/zygote 写入命令行 # 具体封装在 ZygoteSendArgs/ZygoteProcess
→ zygote: ZygoteConnection.handleStartChild
→ Zygote.forkAndSpecialize # 上面的 specialize
→ runChild → ActivityThread.main() # 新进程主线程自此开始注意两个细节:命令行里传的是 socket fd 与参数清单,不是大对象;以及 AMS 会等子进程 attachApplication 回来才认为进程「存在」,孵化与 attach 是两个时刻。
十、init/zygote 排障手册
| 症状 | 定位思路 |
|---|---|
| 某服务反复重启 | getprop 过滤 ctl.、dmesg 过滤服务名、logcat -b events 查 service 启停事件 |
| 服务起不来且 SELinux 拒绝 | dmesg 过滤 avc:域/类型没授权,补 *.te 或找 sepolicy owner |
| zygote 崩了 | init 会重启 zygote(critical),整机 App 全灭属预期;看 logcat crash 与 tombstone |
| App 秒崩无堆栈 | 多为 specialize 阶段 SELinux/uid 校验失败,串口与 logcat 有 Zygote: ... exit 线索 |
| 属性读不到 | getprop <name> 空 → 检查 property_contexts 与写方进程域权限 |
| fork 太慢 | 预加载清单膨胀、dex 热类异常;systrace 看 ZygoteInit: preloading classes 时长 |
十一、版本注记
- Android 5(API 21):zygote 预加载清单机制随 ART 登场;64 位后期出现
zygote_secondary双实例。 - Android 7(API 24):引入 USAP(unallocated app process,预热池)降低首启延迟。
- Android 10(API 29):first-stage/second-stage init 分家;app zygote(应用自建子 zygote,如 WebView/媒体进程)正式 API 化。
- Android 11(API 30):property 存储区重构(新 format 2),
/dev/__properties__布局升级。 - Android 12(API 31):USAP 池因收益与内存成本不匹配被移除;zygote socket 命令协议精简。
- Android 13+(API 33~36):
ZygoteCommand参数结构化持续演进;HIDL 时代遗留服务在 rc 中继续退场。
本页小结
本页小结:init 用「trigger 状态机 + property 总线 + socket 先行」把一堆二进制编成一支交响乐队;zygote 用「预加载 + COW fork + 特化换装」把进程冷启动变成毫秒级流水线。排障口诀:init 的问题问
dmesg与 avc,zygote 的问题问 logcat 的Zygote:前缀与 tombstone——因为死在 specialize 的进程来不及留下 Java 堆栈。
参考
- init 官方 README:cs.android.com/android/platform/system/core/+/main/init/README.md
- ZygoteInit 源码:cs.android.com/android/platform/frameworks/base/+/main/core/java/com/android/internal/os/ZygoteInit.java
- ZygoteProcess 源码:cs.android.com/android/platform/frameworks/base/+/main/core/java/com/android/internal/os/ZygoteProcess.java
- 启动流程文档:source.android.com/docs/core/architecture