Skip to content

USAP池

追踪 USAP 策略、预 fork、native table、specialization、PID reporting pipe、SIGTERM 和 refill。

基于android-17.0.0_r1
AndroidZygoteUSAPfork源码阅读

USAP池 ​

USAP(Unspecialized App Process)把“fork 一个空白应用进程”和“把它特化成目标应用”拆成两个时间点。Zygote 空闲时预 fork 若干 USAP,保存在 native table;应用启动时 SystemServer 直接连接 USAP socket,USAP 校验参数、返回自己的 pid、通知父 Zygote 从池表移除,随后执行 uid/gid/SELinux/mount specialization 和 zygoteInit()。池数量降低后,ZygoteServer 根据 min/max/threshold 立即或延迟补池。

1. 池策略参数 ​

源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteServer.java

java
mUsapPoolSizeMax = Integer.min(
        ZygoteConfig.getInt(
                ZygoteConfig.USAP_POOL_SIZE_MAX,
                ZygoteConfig.USAP_POOL_SIZE_MAX_DEFAULT),
        ZygoteConfig.USAP_POOL_SIZE_MAX_LIMIT);

mUsapPoolSizeMin = Integer.max(
        ZygoteConfig.getInt(
                ZygoteConfig.USAP_POOL_SIZE_MIN,
                ZygoteConfig.USAP_POOL_SIZE_MIN_DEFAULT),
        ZygoteConfig.USAP_POOL_SIZE_MIN_LIMIT);

mUsapPoolRefillThreshold = Integer.min(
        ZygoteConfig.getInt(
                ZygoteConfig.USAP_POOL_REFILL_THRESHOLD,
                ZygoteConfig.USAP_POOL_REFILL_THRESHOLD_DEFAULT),
        mUsapPoolSizeMax);

max/min/threshold/delay 可运行时调整,并限制在硬编码上下界。若 min >= max,源码恢复默认值并把 threshold 设置为 max/2。策略由 ZygoteServer 保存,不属于单个 USAP。

2. fillUsapPool ​

java
int usapPoolCount = Zygote.getUsapPoolCount();
int numUsapsToSpawn;
if (isPriorityRefill) {
    numUsapsToSpawn =
            mUsapPoolSizeMin - usapPoolCount;
} else {
    numUsapsToSpawn =
            mUsapPoolSizeMax - usapPoolCount;
}

ZygoteHooks.preFork();
while (--numUsapsToSpawn >= 0) {
    Runnable caller = Zygote.forkUsap(
            mUsapPoolSocket,
            sessionSocketRawFDs,
            isPriorityRefill);
    if (caller != null) return caller;
}
ZygoteHooks.postForkCommon();

priority refill 只补到 min,降低应用关键路径延迟;delayed refill 补到 max。fork 前统一停止 ART daemon;父 Zygote 完成批量 fork 后再 postForkCommon()。session fd 列表传给 native child 关闭,避免 USAP 继承其他启动连接。

3. forkUsap父子分流 ​

源码文件:frameworks/base/core/java/com/android/internal/os/Zygote.java

java
FileDescriptor[] pipeFDs = Os.pipe2(O_CLOEXEC);
FileDescriptor readFD = pipeFDs[0];
FileDescriptor writeFD = pipeFDs[1];

int pid = nativeForkApp(
        readFD.getInt$(), writeFD.getInt$(),
        sessionSocketRawFDs,
        false /*argsKnown*/, isPriorityFork);

if (pid == 0) {
    IoUtils.closeQuietly(readFD);
    return childMain(null, usapPoolSocket, writeFD);
} else if (pid == -1) {
    return null;
} else {
    IoUtils.closeQuietly(writeFD);
    nativeAddUsapTableEntry(pid, readFD.getInt$());
    return null;
}

每个 USAP 都有 reporting pipe。父进程把 pid/readFD 加入 native table,子进程保留 writeFD 并进入 childMain() 等待 specialization。fork 失败返回 null,调用方仍继续处理剩余补池循环,日志/池数量用于发现缺口。

4. USAP等待命令 ​

java
Process.setArgV0(
        Process.is64Bit() ? "usap64" : "usap32");
boostUsapPriority();

while (true) {
    sessionSocket = usapPoolSocket.accept();
    blockSigTerm();
    Credentials peer = sessionSocket.getPeerCredentials();
    ZygoteCommandBuffer buffer =
            new ZygoteCommandBuffer(sessionSocket);
    args = ZygoteArguments.getInstance(buffer);
    applyUidSecurityPolicy(args, peer);
    validateUsapCommand(args);
    break;
}

