Android 系统启动机制(五):system_server 是 init 启动的,还是 Zygote fork 出来的?

关于 system_server 的来源,常见两句话看起来互相冲突:

csharp 复制代码
Android 的服务都是 init 启动的;
system_server 是 Zygote fork 出来的。

冲突来自"启动"这个词太宽。它可能表示"位于整棵进程树的上游",也可能表示"直接调用 fork 创建子进程"。

先给出判定:

init 通过 fork/exec app_process64 建立主 Zygote;主 Zygote 在自己的初始化流程中直接调用 forkSystemServer() 创建 system_server。init 是上游祖先进程与 Zygote 的监督者,Zygote 才是 system_server 的直接创建者。system_server 也不是通过普通 App 使用的 Zygote socket 请求创建的。

准确进程树是:

csharp 复制代码
init,PID 1
  └─ app_process64 / zygote64
       └─ system_server

本文只追 system_server 的出生路径;进入 SystemServer.run() 后如何装配服务,留到第七篇。

源码基线: Android 16 QPR2 android-16.0.0_r4frameworks/base commit 45034f0663f960d9ee5fb0a101a4732b71f6e2f4

一、先用三个问题拆掉"谁启动谁"

问题 Android 16 的答案
谁通过 init Service 配置启动 Zygote? init
谁直接执行 fork() 得到 system_server PID? 主 Zygote
谁在 system_server 死亡后参与整组重启? Zygote 检测死亡并退出,init 再按 Service 策略重启 Zygote

所以两句话可以改写为:

csharp 复制代码
init 建立并监督"Zygote → system_server"这组关键启动单元;
Zygote 直接 fork 出 system_server。

这样就不再把"上游负责"与"直接父进程"混为一谈。

二、ZygoteInit.main() 中的顺序没有经过 socket

主 Zygote 在 ZygoteInit.main() 完成参数解析和预加载后,根据 rc 传入的 --start-system-server 走下面的分支:

ini 复制代码
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
if (r != null) {
    r.run();
    return;
}
​
caller = zygoteServer.runSelectLoop(abiList);

这段顺序揭示了两个事实:

markdown 复制代码
1. forkSystemServer() 发生在 Zygote 进入普通请求 select loop 之前;
2. 调用来自 Zygote 自己的初始化控制流,不需要 system_server 先连 socket。

这也符合因果关系:如果"system_server 通过 socket 请求创建自己",它必须在出生前先运行客户端代码,形成循环前提。

三、forkSystemServer() 给这个子进程准备了什么身份?

ZygoteInit.forkSystemServer() 组装一组专用参数。Android 16 固定基线中的核心信息包括:

ini 复制代码
--setuid=1000
--setgid=1000
--setgroups=...一组系统 supplementary groups
--capabilities=...
--nice-name=system_server
com.android.server.SystemServer

这些参数分别解决:

参数 作用
UID/GID 1000 让子进程成为 Android system 身份,而不是继续作为 root Zygote
supplementary groups 获得 system_server 所需的特定设备/系统组访问能力
capabilities 在放弃 root UID 后保留经过计算的必要 Linux capability
nice-name 设置可见进程名 system_server
com.android.server.SystemServer 指定最终 Java 入口类

这里也要避免反向夸大:system_server 使用 system UID 并持有一组能力,不代表它"绕过全部权限系统"。SELinux、Binder 身份检查、Linux DAC、capability、进程沙箱和服务自身权限仍共同约束它。

四、真正的 fork 在 Native 层

Java 层调用链可以压缩为:

scss 复制代码
ZygoteInit.forkSystemServer()
  → Zygote.forkSystemServer()
  → nativeForkSystemServer()
  → com_android_internal_os_Zygote_nativeForkSystemServer()
  → zygote::ForkCommon(..., is_system_server=true)
  → fork()

Android 16 的 Native 入口在 com_android_internal_os_Zygote.cpp。它先准备 system_server 不应继承的 USAP / system server socket FD,再调用 ForkCommon()

fork 的返回值让同一段调用栈分成父子两条路:

