Skip to content

ZygoteServer循环

深入 ZygoteServer.runSelectLoop 的 pollfd 索引、连接处理、USAP 管理 fd、超时 refill 与 fork 子进程返回。

基于android-17.0.0_r1
AndroidZygoteUSAP源码阅读

ZygoteServer循环 ​

runSelectLoop() 是 Zygote 常驻父进程的消费者循环。它同时管理主 Zygote server socket、已建立的 session socket、USAP pool event fd 和 USAP reporting pipe;同一个 pollfd 数组中的索引决定事件属于哪一种 owner。本文只展开循环本身,不重复 SS010 的 ABI 选择和 SS005 的 init socket 包装。

1. 两张并行表 ​

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

java
Runnable runSelectLoop(String abiList) {
    ArrayList<FileDescriptor> socketFDs = new ArrayList<>();
    ArrayList<ZygoteConnection> peers = new ArrayList<>();

    socketFDs.add(mZygoteSocket.getFileDescriptor());
    peers.add(null); // index 0 is the server socket.
    mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;

socketFDs 与 peers 使用同一索引:索引 0 是主监听 socket,peers[1...] 是由主 socket accept 出来的 session connection。这个并行关系让 pollIndex 可以直接取出对应 ZygoteConnection;删除连接时必须同步删除两张表,否则下一轮事件会把 fd 映射到错误 peer。

2. pollfd构造 ​

java
if (mUsapPoolEnabled) {
    usapPipeFDs = Zygote.getUsapPipeFDs();
    pollFDs = new StructPollfd[
            socketFDs.size() + 1 + usapPipeFDs.length];
} else {
    pollFDs = new StructPollfd[socketFDs.size()];
}

int pollIndex = 0;
for (FileDescriptor socketFD : socketFDs) {
    pollFDs[pollIndex] = new StructPollfd();
    pollFDs[pollIndex].fd = socketFD;
    pollFDs[pollIndex].events = (short) POLLIN;
    ++pollIndex;
}

final int usapPoolEventFDIndex = pollIndex;
if (mUsapPoolEnabled) {
    pollFDs[pollIndex++].fd = mUsapPoolEventFD;
    for (int usapPipeFD : usapPipeFDs) {
        FileDescriptor managedFd = new FileDescriptor();
        managedFd.setInt$(usapPipeFD);
        pollFDs[pollIndex++].fd = managedFd;
    }
}

构造顺序是:主 socket/session socket → USAP event fd → USAP reporting pipes。源码注释要求 USAP event/pipe 先于 session/server 事件被处理,以保持 USAP accounting 准确;实现通过记录 usapPoolEventFDIndex 和倒序处理 pollIndex 实现分类,而不是依赖 socket 名称。

3. 超时与补池 ​

java
if (mUsapPoolRefillTriggerTimestamp == INVALID_TIMESTAMP) {
    pollTimeoutMs = -1;
} else {
    long elapsed = System.currentTimeMillis()
            - mUsapPoolRefillTriggerTimestamp;
    if (elapsed >= mUsapPoolRefillDelayMs) {
        pollTimeoutMs = 0;
        mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
        mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED;
    } else {
        pollTimeoutMs = (int)
                (mUsapPoolRefillDelayMs - elapsed);
    }
}

pollReturnValue = Os.poll(pollFDs, pollTimeoutMs);

没有延迟 refill 时 poll(-1) 无限等待;有延迟时间戳时,poll 只等待到 refill deadline。deadline 到期后先设置 DELAYED,再用非阻塞 poll 检查当前 ready fd,避免 refill 与已经到达的连接事件产生错误顺序。系统时钟回拨时源码重新开始倒计时,因为 currentTimeMillis() 不是单调时钟。

4. Socket事件 ​

java
while (--pollIndex >= 0) {
    if ((pollFDs[pollIndex].revents & POLLIN) == 0) continue;

    if (pollIndex == 0) {
        ZygoteConnection newPeer = acceptCommandPeer(abiList);
        peers.add(newPeer);
        socketFDs.add(newPeer.getFileDescriptor());
    } else if (pollIndex < usapPoolEventFDIndex) {
        ZygoteConnection connection = peers.get(pollIndex);
        boolean multipleForksOK = !isUsapPoolEnabled()
                && ZygoteHooks.isIndefiniteThreadSuspensionSafe();
        Runnable command = connection.processCommand(
                this, multipleForksOK);

主 socket 可读表示有新客户端,acceptCommandPeer() 创建新 peer 并把 fd 追加到两张并行表。session fd 可读则进入 processCommand(),一次消费一个 spawn request;multipleForksOK 只有在 USAP 未启用且 ART 允许无限线程挂起时才打开,不能被解释为普通连接的永久多 fork 权限。

5. 父子返回 ​

java
if (mIsForkChild) {
    if (command == null) {
        throw new IllegalStateException("command == null");
    }
    return command;
} else {
    if (command != null) {
        throw new IllegalStateException("command != null");
    }
    if (connection.isClosedByPeer()) {
        connection.closeSocket();
        peers.remove(pollIndex);
        socketFDs.remove(pollIndex);
    }
}

fork 后的 child 在同一 Java 调用栈中返回 Runnable,由 ZygoteInit.main() 最后执行 caller.run();父 Zygote 不应从 processCommand() 得到 Runnable。父端检测 peer close 后同步关闭 connection 并从两张表移除。异常时父端关闭 peer 让客户端立即感知,子端则记录 post-fork exception 并终止,避免带着半初始化状态继续执行。

6. USAP事件 ​

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

java
long messagePayload;
byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];
int readBytes = Os.read(
        pollFDs[pollIndex].fd, buffer, 0, buffer.length);
if (readBytes != Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {
    Log.e(TAG, "Incomplete read from USAP management FD of size "
            + readBytes);
    continue;
}
messagePayload = new DataInputStream(
        new ByteArrayInputStream(buffer)).readLong();
if (pollIndex > usapPoolEventFDIndex) {
    Zygote.removeUsapTableEntry((int) messagePayload);
}
usapPoolFDRead = true;

event fd 的 payload 是移除数量,reporting pipe 的 payload 是刚 specialization 的 USAP PID。两者都必须读取固定字节数;短读只记录错误并跳过本轮。只有 reporting pipe(索引大于 event fd 索引)才调用 removeUsapTableEntry(pid)。

7. refill决策 ​

java
if (usapPoolFDRead) {
    int usapPoolCount = Zygote.getUsapPoolCount();
    if (usapPoolCount < mUsapPoolSizeMin) {
        mUsapPoolRefillAction = UsapPoolRefillAction.IMMEDIATE;
    } else if (mUsapPoolSizeMax - usapPoolCount
            >= mUsapPoolRefillThreshold) {
        mUsapPoolRefillTriggerTimestamp =
                System.currentTimeMillis();
    }
}

低于 min 立即补,低于 max 且缺口达到 threshold 则延迟补。fillUsapPool() 接收当前 session fd 列表,防止新 USAP 继承不应保留的连接;优先 refill 返回 null 时再安排 delayed refill。USAP 统计是 event/pipe 消费后的状态,不是 poll 返回次数。

8. 失败定位与导航 ​

源码文件:

  • frameworks/base/core/java/com/android/internal/os/ZygoteServer.java
  • frameworks/base/core/java/com/android/internal/os/Zygote.java
bash
# 输入循环实现,输出 fd 索引、poll 超时、连接和异常路径。
rg -n "runSelectLoop|pollFDs|usapPoolEventFDIndex|Os\.poll|acceptCommandPeer|processCommand" \
  frameworks/base/core/java/com/android/internal/os/ZygoteServer.java

# 输入 USAP 消息和池统计,输出 PID 移除、min/threshold 和 refill。
rg -n "USAP_MANAGEMENT_MESSAGE_BYTES|removeUsapTableEntry|getUsapPoolCount|mUsapPoolSizeMin|fillUsapPool" \
  frameworks/base/core/java/com/android/internal/os/ZygoteServer.java \
  frameworks/base/core/java/com/android/internal/os/Zygote.java

看到 Zygote 停止接受请求时,先区分主 socket 无事件、session processCommand() 异常、poll 失败和 USAP 短读;看到 USAP 数量异常,再联读 event fd、reporting pipe、table entry 和 refill timestamp,不能只看 pool count。

下一篇将进入 Zygote 的安全限制与 specialization 参数,展开 SELinux、uid/gid、capability 和 seccomp;不会重复本文的 pollfd 和 USAP refill 算法。