空白进程改名为 usap32/64,并提高 accept 阶段优先级。收到请求后先阻塞 SIGTERM,避免父 Zygote清池时杀掉正在 specialization 的 USAP;再读取 peer credentials、解析参数和执行 USAP 专用命令白名单。失败会关闭当前 session、解除 SIGTERM 后继续 accept。

5. PID双通道 ​

java
usapOutputStream.writeInt(pid);

ByteArrayOutputStream buffer =
        new ByteArrayOutputStream(
                Zygote.USAP_MANAGEMENT_MESSAGE_BYTES);
DataOutputStream output =
        new DataOutputStream(buffer);
output.writeLong(pid);
output.flush();
Os.write(writePipe, buffer.toByteArray(),
        0, buffer.size());

session socket 的 int pid 返回给 ZygoteProcess.attemptUsapSendArgsAndGetResult();reporting pipe 的 long pid 返回父 ZygoteServer,用于从 native USAP table 移除该进程并触发 refill。两个通道服务不同消费者,不能只读一个就认为池记账完成。

6. 进程特化 ​

java
specializeAppProcess(
        args.mUid, args.mGid, args.mGids,
        args.mRuntimeFlags, rlimits,
        args.mMountExternal, args.mSeInfo,
        args.mNiceName, args.mStartChildZygote,
        args.mInstructionSet, args.mAppDataDir,
        args.mIsTopApp, args.mPkgDataInfoList,
        args.mAllowlistedDataInfoList,
        args.mBindMountAppDataDirs,
        args.mBindMountAppStorageDirs,
        args.mBindMountSyspropOverrides);

Zygote.setAppProcessName(args, TAG);
return ZygoteInit.zygoteInit(
        args.mTargetSdkVersion,
        args.mDisabledCompatChanges,
        args.mEnabledCompatChanges,
        args.mRemainingArgs, null);

真正的 uid/gid、SELinux、mount、runtime flags 在这里生效;随后设置应用进程名并进入 RuntimeInit/ActivityThread。finally 解除 SIGTERM,恢复普通进程信号行为。

7. refill状态机 ​

USAP event fd 报告批量移除数量,reporting pipe 报告 specialization pid。ZygoteServer 消费后读取 getUsapPoolCount():低于 min 立即补到 min;低于 max 且缺口达到 threshold 时设置延迟 timestamp;poll timeout 到期后补到 max。状态变化和 refill 都由父 Zygote执行,不由 USAP 子进程自我复制。

8. 失败与回退 ​

  • USAP 不支持/未启用/policy 不允许:客户端直接走普通 Zygote;
  • USAP socket I/O 失败:ZygoteProcess 记录后回退普通 Zygote;
  • 命令不在 USAP 白名单:USAP 关闭 session 并继续等待;
  • reporting pipe 写失败:子进程终止,父池最终通过 signal/event 清理;
  • specialization 失败:客户端得到负 pid/异常;
  • pool policy 无效:恢复默认 min/max/threshold;
  • 清池期间 specialization:SIGTERM 屏蔽防止竞态,完成后恢复。

9. 导航与后续 ​

bash
# ZygoteServer 的策略、补池和 event/report pipe 消费。
rg -n "USAP_POOL_SIZE|fetchUsapPoolPolicyProps|fillUsapPool|getUsapPoolCount|IMMEDIATE|DELAYED|usapPoolEventFDIndex" \
  frameworks/base/core/java/com/android/internal/os/ZygoteServer.java

# USAP fork、native table、accept、PID 回报和 specialization。
rg -n "forkUsap|nativeAddUsapTableEntry|childMain|boostUsapPriority|blockSigTerm|writeLong|specializeAppProcess|removeUsapTableEntry" \
  frameworks/base/core/java/com/android/internal/os/Zygote.java

# 客户端 eligibility、USAP 请求和普通 Zygote 回退。
rg -n "shouldAttemptUsapLaunch|attemptUsapSendArgsAndGetResult|attemptZygoteSendArgsAndGetResult" \
  frameworks/base/core/java/android/os/ZygoteProcess.java

下一篇将分析 Linux fork/COW 与 Zygote 预加载之间的真实内存关系;不会重复 USAP 池协议和 refill。