SystemServer知识图谱
本文面向已经读过 Android启动全景、SystemServer.run、SystemServiceManager 和 Process.start 的读者。它不是把前面的文章再压缩一遍,也不替代某个函数的逐行分析,而是建立一套可以反复使用的源码坐标系:看到一个启动问题时,先判断状态属于哪个进程和对象,再找到写入者、消费者、生效时机以及失败后的清理位置。
全文固定在 Android 17。读完后,你应能从 ZygoteInit.main() 解释 Zygote 父进程与 system_server 子进程为何走向不同入口;从 SystemServiceManager 区分“Java 服务实例已启动”“Binder 名称已发布”和“Boot Phase 已到达”;从 ProcessList 的 startSeq 追到 ActivityThread.attach();最后根据现象选择源码入口,而不是从 SystemServer.java 第一行开始盲目搜索。
1. 四层所有权
SystemServer 系列最容易混淆的不是函数名,而是不同层次都使用“启动”“注册”“就绪”这些词。先把四类所有者分开,后面的调用链才不会串错。
| 层次 | 主要所有者 | 持有的状态 | 直接消费者 | 典型完成点 |
|---|---|---|---|---|
| 进程孵化 | Zygote、ZygoteServer | socket、ABI、预加载结果、fork 返回值 | system_server 或应用子进程 | 父进程回到 runSelectLoop();子进程执行返回的 Runnable |
| 服务编排 | SystemServer、SystemServiceManager | 服务实例列表、当前 Boot Phase、并行任务 | 各 SystemService | onStart() 返回;某阶段全部 Future.get() 返回 |
| 服务发现 | servicemanager、ServiceManager | Binder 服务名到 Binder 对象的映射 | framework API、其他系统服务、应用进程 | IServiceManager.addService() 完成 |
| 应用进程 | AMS、ProcessList、ProcessRecord | pendingStart、startSeq、pid、attach 状态 | Zygote、ActivityThread、AMS | pid 被接收并登记;应用以相同 startSeq attach |
这四层之间存在依赖,但不存在一个覆盖全部含义的“ready”布尔值。例如,服务对象已经进入 mServices,并不表示它已经发布 Binder 接口;Binder 接口可以查询,也不表示服务已经收到 PHASE_BOOT_COMPLETED;Zygote 已返回 pid,也不表示应用已经完成 attachApplication()。
2. 启动主线
2.1 Zygote分叉
启动主线的第一个关键点是 forkSystemServer() 的返回值。
源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
相关函数:ZygoteInit.main
if (startSystemServer) {
Runnable r = forkSystemServer(
abiList, zygoteSocketName, zygoteServer);
// 本文注:父进程得到 null;system_server 子进程得到自己的入口。
if (r != null) {
r.run();
return;
}
}
Log.i(TAG, "Accepting command socket connections");
// 本文注:Zygote 父进程继续监听应用进程创建请求。
caller = zygoteServer.runSelectLoop(abiList);
// 本文注:普通应用子进程从 select loop 提前返回并执行应用入口。
if (caller != null) {
caller.run();
}同一个 Java 调用点在 fork 后分成三条控制流:Zygote 父进程进入 socket 循环;system_server 子进程直接执行 handleSystemServerProcess() 返回的入口;以后创建的应用子进程则从 runSelectLoop() 提前返回。判断代码运行在哪个进程,不能只看方法所属类,必须看 fork 返回值和后续 return。
forkSystemServer() 还固定了 system_server 的身份和入口。
源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
相关函数:forkSystemServer
String[] args = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
+ "1024,1032,1065,3001,3002,3003,3005,3006,3007,3009,3010,3011,3012",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
"--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
"com.android.server.SystemServer",
};
// 本文注:native fork 返回 0 的分支才是 system_server 子进程。
pid = Zygote.forkSystemServer(
parsedArgs.mUid, parsedArgs.mGid,
parsedArgs.mGids, parsedArgs.mRuntimeFlags,
null, parsedArgs.mPermittedCapabilities,
parsedArgs.mEffectiveCapabilities);
if (pid == 0) {
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
zygoteServer.closeServerSocket();
return handleSystemServerProcess(parsedArgs);
}
return null;这里的状态所有者仍是 Zygote:它准备 UID、GID、supplementary groups、capability 和目标类。子进程首先关闭继承来的 server socket,避免 system_server 意外继续拥有 Zygote 的监听端;双 Zygote 配置下还会等待另一侧 socket 可连接。任何“system_server 已 fork”的判断,都至少要区分“native fork 成功”“子进程入口已执行”和“Java 服务已启动”三个时刻。
下面的图只表达控制流分叉,不把预加载、服务注册和应用绑定混成一条直线。
图中蓝色节点由 Zygote 持有,绿色节点属于 system_server,靛色节点属于应用进程。橙色 fork 是所有权切换点;红色节点说明 socket 清理位于 finally,不是只有正常退出才执行。
2.2 服务分组
system_server 子进程进入 SystemServer.main() 后,核心编排发生在 run()。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
相关函数:SystemServer.run
try {
t.traceBegin("StartServices");
if (SystemProperties.getBoolean(
"sys.system_server_inherit_rt", false)) {
Binder.setGlobalInheritRt(true);
}
// 本文注:四个分组表达启动编排,不等同于四个独立进程。
startBootstrapServices(t);
startCoreServices(t);
startOtherServices(t);
startApexServices(t);
updateWatchdogTimeout(t);
CriticalEventLog.getInstance()
.logSystemServerStarted();
} catch (Throwable ex) {
Slog.e("System",
"************ Failure starting system services", ex);
throw ex;
} finally {
t.traceEnd();
}四个 start*Services() 是启动顺序的组织边界,不是服务生命周期本身。具体服务可能由 SystemServiceManager.startService() 创建,也可能由静态工厂创建后再注册;某些 APEX 服务还受模块安装结果和设备特性约束。主线程上的未处理异常会被重新抛出,不会自动跳过失败服务继续进入主循环。
2.3 主循环
服务启动完成后,system_server 主线程进入永久 Looper。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
相关位置:SystemServer.run 尾部
// 本文注:Binder 错误回调在主要系统服务启动后才安装。
Binder.setTransactionCallback(new IBinderCallback() {
@Override
public void onTransactionError(
int pid, int code, int flags, int err) {
mActivityManagerService
.frozenBinderTransactionDetected(
pid, code, flags, err);
}
});
// 本文注:正常运行期间主线程不应从 Looper.loop() 返回。
Looper.loop();
throw new RuntimeException(
"Main thread loop unexpectedly exited");这段代码给出两个重要边界。第一,服务分组返回只是初始化主线结束,SystemServer 的长期消费者是主线程消息队列、Binder 线程池和各服务自有线程。第二,Looper.loop() 返回被视为不变量破坏,恢复单位不是“重进 run”,而是由 Zygote/init 侧处理 system_server 进程死亡,详见 崩溃恢复链。
3. 服务状态
3.1 实例与名称
SystemServiceManager 管理 Java 服务实例和生命周期回调;ServiceManager 管理跨进程可发现的 Binder 名称。两者经常在一个 onStart() 中连续出现,但不是同一张表。
源码文件:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
相关函数:startService(Class)、startService(SystemService)
前一个重载通过公开的 Context 构造函数反射创建实例;下面保留后一个重载的真实状态写入顺序。
public void startService(@NonNull final SystemService service) {
// Check if already started
String className = service.getClass().getName();
if (mServiceClassnames.contains(className)) {
Slog.i(TAG, "Not starting an already started service " + className);
return;
}
// 本文注:先登记实例,再同步调用 onStart()。
mServiceClassnames.add(className);
mServices.add(service);
long time = SystemClock.elapsedRealtime();
try {
service.onStart();
} catch (RuntimeException ex) {
throw new RuntimeException("Failed to start service "
+ service.getClass().getName()
+ ": onStart threw an exception", ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart");
}mServiceClassnames 防止同一类被重复启动,mServices 是以后广播 Boot Phase 和用户生命周期事件的消费者列表。值得注意的是,源码先把实例加入列表,再调用 onStart();若 onStart() 抛异常,异常会终止当前启动主线,实例不会在这里自动从列表移除。它依赖 system_server 进程级失败恢复,而不是在同一进程内回滚所有已启动服务。
服务需要对其他进程可见时,还要主动发布 Binder 名称。
源码文件:frameworks/base/services/core/java/com/android/server/SystemService.java
相关函数:publishBinderService、publishLocalService
protected final void publishBinderService(
String name, IBinder service,
boolean allowIsolated, int dumpPriority) {
// 本文注:跨进程消费者通过 servicemanager 名称查询。
ServiceManager.addService(
name, service, allowIsolated, dumpPriority);
}
protected final <T> void publishLocalService(
Class<T> type, T service) {
// 本文注:LocalServices 只服务于 system_server 进程内部。
LocalServices.addService(type, service);
}因此,“服务注册”至少有三种含义:实例进入 SystemServiceManager.mServices,Binder 对象进入 servicemanager 名称空间,本地接口进入 LocalServices。排查 getSystemService() 返回空时应看 Binder 发布链;排查 system_server 内部依赖为空时应看 LocalServices;排查 onBootPhase() 没被调用时才看 mServices 和阶段推进。
3.2 阶段推进
Boot Phase 是单调递增的全局阶段,不是每个服务独立推进的状态机。
源码文件:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
相关函数:startBootPhase
public void startBootPhase(@NonNull TimingsTraceAndSlog t, int phase) {
if (phase <= mCurrentPhase) {
throw new IllegalArgumentException("Next phase must be larger than previous");
}
mCurrentPhase = phase;
Slog.i(TAG, "Starting phase " + mCurrentPhase);
try {
t.traceBegin("OnBootPhase_" + phase);
final int serviceLen = mServices.size();
ArrayList<SystemService> serialServices = new ArrayList<>();
ArrayList<SystemService> parallelServices = new ArrayList<>();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
if (!Flags.parallelizeOnbootphase()
|| service.getBootPhaseSerial(mCurrentPhase)) {
serialServices.add(service);
} else {
parallelServices.add(service);
}
}
// 本文注:串行服务直接在当前线程执行。
for (final SystemService service : serialServices) {
long time = SystemClock.elapsedRealtime();
t.traceBegin("OnBootPhase_" + phase + "_" + service.getClass().getName());
try {
service.onBootPhase(mCurrentPhase);
} catch (Exception ex) {
throw new RuntimeException("Failed to boot service "
+ service.getClass().getName()
+ ": onBootPhase threw an exception during phase "
+ mCurrentPhase, ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onBootPhase");
t.traceEnd();
}
if (Flags.parallelizeOnbootphase()) {
final Future[] futures = new Future[parallelServices.size()];
for (int i = 0; i < parallelServices.size(); i++) {
final SystemService service = parallelServices.get(i);
futures[i] = SystemServerInitThreadPool.submit(() -> {
long time = SystemClock.elapsedRealtime();
try {
service.onBootPhase(mCurrentPhase);
} catch (Exception ex) {
throw new RuntimeException("Failed to boot service "
+ service.getClass().getName()
+ ": onBootPhase threw an exception during phase "
+ mCurrentPhase, ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time,
service, "onBootPhase");
}, "OnBootPhase_" + phase + "_" + service.getClass().getName());
}
// 本文注:阶段完成点仍是所有任务汇合,而不是 submit 返回。
for (int i = 0; i < futures.length; i++) {
try {
futures[i].get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
} finally {
t.traceEnd();
}
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
final long totalBootTime = SystemClock.uptimeMillis() - mRuntimeStartUptime;
t.logDuration("TotalBootTime", totalBootTime);
shutdownInitThreadPool();
}
}这段源码同时展示了 trace、耗时告警和异常包装。mCurrentPhase 先更新,再向服务分发;阶段号倒退或重复会立即失败。并行开关开启后,服务可以通过 getBootPhaseSerial() 留在串行组,其他服务才进入初始化线程池。Future.get() 是本阶段的提交屏障:后续阶段不能在并行回调仍运行时开始。到 PHASE_BOOT_COMPLETED 后线程池被关闭,所以不能把它当作系统运行期的通用执行器。
图中主线程拥有阶段顺序,服务实例拥有各自业务状态,线程池只拥有任务执行。并行化改变回调运行线程,却没有改变阶段完成的消费者:SystemServer 仍要等 startBootPhase() 返回。
3.3 失败边界
| 失败点 | 当时已写入的状态 | 处理方式 | 不应误判为 |
|---|---|---|---|
| 服务构造失败 | 尚未加入 mServices | 包装为 RuntimeException,终止启动主线 | 服务被忽略后继续启动 |
onStart() 抛异常 | 类名和实例已加入管理列表 | 重新抛出,通常进入进程级恢复 | 自动回滚服务注册 |
| Boot Phase 倒退 | mCurrentPhase 未改写 | IllegalArgumentException | 忽略重复阶段 |
| 串行回调异常 | mCurrentPhase 已推进 | 包装后抛出 | 继续通知剩余服务 |
| 并行回调异常 | 任务已提交 | Future.get() 将异常带回主线程 | 后台静默失败 |
| Binder 发布失败 | Java 实例可能已启动 | ServiceManager.addService() 记录远端异常 | 生命周期和服务发现同时成功 |
这张表说明服务启动缺少跨服务事务式回滚。关键服务初始化失败时,Android 依靠 system_server 的进程重启重建整体状态;服务自身若持有外部资源,仍应在内部失败路径中及时关闭文件、线程或 native handle,不能假设进程一定立刻退出。
4. 进程主线
4.1 待启动状态
应用进程启动不是“AMS 调用一次 fork”这么简单。AMS 侧先创建一个带序号的待完成事务,Zygote 返回 pid 后再验证这次返回是否仍对应当前 ProcessRecord。
源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java
相关函数:startProcessLocked
boolean startProcessLocked(
HostingRecord hostingRecord, String entryPoint,
ProcessRecord app, int uid, int[] gids,
int runtimeFlags, int zygotePolicyFlags,
int mountExternal, String seInfo,
String requiredAbi, String instructionSet,
String invokeWith, long startUptime,
long startElapsedTime) {
app.setPendingStart(true);
app.setRemoved(false);
// ... 查询 compat change,并记录 wrapper 等启动参数。
final long startSeq = ++mProcStartSeqCounter;
app.setStartSeq(startSeq);
app.setStartParams(uid, hostingRecord, seInfo,
startUptime, startElapsedTime);
mPendingStarts.put(startSeq, app);
if (mService.mConstants.FLAG_PROCESS_START_ASYNC) {
// 本文注:异步路径释放 AMS 全局锁后再访问 Zygote。
mService.mProcStartHandler.post(() ->
handleProcessStart(app, entryPoint, gids,
runtimeFlags, zygotePolicyFlags,
mountExternal, requiredAbi,
instructionSet, invokeWith, startSeq));
return true;
}
try {
Process.ProcessStartResult result =
startProcess(hostingRecord, entryPoint, app,
uid, gids, runtimeFlags,
zygotePolicyFlags, mountExternal,
seInfo, requiredAbi, instructionSet,
invokeWith, startUptime);
handleProcessStartedLocked(app, result.pid,
result.usingWrapper, startSeq, false);
} catch (RuntimeException e) {
app.setPendingStart(false);
mService.forceStopPackageLocked(
app.info.packageName,
UserHandle.getAppId(app.uid),
false, false, true, false, false, false,
app.userId, "start failure");
}
return app.getPid() > 0;
}startSeq 是一次启动尝试的身份,不是 pid 的替代品。pid 可能复用,异步返回也可能晚于一次新的启动尝试;mPendingStarts 用 startSeq 找回 owner,pendingStart 表示事务尚未完成。异步开关开启时,方法返回 true 只表示任务已投递,不表示 fork 已完成,更不表示应用已经 attach。
Zygote 返回后,handleProcessStartedLocked() 先消费待启动记录,再决定是否接受 pid。
源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java
相关函数:handleProcessStartedLocked
boolean handleProcessStartedLocked(
ProcessRecord app, int pid, boolean usingWrapper,
long expectedStartSeq, boolean procAttached) {
mPendingStarts.remove(expectedStartSeq);
String reason =
isProcStartValidLocked(app, expectedStartSeq);
if (reason != null) {
// 本文注:过期启动返回的 pid 不能覆盖更新后的 ProcessRecord。
app.setPendingStart(false);
killProcessQuiet(pid);
final int appPid = app.getPid();
if (appPid != 0) {
Process.killProcessGroup(app.uid, appPid);
}
noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_INVALID_START, reason);
app.doEarlyCleanupIfNecessaryLocked();
return false;
}
// ... 记录 BatteryStats、EventLog、PMS 和 Watchdog 启动信息。
synchronized (mProcLock) {
app.setPid(pid);
app.setUsingWrapper(usingWrapper);
app.setPendingStart(false);
}
mService.addPidLocked(app);
synchronized (mService.mPidsSelfLocked) {
if (!procAttached) {
Message msg = mService.mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
msg.obj = app;
mService.mHandler.sendMessageDelayed(msg, usingWrapper
? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
}
}
dispatchProcessStarted(app, pid);
return true;
}正常消费者是 AMS 的 pid 映射和后续 attach;过期结果的消费者则是清理路径。这里体现了一个重要不变量:只有 expectedStartSeq 仍匹配当前启动尝试,pid 才能写回 ProcessRecord。否则系统宁愿杀掉新 fork 的进程,也不能把它错误绑定到旧记录。
4.2 参数协议
ProcessList 把安全身份、挂载模式、ABI、SELinux 信息和 startSeq 传给 Process.start()。
源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java
相关位置:startProcess 的 regular Zygote 分支
startResult = Process.start(
entryPoint,
app.processName,
uid,
uid,
gids,
runtimeFlags,
mountExternal,
app.info.targetSdkVersion,
seInfo,
requiredAbi,
instructionSet,
app.info.dataDir,
invokeWith,
app.info.packageName,
zygotePolicyFlags,
isTopApp,
app.getDisabledCompatChanges(),
app.getEnabledCompatChanges(),
useDeliQueue,
pkgDataInfoMap,
allowlistedAppDataInfoMap,
bindMountAppsData,
bindMountAppStorageDirs,
bindOverrideSysprops,
app.getStartSeq(),
new String[]{PROC_START_SEQ_IDENT
+ app.getStartSeq()});这些参数不是装饰性元数据。UID/GID 和 supplementary groups 决定 Linux 身份;seInfo 参与 SELinux domain 选择;ABI 决定连接主 Zygote 还是次 Zygote;mountExternal 和 bind-mount 参数决定可见文件系统;startSeq 最终回到 AMS 完成身份校验。修改其中一个字段时,必须继续追踪 Zygote 参数解析和 native specialization,而不是只改 Java 调用签名。
ZygoteProcess.startViaZygote() 将结构化参数编码为换行协议。
源码文件:frameworks/base/core/java/android/os/ZygoteProcess.java
相关函数:startViaZygote、zygoteSendArgsAndGetResult
ArrayList<String> argsForZygote = new ArrayList<>();
// 本文注:身份与运行参数位于协议前部。
argsForZygote.add("--runtime-args");
argsForZygote.add("--setuid=" + uid);
argsForZygote.add("--setgid=" + gid);
argsForZygote.add("--runtime-flags=" + runtimeFlags);
// ... 根据 mountExternal 添加对应的挂载模式参数。
argsForZygote.add("--target-sdk-version="
+ targetSdkVersion);
if (gids != null && gids.length > 0) {
final StringBuilder sb = new StringBuilder();
sb.append("--setgroups=");
final int sz = gids.length;
for (int i = 0; i < sz; i++) {
if (i != 0) {
sb.append(',');
}
sb.append(gids[i]);
}
argsForZygote.add(sb.toString());
}
if (niceName != null) {
argsForZygote.add("--nice-name=" + niceName);
}
if (seInfo != null) {
argsForZygote.add("--seinfo=" + seInfo);
}
if (instructionSet != null) {
argsForZygote.add("--instruction-set="
+ instructionSet);
}
if (appDataDir != null) {
argsForZygote.add("--app-data-dir=" + appDataDir);
}
argsForZygote.add(processClass);
if (extraArgs != null) {
Collections.addAll(argsForZygote, extraArgs);
}上面保留了参数类别、顺序和 supplementary groups 的真实构造过程。真正写 socket 前还有一次完整校验。
源码文件:frameworks/base/core/java/android/os/ZygoteProcess.java
相关函数:zygoteSendArgsAndGetResult
for (String arg : args) {
// 本文注:先验证整个列表,避免只发送半条命令。
if (arg.indexOf('\n') >= 0) {
throw new ZygoteStartFailedEx(
"Embedded newlines not allowed");
} else if (arg.indexOf('\r') >= 0) {
throw new ZygoteStartFailedEx(
"Embedded carriage returns not allowed");
} else if (arg.indexOf('\u0000') >= 0) {
throw new ZygoteStartFailedEx(
"Embedded nulls not allowed");
}
}
// 本文注:第一行是参数数量,后续每行一个参数。
String msgStr = args.size() + "\n"
+ String.join("\n", args) + "\n";换行、回车和 NUL 会破坏帧边界,所以校验发生在任何写入之前。这是协议的失败原子性:编码失败不会让 Zygote 收到一个参数计数正确、内容却不完整的请求。socket I/O 失败则属于另一层,调用方会将其包装为进程启动失败并清理 mPendingStarts。
4.3 attach闭环
应用子进程从 RuntimeInit.applicationInit() 找到 ActivityThread.main()。startSeq 作为普通命令行参数进入应用进程,并在 attach 时原样带回 AMS。
源码文件:frameworks/base/core/java/android/app/ActivityThread.java
相关函数:main、attach
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null
&& args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(args[i].substring(
PROC_START_SEQ_IDENT.length()));
}
}
}
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
Looper.loop();
throw new RuntimeException(
"Main thread loop unexpectedly exited");main() 只负责建立应用主线程和发起 attach;真正跨进程交回 AMS 的代码位于 attach(false, startSeq)。
源码文件:frameworks/base/core/java/android/app/ActivityThread.java
相关函数:attach
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
mSystemThread = system;
mStartSeq = startSeq;
if (!system) {
RuntimeInit.setApplicationObject(
mAppThread.asBinder());
IActivityManager mgr =
ActivityManager.getService();
try {
// 本文注:Binder 调用把应用线程接口和启动序号交回 AMS。
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}ActivityThread 是应用进程内的状态所有者,mAppThread 是 system_server 回调应用进程的 Binder 入口。AMS 消费 startSeq 时还会同时读取 Binder calling pid 和 uid。
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
相关函数:attachApplicationLocked
ProcessRecord app;
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
if (app != null
&& (app.getStartUid() != callingUid
|| app.getStartSeq() != startSeq)) {
// 本文注:pid 已被其他启动占用时,先清理旧映射。
cleanUpApplicationRecordLocked(
app, pid, false, false, -1,
true, false);
removePidLocked(pid, app);
app = null;
}
if (app == null && startSeq > 0) {
ProcessRecord pending =
mProcessList.mPendingStarts.get(startSeq);
if (pending != null
&& pending.getStartUid() == callingUid
&& pending.getStartSeq() == startSeq
&& mProcessList.handleProcessStartedLocked(
pending, pid, pending.isUsingWrapper(),
startSeq, true)) {
app = pending;
}
}
if (app == null) {
EventLogTags.writeAmDropProcess(pid);
killProcessQuiet(pid);
return;
}这段逻辑覆盖“应用 attach 比 system_server 处理 fork 返回更快”的竞态:如果 pid map 尚未建立,AMS 可以用 startSeq 从 mPendingStarts 找回 ProcessRecord 并补做登记。反方向上,如果 pid、uid 或序号不一致,进程会被丢弃,防止错误的应用线程绑定到另一个记录。
图中的关键并不是方法数量,而是同一个 startSeq 被三个进程共同携带:system_server 创建它,Zygote 透明传递它,应用进程再交还它。它让 AMS 能把异步 fork 结果、pid 映射和 Binder attach 归并为同一次启动尝试。
4.4 失败清理
| 场景 | 检测位置 | 清理动作 | 恢复单位 |
|---|---|---|---|
| 参数含换行、回车或 NUL | ZygoteProcess 写 socket 前 | 不发送请求,抛出启动异常 | 当前启动尝试 |
| socket 或 fork 失败 | ProcessList.handleProcessStart() | 删除 mPendingStarts、清除 pendingStart、force-stop、early cleanup | 包或进程启动请求 |
| 迟到的 fork 结果 | isProcStartValidLocked() | 杀新 pid、记录退出原因、early cleanup | 当前 pid |
| pid 被旧记录占用 | attachApplicationLocked() | 清理旧 ProcessRecord 和 pid map | pid 映射 |
| attach 无匹配记录 | attachApplicationLocked() | 写 am_drop_process 并杀进程 | 当前应用进程 |
| bind 完成超时 | AMS timeout handler | 杀掉卡住的 ProcessRecord | 当前应用进程 |
这一链路没有“收到 pid 就成功”的单一完成点。至少要观察 Zygote 返回、AMS 接受 pid、应用 attach、bindApplication 执行和最终超时消息移除。需要逐步跟踪参数和 Binder 回调时,可继续阅读 Zygote参数传递协议、子进程PID管理 和 handleBindApplication。
5. 故障坐标
下面的关系图把“现象”连接到第一个值得检查的 owner。它不是自动诊断器,而是避免一上来就在错误进程里搜日志。
把图转换成源码搜索表,可以得到更具体的入口:
| 现象 | 第一入口 | 状态 owner | 下一消费者 | 关键边界 |
|---|---|---|---|---|
StartServices 很长 | SystemServer.run() | SystemServer 主线程 | 四个服务分组 | trace slice 可能不等待服务内部异步任务 |
某服务 onStart() 已运行但客户端查不到 | publishBinderService() | 服务实例与 servicemanager | ServiceManager.getService() | Java 生命周期与 Binder 发布是两次操作 |
| Boot Phase 卡住 | startBootPhase() | mCurrentPhase、Future 数组 | 下一阶段调用方 | 并行任务仍由 Future.get() 汇合 |
| 应用启动一直 pending | startProcessLocked() | ProcessRecord、mPendingStarts | Zygote socket | 异步投递成功不等于 fork 成功 |
| 有 pid 但 attach 被拒绝 | attachApplicationLocked() | AMS pid map | bindApplication | pid、uid、startSeq 必须同时匹配 |
| system_server 崩溃后重启 | ZygoteInit 与 init service | Zygote/init | 新 system_server | 恢复单位是进程,不是单个服务 |
| 首次启动明显更慢 | isFirstBootOrUpgrade()、PMS | PackageManagerService | 启动统计与 dexopt | 不能和普通启动样本直接比较 |
| 多用户切换后进程异常 | UserController、SSM user callbacks | 用户状态与 per-user 进程 | 各系统服务 | user 事件与全局 Boot Phase 是两套生命周期 |
| RSS/PSS 持续增长 | dumpsys meminfo、heap/native owner | system_server heap、native allocator | GC、服务缓存、native 组件 | 单次 PSS 不能证明泄漏 |
6. 测试边界
6.1 服务注册测试
源码文件:frameworks/base/services/tests/servicestests/src/com/android/server/SystemServiceManagerTest.java。
@Test
public void testDuplicateServices() throws Exception {
AtomicInteger counter = new AtomicInteger(0);
SystemService service = new SystemService(getContext()) {
@Override
public void onStart() {
counter.incrementAndGet();
}
};
mSystemServiceManager.startService(service);
assertEquals(1, counter.get());
// 本文注:第二次提交同一服务类不能再次执行 onStart()。
mSystemServiceManager.startService(service);
assertEquals(1, counter.get());
}
@Test
public void testSealStartedServices() throws Exception {
// ... 构造 service1 和 service2;service2.onStart() 若被调用会主动失败。
mSystemServiceManager.startService(service1);
assertTrue(serviceStarted.get());
mSystemServiceManager.sealStartedServices();
assertThrows(UnsupportedOperationException.class,
() -> mSystemServiceManager.startService(service2));
}第一个测试的输入是同一个服务实例被提交两次,关键断言是 onStart() 计数仍为 1,它验证类名集合确实承担去重职责。第二个测试先启动一个服务,再封存列表并尝试新增服务,关键断言是抛出 UnsupportedOperationException;它验证封存后的列表不再接受写入。两者都没有覆盖 Boot Phase 并行任务、Binder 名称发布或 onStart() 失败后的进程恢复,不能据此推导这些路径也正确。
6.2 进程超时测试
源码文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java。
@Test
public void testNormal() throws Exception {
if (mRealAms.mConstants
.mEnableWaitForFinishAttachApplication) {
ProcessRecord app =
startProcessAndWait(false);
verify(app, never())
.killLocked(any(), anyInt(), anyBoolean());
}
}
@Test
public void testWedged() throws Exception {
ProcessRecord app = startProcessAndWait(true);
verify(app).killLocked(
any(), anyInt(), anyBoolean());
}两个测试共同调用真实测试辅助函数 startProcessAndWait():它先让 ProcessList 接受 pid,再调用测试 IApplicationThread.bindApplication();wedge=false 时测试线程回报 finish-attach,wedge=true 时等待超过 PROC_START_TIMEOUT。关键断言分别是正常进程不被杀、超时进程必须被杀。它验证了“fork/pid 已完成仍不代表启动完成”,以及超时消息的消费者确实执行清理;但它没有经过真实 Zygote socket、native fork 或设备调度,因此不能用来衡量真实启动耗时。
6.3 参数解析测试
源码文件:frameworks/base/core/tests/coretests/src/com/android/internal/os/ZygoteArgumentsTest.java。
@Test
public void testParseAndMergeCompatChanges_dedup() {
long[] changes = {1, 2};
changes = ZygoteArguments.parseAndMergeCompatChanges(
"--enabled-compat-changes=2,3,3,4",
changes);
long[] expected = {1, 2, 3, 4};
assertArrayEquals(expected, changes);
}
@Test
public void testParseAndMergeCompatChanges_malformed() {
long[] changes = null;
changes = ZygoteArguments.parseAndMergeCompatChanges(
"--disabled-compat-changes=1,,2",
changes);
long[] expected = {1, 2};
assertArrayEquals(expected, changes);
}这组测试验证 compat change 参数在多次输入下会排序、合并和去重,并允许空片段;它说明 Zygote 参数不是简单字符串透传,而是会形成结构化状态。测试没有覆盖 socket 帧校验、UID/GID 权限限制和 native specialization,阅读参数链时仍要继续跟到 ZygoteArguments.getInstance() 与 Zygote.nativeForkAndSpecialize()。
7. 全篇索引
索引按问题所有者分组。每个链接的可见名称都与文章标题一致;编号只留在目标文件名中,不要求读者记忆。
7.1 Zygote入口
| 阅读目的 | 文章 |
|---|---|
| 建立全局进程图 | Android启动全景 |
| 找到 native 入口 | app_process入口、AndroidRuntime.start |
| 进入 Java 主线 | ZygoteInit.main、Zygote socket注册 |
| 理解共享预加载 | Zygote预加载、preloadClasses、preloadResources、Zygote fork准备 |
| 理解双进程与请求循环 | Zygote双进程、ZygoteServer循环、Zygote安全限制 |
7.2 SystemServer入口
| 阅读目的 | 文章 |
|---|---|
| 跟踪 fork 后接管 | forkSystemServer、SystemServer接管、zygoteInit与RuntimeInit |
| 跟踪 Java 启动骨架 | SystemServer.main、SystemServer.run、createSystemContext |
| 分解服务分组 | Bootstrap服务、Core服务、Other服务、APEX服务 |
7.3 服务编排
| 阅读目的 | 文章 |
|---|---|
| 区分名称注册与实例管理 | ServiceManager.addService、SystemServiceManager |
| 理解生命周期和阶段 | SystemService生命周期、BootPhase阶段 |
| 理解存活监控和顺序 | Watchdog监控、核心服务顺序、AMS/WMS/PMS时序 |
| 分析依赖与耗时 | 服务依赖关系、服务启动耗时、Trampoline模式 |
7.4 应用进程
| 阅读目的 | 文章 |
|---|---|
| 进入创建请求 | Process.start、ZygoteProcess.start |
| 理解 fork 策略 | USAP池、fork与COW、进程specialization、ABI进程选择 |
| 进入应用 Java 世界 | RuntimeInit.applicationInit、ActivityThread.main、handleBindApplication、Application.onCreate |
7.5 协议与隔离
| 阅读目的 | 文章 |
|---|---|
| 跟踪 socket 请求 | LocalSocket与Zygote通信、ZygoteConnection.processCommand、Zygote参数传递协议 |
| 跟踪 pid 生死 | 子进程PID管理、SIGCHLD进程死亡处理 |
| 理解安全身份 | 进程UID/GID设置、进程Capability设置、Seccomp沙箱 |
7.6 启动优化
| 阅读目的 | 文章 |
|---|---|
| 建立时间线 | 启动时间分析、开机动画时序、启动场景判定 |
| 选择优化手段 | 预加载优化、并行启动、延迟启动 |
7.7 运行诊断
| 阅读目的 | 文章 |
|---|---|
| 处理进程级故障 | 崩溃恢复链 |
| 处理环境差异 | 多用户进程、容器化启动 |
| 处理资源与性能 | SystemServer内存、SystemServer分析 |
8. 问题入口
| 你要回答的问题 | 建议起点 | 继续深入 | 不要先做什么 |
|---|---|---|---|
| 哪个阶段拖慢开机 | 启动时间分析 | 并行启动、SystemServer分析 | 先改 preload 列表 |
| 某系统服务为什么没起来 | SystemServiceManager | SystemService生命周期、服务依赖关系 | 只搜 addService 字符串 |
| 应用为什么没有 pid | Process.start | ZygoteProcess.start、Zygote参数传递协议 | 直接从 Application.onCreate() 倒推 |
| 有 pid 为什么没进入应用 | ActivityThread.main | handleBindApplication、子进程PID管理 | 把 fork 返回当成启动完成 |
| system_server 为什么反复重启 | 崩溃恢复链 | Watchdog监控、SIGCHLD进程死亡处理 | 只查看最后一次 Java exception |
| 用户切换后哪些服务被通知 | 多用户进程 | SystemService生命周期 | 把用户事件当作 Boot Phase |
| Cuttlefish 中 Zygote 有何差异 | 容器化启动 | 进程Capability设置、进程specialization | 假设宿主 capability 全部可用 |
| system_server RSS/PSS 为什么增长 | SystemServer内存 | SystemServer分析 | 用一份 meminfo 直接下泄漏结论 |
| 首次启动为何无法复现普通启动 | 启动场景判定 | 预加载优化 | 混合 first boot、OTA 和 runtime restart 样本 |
9. 源码导航
以下命令都在 AOSP 根目录执行。注释先说明问题,再给出搜索范围,避免全仓搜索产生大量同名符号。
# 找到 SystemServer 服务分组、阶段推进和最终主循环。
rg -n 'start(Bootstrap|Core|Other|Apex)Services|startBootPhase|Looper\.loop' \
frameworks/base/services/java/com/android/server/SystemServer.java \
frameworks/base/services/core/java/com/android/server/SystemServiceManager.java若问题发生在 fork 前后,再把搜索范围切换到 Zygote 入口和请求循环:
# 找到 system_server fork 的父子分支和应用请求循环。
rg -n 'forkSystemServer|handleSystemServerProcess|runSelectLoop' \
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java \
frameworks/base/core/java/com/android/internal/os/ZygoteServer.java若现象是应用进程迟迟不能完成启动,先追踪 AMS 持有的启动序号:
# 跟踪同一个 startSeq 如何从 ProcessList 进入应用,再返回 AMS。
rg -n 'mProcStartSeqCounter|mPendingStarts|PROC_START_SEQ_IDENT|attachApplication' \
frameworks/base/services/core/java/com/android/server/am/ProcessList.java \
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java \
frameworks/base/core/java/android/app/ActivityThread.java确认 startSeq 主线后,再检查它与安全身份如何一起编码和解析:
# 查找 Zygote 参数编码、解析和 native specialization 的边界。
rg -n 'startViaZygote|zygoteSendArgsAndGetResult|getInstance|nativeForkAndSpecialize' \
frameworks/base/core/java/android/os/ZygoteProcess.java \
frameworks/base/core/java/com/android/internal/os/ZygoteArguments.java \
frameworks/base/core/java/com/android/internal/os/Zygote.java完成静态阅读后,可以用对应测试检查服务去重和 attach 超时边界:
# 运行前先确认设备或测试环境支持对应模块;两个测试分别覆盖服务注册和启动超时。
atest SystemServiceManagerTest AsyncProcessStartTest命令输出的函数位置只是入口。真正阅读时,应继续回答四个问题:谁写状态、在哪把状态交给另一个进程或线程、哪个条件使它生效、失败时由谁清理。
10. 闭环复述
- 从
ZygoteInit.main()开始,分别复述父 Zygote、system_server 子进程和普通应用子进程的返回值与下一入口,并指出每条路径关闭哪个 socket。 - 选择一个
SystemService,分别找到它进入mServices、执行onStart()、发布 Binder 或 LocalService、接收关键 Boot Phase 的位置。若四个位置不全,说明缺少的是哪一种能力。 - 从
ProcessList.startProcessLocked()的startSeq出发,追到ZygoteProcess参数列表、ActivityThread.main()参数解析和ActivityManagerService.attachApplicationLocked()校验。然后解释 pid 复用为什么不能只靠 pid 识别启动尝试。 - 给定“开机慢但 CPU 不高”的现象,先用 启动时间分析 找长阶段,再检查该阶段是否等待
Future.get()、Binder 返回或锁;只有确认 owner 后,才选择 SystemServer分析 中的 Perfetto、BinderCallsStats 或 LooperStats。
这四条主线能完整复述后,SystemServer 就不再是一份超长启动函数,而是一组边界清楚的状态交接:Zygote 交出进程控制权,SystemServiceManager 交出生命周期阶段,servicemanager 交出跨进程可发现性,ProcessList 用 startSeq 把异步 fork 和应用 attach 收拢为同一次启动事务。以后遇到新服务、新启动 flag 或新的进程类型,先把它放回这套所有权坐标,再沿真实调用方和消费者向两端扩展。
