Skip to content

子进程PID管理

追踪 Zygote 返回的 PID 如何经过 startSeq 校验,进入 ProcessRecord、PID 索引、Watchdog 和应用 attach 超时处理。

基于android-17.0.0_r1
AndroidAMSProcessRecordZygote源码阅读

子进程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()

java
@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()

java
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

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()

java
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()

java
@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 管理的核心分水岭:

  1. 先从 mPendingStarts 移除预期序号,表明该启动结果已被消费。
  2. isProcStartValidLocked() 检查 startSeq、UID、包是否仍可启动等条件。
  3. 失败结果不会写入 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()

java
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()

java
@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()

java
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()

java
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

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 日志反查完整链路:

bash
# 启动序号、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”三类现象区分开。