子进程PID管理
Zygote 返回一个整数 PID,只说明 fork 或 USAP specialization 已经给出子进程身份;它还没有说明 AMS 已经接受这次启动,更不代表 ActivityThread.attachApplication() 已经完成。Android 17 把这段时间拆成几个可观察状态:ProcessList 先以 startSeq 保存 pending start,ZygoteProcess 返回 ProcessStartResult 后再次校验请求,成功才写入 ProcessRecord 和 PID 索引,最后等待应用用同一个 startSeq attach。
本文接在 ZygoteProcess.start 之后,边界到 ProcessList.handleProcessStartedLocked() 和 ActivityManagerService.attachApplicationLocked()。SIGCHLD 回收由下一篇 SIGCHLD进程死亡处理 负责;这里只讨论启动成功、迟到结果、PID 冲突和 attach 超时。
1. 启动请求的身份
源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java
相关函数:ProcessList.startProcessLocked()
@GuardedBy("mService")
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);
if (app.getStartSeq() != 0 || app.getPid() != 0) {
Slog.wtf(TAG, "process already has start identity: " + app);
}
final long startSeq = ++mProcStartSeqCounter;
app.setStartSeq(startSeq);
app.setStartParams(uid, hostingRecord, seInfo,
startUptime, startElapsedTime);
app.setUsingWrapper(invokeWith != null
|| Zygote.getWrapProperty(app.processName) != null);
mPendingStarts.put(startSeq, app);
...
}ProcessRecord 在这一刻仍然没有 PID,getPid() 还是 0;mPendingStarts 的 key 是本次启动请求的单调递增序号,而不是进程名。这样,同一包快速重启、同名进程并发启动或旧结果晚到时,AMS 可以判断“这是哪一次启动”,而不是只拿进程名或 UID 猜测。
startProcessLocked() 还会把 UID、HostingRecord、SELinux 信息和启动时间写入记录。它们不是 PID 的附属字段:后续的有效性检查、事件日志、BatteryStats 和 attach 校验都会消费同一份启动身份。
2. PID返回契约
源码文件:frameworks/base/core/java/android/os/Process.java
相关类型:Process.ProcessStartResult、Process.start()
public static ProcessStartResult start(
@NonNull String processClass, @Nullable String niceName,
int uid, int gid, @Nullable int[] gids,
int runtimeFlags, int mountExternal, int targetSdkVersion,
@Nullable String seInfo, @NonNull String abi,
@Nullable String instructionSet, @Nullable String appDataDir,
@Nullable String invokeWith, @Nullable String packageName,
int zygotePolicyFlags, boolean isTopApp,
@Nullable long[] disabledCompatChanges,
@Nullable long[] enabledCompatChanges, boolean useDeliQueue,
@Nullable Map<String, Pair<String, Long>> pkgDataInfoMap,
@Nullable Map<String, Pair<String, Long>> allowlistedDataInfoMap,
boolean bindMountAppsData, boolean bindMountAppStorageDirs,
boolean bindMountSystemOverrides, long startSeq,
@Nullable String[] zygoteArgs) {
final boolean isNative = android.os.Flags.nativeFrameworkPrototype()
&& (zygotePolicyFlags & ZYGOTE_POLICY_FLAG_NATIVE_PROCESS) != 0;
final IZygoteProcess process = isNative
? NATIVE_ZYGOTE_PROCESS : ZYGOTE_PROCESS;
return process.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi,
instructionSet, appDataDir, invokeWith, packageName,
zygotePolicyFlags, isTopApp, disabledCompatChanges,
enabledCompatChanges, useDeliQueue, pkgDataInfoMap,
allowlistedDataInfoMap, bindMountAppsData,
bindMountAppStorageDirs, bindMountSystemOverrides,
startSeq, zygoteArgs);
}Process.start() 选择 Java/native Zygote backend 并透传参数;它不负责把 PID 放进 AMS。真正的返回契约在 ProcessStartResult:
源码文件:frameworks/base/core/java/android/os/Process.java
public static final class ProcessStartResult {
/** The PID of the newly started process. */
public int pid;
/** True when a wrapper process was attached to the child. */
public boolean usingWrapper;
}失败通过异常表示,成功结果只有 pid 和 usingWrapper。因此读者在调用方看到 pid >= 0 时,不能直接推导“应用已经注册”;还要继续读 ProcessList.handleProcessStartedLocked()。
3. 普通Zygote与USAP响应
源码文件:frameworks/base/core/java/android/os/ZygoteProcess.java
相关函数:zygoteSendArgsAndGetResult()、attemptZygoteSendArgsAndGetResult()、attemptUsapSendArgsAndGetResult()
private Process.ProcessStartResult attemptZygoteSendArgsAndGetResult(
ZygoteState zygoteState, String msgStr)
throws ZygoteStartFailedEx {
try {
final BufferedWriter writer = zygoteState.mZygoteOutputWriter;
final DataInputStream input = zygoteState.mZygoteInputStream;
writer.write(msgStr);
writer.flush();
// 先读完整响应,避免残留字节污染下一次启动。
Process.ProcessStartResult result =
new Process.ProcessStartResult();
result.pid = input.readInt();
result.usingWrapper = input.readBoolean();
if (result.pid < 0) {
throw new ZygoteStartFailedEx("fork() failed");
}
return result;
} catch (IOException ex) {
zygoteState.close();
throw new ZygoteStartFailedEx(ex);
}
}
private Process.ProcessStartResult attemptUsapSendArgsAndGetResult(
ZygoteState zygoteState, String msgStr)
throws ZygoteStartFailedEx, IOException {
try (LocalSocket socket = zygoteState.getUsapSessionSocket()) {
...
Process.ProcessStartResult result =
new Process.ProcessStartResult();
result.pid = usapReader.readInt();
result.usingWrapper = false;
if (result.pid >= 0) return result;
throw new ZygoteStartFailedEx("USAP specialization failed");
}
}普通 Zygote 的响应是 int pid 加一个 boolean usingWrapper;USAP 响应只有 PID,因为 USAP 不能承担 wrapper 进程。USAP 的 I/O 失败会回退普通 Zygote,但 USAP 已经返回负 PID 时表示 specialization 失败,不应在调用方无条件再 fork 一次。
这里还有一个容易漏掉的边界:ZygoteProcess 读到成功 PID 后,子进程可能马上退出,或者 AMS 里对应的 pending start 已经被取消。PID 的生命周期因此必须由接收端再次校验。
4. 结果有效性
源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java
相关函数:handleProcessStartedLocked(ProcessRecord, ProcessStartResult, long)、isProcStartValidLocked()
@GuardedBy("mService")
private boolean handleProcessStartedLocked(ProcessRecord pending,
Process.ProcessStartResult startResult, long expectedStartSeq) {
// pending start 已被取消或已被另一条路径消费。
if (mPendingStarts.get(expectedStartSeq) == null) {
if (pending.getPid() == startResult.pid) {
pending.setUsingWrapper(startResult.usingWrapper);
}
return false;
}
return handleProcessStartedLocked(pending, startResult.pid,
startResult.usingWrapper, expectedStartSeq, false);
}
@GuardedBy("mService")
boolean handleProcessStartedLocked(ProcessRecord app, int pid,
boolean usingWrapper, long expectedStartSeq, boolean procAttached) {
mPendingStarts.remove(expectedStartSeq);
final String reason = isProcStartValidLocked(app, expectedStartSeq);
if (reason != null) {
Slog.w(TAG_PROCESSES, app + " start not valid, killing pid="
+ pid + ", " + reason);
app.setPendingStart(false);
killProcessQuiet(pid);
if (app.getPid() != 0) {
Process.killProcessGroup(app.uid, app.getPid());
}
noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_INVALID_START, reason);
app.doEarlyCleanupIfNecessaryLocked();
return false;
}
...
}这段代码是 PID 管理的核心分水岭:
- 先从
mPendingStarts移除预期序号,表明该启动结果已被消费。 isProcStartValidLocked()检查startSeq、UID、包是否仍可启动等条件。- 失败结果不会写入 PID map,而是杀掉刚 fork 的进程并记录
INVALID_START。
这解释了为什么“Zygote 日志有 Start proc”与“AMS 中能查到该 PID”可能同时不成立:前者发生在 fork 返回处,后者还要经过这段有效性检查。
5. 写入PID索引
源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java
相关函数:handleProcessStartedLocked()
mService.mBatteryStatsService.noteProcessStart(
app.processName, app.info.uid);
Watchdog.getInstance().processStarted(app.processName, pid);
StringBuilder buf = mStringBuilder;
buf.setLength(0);
buf.append("Start proc ").append(pid).append(':')
.append(app.processName).append('/')
.append(UserHandle.formatUid(app.getStartUid()));
mService.reportUidInfoMessageLocked(TAG, buf.toString(), app.getStartUid());
synchronized (mProcLock) {
app.setPid(pid);
app.setUsingWrapper(usingWrapper);
app.setPendingStart(false);
}
mService.addPidLocked(app);
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);顺序很重要:先完成启动统计和 Watchdog 记录,再在 mProcLock 下提交 pid、usingWrapper 与 pendingStart=false,接着调用 addPidLocked(),最后为尚未 attach 的进程安排超时消息。procAttached=true 只在 attach 竞争到达、需要把 pending start 直接补齐时使用。
addPidLocked() 并不是简单的 Map.put()。
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
相关函数:addPidLocked()、removePidLocked()
@GuardedBy("this")
void addPidLocked(ProcessRecord app) {
final int pid = app.getPid();
synchronized (mPidsSelfLocked) {
mPidsSelfLocked.doAddInternal(pid, app);
}
synchronized (sActiveProcessInfoSelfLocked) {
if (app.processInfo != null) {
sActiveProcessInfoSelfLocked.put(pid, app.processInfo);
} else {
sActiveProcessInfoSelfLocked.remove(pid);
}
}
mAtmInternal.onProcessMapped(pid,
app.getWindowProcessController());
}
@GuardedBy("this")
boolean removePidLocked(int pid, ProcessRecord app) {
final boolean removed;
synchronized (mPidsSelfLocked) {
removed = mPidsSelfLocked.doRemoveInternal(pid, app);
}
if (removed) {
synchronized (sActiveProcessInfoSelfLocked) {
sActiveProcessInfoSelfLocked.remove(pid);
}
mAtmInternal.onProcessUnMapped(pid);
}
return removed;
}一个 PID 同时服务三类消费者:AMS 的 mPidsSelfLocked 查询、活动进程信息查询、ATMS 的 WindowProcessController 映射。只改 ProcessRecord.pid 而不调用 addPidLocked(),会让 attach、窗口管理和 dumpsys activity processes 看到不一致的状态。
6. attach的第二次确认
源码文件:frameworks/base/core/java/android/app/ActivityThread.java
相关函数:attach()
private void attach(boolean system, long startSeq) {
if (!system) {
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
相关函数:attachApplicationLocked()
private void attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
ProcessRecord app;
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
if (app != null && (app.getStartUid() != callingUid
|| app.getStartSeq() != startSeq)) {
cleanUpApplicationRecordLocked(app, pid, false, false, -1,
true /* replacingPid */, false /* fromBinderDied */);
removePidLocked(pid, app);
app = null;
}
if (app == null && startSeq > 0) {
final 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;
}
}
...
}应用端的 attach 把自身 PID、调用 UID 和 startSeq 一起送回 AMS。AMS 先按 PID 查找,再用 UID/序号复核;PID 被复用或序号不匹配时,会清掉旧记录,不把新进程绑定到旧的 ProcessRecord。如果进程先 attach、ProcessList 的结果回调还没来,AMS 可以用 pending start 和 procAttached=true 补齐索引。
7. 冲突、超时与清理
PID复用
源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java
ProcessRecord oldApp;
synchronized (mService.mPidsSelfLocked) {
oldApp = mService.mPidsSelfLocked.get(pid);
}
if (oldApp != null && !app.isolated) {
Slog.wtf(TAG, "pid belongs to another existing app: " + pid);
mService.cleanUpApplicationRecordLocked(oldApp, pid,
false, false, -1,
true /* replacingPid */, false /* fromBinderDied */);
}
mService.addPidLocked(app);非 isolated 进程遇到同 PID 旧记录时,先清理旧的连接、LRU 和窗口状态,再写入新映射。startSeq 用来解释“哪个启动请求赢得了这个 PID”;它不是防止 Linux PID 复用的机制,而是防止 Framework 把旧异步结果写到新对象上。
attach超时
如果 ActivityThread.attach() 没有在期限内到达,PROC_START_TIMEOUT_MSG 会进入 AMS 的进程启动超时处理。wrapper 使用更长的 PROC_START_TIMEOUT_WITH_WRAPPER,因为 wrapper 进程还要完成额外的一跳。此时可以看到 PID 已经存在,但 ProcessRecord 仍会被判定为启动失败并进入杀进程/重启策略。
启动异常
Process.start() 抛异常时,异步路径会移除 mPendingStarts、清除 pendingStart 并执行 forceStopPackageLocked();同步路径也会把异常转换成启动失败。不要只在 logcat 中搜索 fork(),还应检查 pending start 是否被移除、PID map 是否写入,以及 attach timeout 是否被取消。
8. 沿源码验证
下面三组搜索分别覆盖“启动身份”“PID提交”“attach消费”,适合从一次 Start proc 日志反查完整链路:
# 启动序号、pending 映射、有效性检查和 PID 提交。
rg -n "mProcStartSeqCounter|mPendingStarts|handleProcessStartedLocked|isProcStartValidLocked|setPid\\(" \\
frameworks/base/services/core/java/com/android/server/am/ProcessList.java
# Zygote 普通/USAP 的 pid + wrapper 响应。
rg -n "ProcessStartResult|readInt\\(\\)|readBoolean\\(\\)|USAP specialization" \\
frameworks/base/core/java/android/os/Process.java \\
frameworks/base/core/java/android/os/ZygoteProcess.java
# ActivityThread 把 PID、UID 和 startSeq 交还给 AMS。
rg -n "attach\\(.*startSeq|attachApplicationLocked|mPendingStarts|startSeq" \\
frameworks/base/core/java/android/app/ActivityThread.java \\
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java一次排障应按这个顺序判断:Zygote 是否返回 PID;mPendingStarts 是否仍有该 startSeq;handleProcessStartedLocked() 是否因有效性失败杀掉 PID;mPidsSelfLocked 是否建立映射;最后才看 attachApplicationLocked() 和启动超时。这个顺序能把“没有 fork”“fork 后被拒绝”“已映射但未 attach”三类现象区分开。
