Skip to content

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)身兼四职:

  1. 第一用户态进程:内核挂载 rootfs 后直接执行 /init,它负责补全挂载表、创建设备节点(ueventd)、准备 SELinux 环境;
  2. rc 脚本解释器:解析 *.rc,按触发器(trigger)编排启动事件;
  3. 服务保姆:service 声明的进程由它 fork+exec,崩溃按 restart/critical 策略拉起;
  4. 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_stopclass 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不自动启动,等显式 startcharger 相关、按需服务
user/group/capabilities/seclabel降权与最小权限几乎所有 vendor 服务
setprop/write/mkdir/symlink文件系统与属性小动作write /sys/... 1 调驱动参数
exec_background/exec_startrc 里跑一次性命令开机打 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:

  1. preload():按清单批量加载类(/system/etc/preloaded-classes 系列)、预读 android.R 常用资源与主题、注册 JNI;
  2. 建 socket:主进程 64 位 socket zygote,若设备有 32 位需求,zygote_secondary 由第二条 service 提供(socket 名带 _secondary);
  3. forkSystemServer() 生 system_server 后,进入 ZygoteServer.runSelectLoop() 终身待命;
  4. 收到请求按命令分流:主命令 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)按请求参数逐项「换装」:

  1. uid/gid/caps:setgid→setuid→降 capabilities(fork 后 root 身份必须马上交还);
  2. 资源限制/oom_adj/nice/rlimit 按参数设置;
  3. SELinux:从 zygote 域切到 untrusted_app/platform_app 等目标域(libselinux setcon 系列调用,封装在 native forkAndSpecialize 里;切不过去直接 fail,这是「安装即崩」的常见根因);
  4. 关闭不该继承的 fd(zygote 持有的 socket、日志句柄按白名单保留);
  5. mount namespace 私有化、seccomp 过滤表应用;
  6. 回 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

基于 Android 16 / API 36 · 内容为原创整理,转载请注明出处