ini 复制代码
pid > 0:当前仍是 Zygote 父进程,pid 是 system_server 的 PID;
pid == 0:当前已经是 system_server 子进程;
pid < 0:fork 失败。

"fork 返回两次"不是两个线程拿到不同结果,而是父、子两个进程从同一个代码位置继续执行。

五、子分支:模板副本怎样进入 SystemServer.main()?

1. Native specialize

pid == 0 分支,nativeForkSystemServer() 调用 SpecializeCommon(),应用 UID/GID、group、capability、system seccomp 与运行时子进程钩子。

它把刚 fork 出来、仍像 Zygote 的模板副本改造成 system_server 身份。

2. Java child 分支关闭 Zygote server socket

回到 ZygoteInit.forkSystemServer()pid == 0 的子进程进入 handleSystemServerProcess()。它会关闭当前子进程不应继续持有的 Zygote server socket。

这一步非常关键:system_server 不应冒充另一个 Zygote 接收 App 创建请求。

3. RuntimeInit 找到 SystemServer.main()

handleSystemServerProcess() 最终走向:

scss 复制代码
ZygoteInit.zygoteInit()
  → RuntimeInit.commonInit()
  → ZygoteInit.nativeZygoteInit()
  → RuntimeInit.applicationInit()
  → RuntimeInit.findStaticMain("com.android.server.SystemServer")
  → 返回 Runnable(当前实现为 MethodAndArgsCaller)

findStaticMain() 的 API 返回类型是 Runnable;Android 16 当前返回的具体实现对象是 MethodAndArgsCallerZygoteInit.main() 的 child 路径拿到这个 Runnable 后调用 r.run(),最终执行:

arduino 复制代码
SystemServer.main(String[] args)

上面描述的是正常的无 wrapper 路径;调试配置若设置 invoke-withhandleSystemServerProcess() 还可以转入 WrapperInit.execApplication()。这类可选分支不改变"主 Zygote 直接 fork system_server"的进程出生结论,但会改变进入最终 Java 类之前的执行细节。

在上述正常路径中,没有再 exec 一个 system_server ELF。system_server 仍然运行在从 app_process/Zygote 继承来的 ART 进程映像里,只是权限、名字、运行时状态与 Java 入口已经改变。

六、Binder 线程池是在 SystemServer.main() 里才启动的吗?

不是。

child 进入通用初始化路径时,Binder 线程池的完整事实链是:

scss 复制代码
Zygote child
  → RuntimeInit.zygoteInit()
  → ZygoteInit.nativeZygoteInit()
  → AppRuntime::onZygoteInit()
  → ProcessState::self()->startThreadPool()

其中 Native 回调最终落到 AppRuntime::onZygoteInit()

css 复制代码
ProcessState::self()->startThreadPool();

所以,Binder 线程池的启动位于 fork 后通用 child 初始化路径,早于 SystemServer.main()。但这不表示此时 Framework 服务已经注册:

markdown 复制代码
Binder 驱动与线程池可用
        ≠
ActivityManagerService / WindowManagerService 已创建并发布

传输基础设施准备好,与具体服务对象进入 ServiceManager 是两个里程碑。

七、父分支:Zygote 没有变成 system_server

在父进程中,forkSystemServer() 返回 null;主 Zygote 随后进入 ZygoteServer.runSelectLoop(),继续承担普通 App 进程孵化职责。

因此,下面这两句话都错:

scss 复制代码
Zygote 调用 SystemServer.main() 后自己变成了 system_server;
system_server 创建以后 Zygote 的任务就结束了。

实际状态是两个独立进程:

perl 复制代码
zygote64
  ├─ 继续监听 socket、fork App / USAP
  └─ 作为 system_server 的父进程监视其死亡

system_server
  └─ 执行 SystemServer.main()、创建 Framework 服务

它们最初复用一部分预加载物理页,但拥有独立 PID、地址空间演化、线程集合与职责。

八、system_server 死亡为什么会连带 Zygote 重启?

Native 父分支保存 gSystemServerPidSIGCHLD 处理路径发现退出的 PID 正是 system_server 时,会记录错误并杀掉当前 Zygote,注释明确说明:让 init 重启 Zygote,再从那里重建 system_server,见 SigChldHandler()

恢复链是:

csharp 复制代码
system_server 退出
  → Zygote 收到并回收 child
  → 确认 PID == gSystemServerPid
  → Zygote 退出
  → init 观察到 zygote Service 退出
  → init 按 rc / service 策略处理重启
  → 新 Zygote 再 fork 新 system_server

为什么不只在旧 Zygote 中再 fork 一次 system_server?源码能够直接证明的是"system_server 死亡后,Zygote 主动退出,把恢复交回 init";SigChldHandler() 并没有给出唯一的官方设计理由。

从整体恢复设计看,可以作出一项工程解释 :长期运行的 Zygote 已经历更多状态变化,连同 Zygote 一起重建,能够让核心运行时重新走一遍受控的干净启动路径。这个解释用于理解恢复策略,不应冒充 SigChldHandler() 注释明确证明的事实。

这不意味着每台设备的最终故障表现都完全相同:criticalonrestart、崩溃窗口、bootloader 配置和厂商 rc 会进一步决定是否重启服务、重启 Framework 乃至重启设备。

九、三种创建路径放在一起比较

目标 直接创建者 触发方式 是否 exec 新 ELF 最终 Java 入口
主 Zygote init rc start zygote 是,exec app_process64 ZygoteInit.main()
system_server 主 Zygote 初始化时直接 forkSystemServer() SystemServer.main()
普通 App Zygote / USAP system_server 运行期经 Zygote socket 请求 通常 ActivityThread.main()

这张表是后续阅读所有 Framework 启动源码的坐标系。

十、直接创建者已经确定,容器装配仍未开始

结论 能否由固定源码直接证明
system_server 由主 Zygote 直接 fork
system_server 走了普通 Zygote socket 请求 不能,而且调用顺序反证
init 是 system_server 的直接父进程 不能,而且正常进程树反证
init 是这条进程链的上游创建与监督者
system_server 与 Zygote 共享一个可互改的堆 不能
system_server 一出现,所有系统服务都已 ready 不能,出生只完成进程层里程碑
所有产品的附加 group/capability 完全不变 不能,基线与产品配置可能调整

init 通过 rc 配置、fork/exec app_process64 建立并监督主 Zygote;主 Zygote 在 preload 后、不经过普通 socket 请求,直接 fork 并 specialize 出 system_server;子进程进入 SystemServer.main(),父进程则继续监听并孵化后续 App。

首读主线可以直接进入第七篇,看这个子进程怎样建立服务容器;想补齐运行期进程工厂,再阅读第六篇,区分"源码明确使用 socket"与"为什么没有选择 Binder"的架构解释。

源码定位

相关推荐
智购科技无人售货机工厂1 小时前
2026自动售货机防拆机物理安全设计:从安全螺丝到结构互锁的工程实践~YH
android·网络·驱动开发·python·单片机·安全·云原生
开开心心就好1 小时前
电子教鞭工具支持画框写字插图片功能齐全
android·开发语言·前端·javascript·人工智能·pdf·html
Dovis(誓平步青云)1 小时前
拍视频前先把镜头想清楚:做一个分镜取景辅助器
android·java·服务器·javascript·人工智能
峥嵘life2 小时前
Android16 系统 APEX 模块说明
android·大数据·开发语言
2601_962065493 小时前
PHP For 循环
android·java·php
码农coding4 小时前
android12 WindowManagerService窗口的添加过程
android
小小测试开发4 小时前
RAG评测指标实战:忠实度、上下文精确率/召回率从原理到CI落地
android·人工智能·spring boot·ci/cd
Dovis(誓平步青云)5 小时前
同一条路线在手机和平板上怎样换一种排版
android·开发语言·数据库·智能手机·音视频
firstacui5 小时前
Mysql调优
android·mysql·adb