Android-Fork 机制详解

深入理解 Android Fork 机制:从 Linux 系统调用到 Zygote 进程模型

fork() 是 Linux 内核提供的系统调用,Android 基于它构建了整个应用进程模型(Zygote)。 本文从底层到上层分层讲清楚:fork 本身的语义 → Android 中的调用链 → 关键函数源码分析 → 可运行的示例代码 → 常见踩坑。

适用读者:Android 系统开发、NDK 开发、正在学习 Linux 进程模型的工程师。


一、fork() 系统调用基础

1.1 基本语义

fork() 的作用是:创建一个与当前进程几乎完全相同的子进程

arduino 复制代码
#include <unistd.h>
​
pid_t pid = fork();
​
if (pid < 0) {
    // 失败
} else if (pid == 0) {
    // 子进程:这里执行的代码只在子进程中运行
    execl("/system/bin/sh", "sh", NULL);
} else {
    // 父进程:pid 是子进程的 PID
    int status;
    waitpid(pid, &status, 0);
}

关键点:

特性 说明
返回值 调用一次、返回两次:父进程返回子进程 PID,子进程返回 0,失败返回 -1
地址空间 子进程获得父进程地址空间的副本(虚拟地址相同,物理页通过 COW 共享)
继承的内容 文件描述符(共享文件偏移量)、信号处理函数、环境变量、堆/栈/代码段副本
不继承的内容 锁所在的线程没了(只复制调用线程)、PID 不同、挂起信号被清空

1.2 写时复制(Copy-On-Write)

fork 时内核并不真正复制物理内存 ,而是把父进程的页表项标记为只读,父子进程共享物理页。只有当任意一方写入时,才触发缺页异常,内核这时才复制那一页。

makefile 复制代码
fork 后:
父进程页表 ──┐
             ├──> 同一批物理页 (只读)
子进程页表 ──┘
​
父/子任一方写某页:
写方 ──> 新分配的物理页 (可写)   ← COW 缺页处理
另一方 ──> 原物理页

这正是 Zygote 模式高效的核心:所有 app 进程从 Zygote fork 出来,共享 Zygote 中预加载的类和资源,只要没人写这些页,物理内存就只有一份。

再从硬件视角看一眼 fork 的成本:内核要做的只是复制页表 (而非物理内存)、给物理页记一份引用计数,并使两个进程的 TLB(页表缓存)失效 ------此后首次访问虚拟地址时重新走页表填充 TLB。因此 fork 本身是微秒级操作,子进程在只读阶段几乎不消耗内存带宽;真正昂贵的类验证、资源解析早已由 Zygote 提前完成,这才是"秒开"的本质。

1.3 fork / vfork / clone 的关系

Linux 内核里只有 clone()(内核函数 kernel_clone(),旧版叫 _do_fork)这一个底座:

  • fork() = clone(SIGCHLD, 0) ------ 全复制
  • vfork() = clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0) ------ 共享地址空间且挂起父进程(为 exec 优化,现代已被淘汰)
  • pthread_create() = clone(CLONE_VM | CLONE_FILES | CLONE_THREAD | ...) ------ 共享地址空间

Android 的 bionic libc 中 fork() 就是走 SYS_clone 系统调用实现的(bionic/libc/bionic/fork.cpp 中还负责处理 pthread_atfork 注册的回调)。


二、Android 中的 Fork:Zygote 进程模型

2.1 整体架构

Android 中 fork() 最大的用户就是 Zygote(受精卵进程) 。所有应用进程和 system_server 都是从 Zygote fork 出来的:

perl 复制代码
init (PID 1)
 └─ app_process (Zygote, 通过 init.rc 启动)
     ├─ fork → system_server        ← Zygote.forkSystemServer()
     ├─ fork → com.android.settings ← Zygote.forkAndSpecialize()
     ├─ fork → com.tencent.mm       ← 同上
     └─ fork → ... 所有 app 进程

2.2 为什么用 fork 而不是 exec?

传统 Linux 启动程序用 fork() + exec(),exec 会替换整个地址空间。Android 不这么做,因为:

  1. 免重复加载 :Zygote 启动时 preload() 了数千个核心类(framework.jar)、系统资源(drawables、字符串等),fork 出的子进程天然拥有这些已加载好的类,JVM/ART 运行时直接就绪,不需要每个 app 都重新启动一个虚拟机。
  2. 启动速度:app 冷启动只需几毫秒的 fork + 少量初始化,而不是数百毫秒的 JVM 启动 + 类加载。
  3. 内存共享 :预加载的类通过 COW 被所有 app 共享同一份物理内存,大幅节省 RAM。

2.3 完整启动流程(关键函数链)

scss 复制代码
init (init.rc: service zygote /system/bin/app_process)
 └─ AppRuntime/AppMain.main()                      // frameworks/base/cmds/app_process/app_main.cpp
     └─ AndroidRuntime.start("com.android.internal.os.ZygoteInit")
         └─ ZygoteInit.main()                      // Java 世界入口
             ├─ preload()                          // 预加载类和资源
             ├─ ZygoteServer()                     // 创建 /dev/socket/zygote (LocalServerSocket)
             ├─ forkSystemServer()                 // ① fork 出 system_server
             └─ runServerLoop()                    // ② 循环等待 AMS 请求
                 └─ ZygoteConnection.processOneCommand()
                     ├─ readArgumentList()         // 读取启动参数 (uid, gid, seinfo, niceName...)
                     ├─ Zygote.forkAndSpecialize() // ★ 核心 fork
                     │    ├─ nativeForkAndSpecialize (JNI)
                     │    │    └─ ForkCommon() → fork()   // 真正的系统调用
                     │    │    └─ SpecializeCommon()      // uid/gid 设置、SELinux、capabilities
                     │    └─ 返回后父子进程分道扬镳
                     ├─ 子进程: handleChildProc() → RuntimeInit.applicationInit()
                     │         → 找到 ActivityThread.main() → app 开始运行
                     └─ 父进程: handleParentProc() → 继续监听 socket

2.4 从 app_process 到 Java 世界:启动接力细节

Zygote 本质是磁盘上的可执行文件 /system/bin/app_process,入口在 C++ 层(frameworks/base/cmds/app_process/app_main.cpp),由它完成"C++ → Java"的接力:

arduino 复制代码
// app_main.cpp(简化)
int main(int argc, char* const argv[]) {
    AppRuntime runtime(argv[0], computeArgBlockSize);
    // 解析 --zygote / --start-system-server 等参数
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    }
    ...
}

AppRuntime 继承自 AndroidRuntimestart() 只做两件事:

  1. 通过 JNI 调用 JNI_CreateJavaVM() 启动 ART 虚拟机
  2. 注册好 JNI 方法后,FindClass + CallStaticVoidMethod 调用 ZygoteInit.main()从此控制权进入 Java 世界

另外,ZygoteInit.main() 入口处还会调用 ZygoteHooks.startZygoteNoChildCreation(),注册 Zygote 阶段的运行时钩子(Dalvik 时代的旧实现里还会在此挂一个 SIGUSR1 → 触发 GC 的信号处理)。

2.5 preload() 到底预加载了什么

ZygoteInit.preload() 一次性完成四件事,全部成为后续所有 app 的"遗产":

方法 预加载内容 收益
preloadClasses() 数千个常用类(由 /system/etc/preloaded-classes 列表定义:HashMap、ActivityThread、ContextImpl......) 所有 app 免去类加载/验证耗时
preloadResources() framework-res.apk 的常用 drawable、字符串、布局 资源解析结果常驻内存直接共享
preloadGraphics() OpenGL/EGL 上下文 初始化 GPU 驱动/着色器,app 首帧不用冷启动图形栈
preloadTextResources() 文本整形/断字典等资源(HarfBuzz 相关) 预热文本渲染路径

三、关键函数源码解析

以下代码基于 AOSP 主线,做了删减注释。

3.1 ZygoteInit.main() --- Zygote 入口

ini 复制代码
// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String[] argv) {
    ZygoteServer zygoteServer = null;
    ...
    // 预加载:类 + 资源 + OpenGL/图形驱动,这是所有 app 共享的"遗产"
    preload(bootTimingsTraceLog);
​
    // fork 出 system_server(进程名 system_server,uid=1000)
    if (startSystemServer) {
        Runnable r = forkSystemServer(...);
        if (r != null) {
            r.run();     // 注意:只有子进程才会拿到非 null 返回并执行
            return;
        }
    }
​
    // 父进程继续:监听 zygote socket
    zygoteServer = new ZygoteServer(isPrimaryZygote);
    Runnable caller = zygoteServer.runServerLoop();  // 死循环
    ...
}

fork 的"一次调用、两次返回"在这里被 Java 层封装成一个很优雅的模式:子进程返回一个待执行的 Runnable,父进程返回 null ------用 r != null 区分父子,避免了在 Java 里直接写 C 风格的 if (pid == 0)

3.2 Zygote.forkSystemServer() --- fork system_server

scss 复制代码
// frameworks/base/core/java/com/android/internal/os/Zygote.java
static Runnable forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, ...) {
    ...
    int pid = nativeForkSystemServer(uid, gid, gids, runtimeFlags, rlimits,
            permittedCapabilities, effectiveCapabilities);
    if (pid == 0) {
        // ---- 子进程 ----
        // 嵌套 try:如果 system_server 出问题,直接杀掉整个 zygote,
        // 让 init 重启整个框架
        if (hasSecondZygote()) {
            waitForSecondaryZygote(socketName);
        }
        zygoteServer.closeServerSocket();   // 子进程不需要监听 socket,必须关闭!
        return handleSystemServerProcess(parsedArgs);  // 执行 SystemServer.main
    } else {
        // ---- 父进程 (Zygote) ----
        return null;
    }
}

3.3 Zygote.forkAndSpecialize() --- fork 普通应用进程(最核心)

arduino 复制代码
// frameworks/base/core/java/com/android/internal/os/Zygote.java
static Runnable forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
        int[][] rlimits, int mountExternal, String seInfo, String niceName,
        int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote,
        String instructionSet, String appDataDir) {
    VM_HOOKS.preFork();      // ① 停止守护线程、触发 GC(保证单线程状态下 fork)
​
    int pid = nativeForkAndSpecialize(uid, gid, gids, runtimeFlags, rlimits,
            mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore,
            startChildZygote, instructionSet, appDataDir);
​
    if (pid == 0) {
        // ---- 子进程 ----
        VM_HOOKS.postForkChild(...);  // 恢复 JDWP 等
        return ...;                   // 返回子进程真正入口
    } else {
        // ---- 父进程 ----
        VM_HOOKS.postForkCommon();    // 恢复 Zygote 自己的守护线程
        return null;
    }
}

三个 VM Hook(由 ART 的 Daemons 实现)非常关键:

  • preFork()停掉所有后台守护线程 (HeapTaskDaemon、FinalizerDaemon 等),并做一次 GC。必须在单线程状态下 fork(原因见第五节)。
  • postForkChild():在子进程中重新启动守护线程。
  • postForkCommon():在父进程中重新启动守护线程。

3.4 Native 层 ForkCommon() --- 真正调用 fork() 的地方

c 复制代码
// frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
                        const std::vector<int>& fds_to_close) {
    // 设置 SIGCHLD 处理:让子进程退出后自动 reap,避免僵尸进程占用 PID
    SetSignalHandlers();
​
    // 关闭传入的 fd,防止子进程持有不该持有的描述符
    for (int fd : fds_to_close) close(fd);
​
    pid_t pid = fork();     // ★★ Linux 系统调用,一切发生在这里
​
    if (pid == 0) {
        // ---- 子进程 ----
        // 屏蔽所有信号直到 specialize 完成,
        // 防止中途被杀导致权限状态不一致
        gIsChild = true;
    } else if (pid > 0) {
        gIsChild = false;
    }
    return pid;
}

3.5 SpecializeCommon() --- 子进程"特殊化"

fork 出来的子进程一开始仍是 zygote 身份(uid 0、root 权限、system 的 SELinux 域),必须立刻"降级"成目标 app 身份,这个过程叫 specialize

scss 复制代码
// frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, int* gids, ...) {
​
    // 1. 挂载命名空间:app 的私有存储 (sdcard/external)
    MountEmulatedStorage(uid, mount_external, use_native_bridge, &fail_fn);
​
    // 2. 丢弃所有 privileges(capabilities),只保留参数中显式允许的
    DropCapabilitiesBoundingSet(fail_fn);
​
    // 3. SELinux 上下文:从 "zygote" 域切换到 "app domain"
    //    SELinux 规则只允许 zygote 域 fork,且一次 fork 内必须完成切换
    ...
​
    // 4. 设置进程名(nice name,如 "com.android.settings")
    SetProcessName(...);
​
    // 5. 恢复 SIGCHLD 为默认、解除信号屏蔽
    UnsetChldSignalHandlers();
​
    // 6. 切换真实/有效 uid、gid 和补充组
    //   setresgid / setresuid ------ 一旦生效,进程永久失去 root
    ...
}

setresuid() 是不可逆的降权点------之后这个进程再也无法回到 root。这也解释了为什么 specialize 必须在 fork 后一气呵成,中间用信号屏蔽保护。

3.6 handleChildProc() --- 子进程进入 Java 世界

scss 复制代码
// frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java
private Runnable handleChildProc(Arguments parsedArgs, ...) {
    // 关闭从 zygote 继承来的 socket fd(子进程不该监听)
    closeSocket();
​
    // 最终入口:RuntimeInit.applicationInit()
    // -> 通过反射找到 android.app.ActivityThread 的 main() 并包装成 Runnable 返回
    return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
            parsedArgs.mRemainingArgs, cl, null);
}

返回的 Runnable 一路 return 回 ZygoteServer.runServerLoop() 的调用方并 run(),此时调用栈已经完全干净(因为是通过返回值层层上抛执行的,而不是在中间直接调用),等价于一个全新启动的进程。

3.7 父进程如何收到请求:AMS → Zygote socket

scss 复制代码
ActivityManagerService.startProcess()
 └─ Process.start()
     └─ ZygoteProcess.startViaZygote()
         └─ zygoteSendArgsAndGetResult()
             └─ 写入 LocalSocket → /dev/socket/zygote
Zygote 端 runServerLoop() 中 accept() → processOneCommand() → forkAndSpecialize()

AMS 传给 Zygote 的参数列表包括:--uid, --gid, --gids, --runtime-flags, --seinfo, --nice-name, --target-sdk-version, 以及入口类名 android.app.ActivityThread

通信协议本身很朴素:zygote 的监听 socket 由 init 解析 init.rc 中的 socket zygote stream 660 root system 创建于 /dev/socket/zygoteLocalSocket/LocalServerSocket 构建在 Linux AF_UNIX socket 之上,zygote 用的是文件系统 socket,而非抽象命名空间)。AMS 连接后按行写入参数 ,Zygote 端 readArgumentList() 解析、fork,再把新进程的 PID(或错误码)写回 socket 作为响应------一问一答,无锁、无共享内存。

3.8 USAP 优化(Android 10+)

fork + specialize 本身有可观的延迟(页表复制、SELinux 切换)。Android 10 引入 USAP(Unspecialized App Process Pool) :Zygote 空闲时预先 fork 出一批"半成品"进程(fork 完但不 specialize),AMS 请求到来时直接取一个现成的 USAP 做 specialize,把 fork 的耗时从关键路径上拿掉了。

3.9 Zygote 自己的生命周期:runSelectLoop 与"自我净化"

Zygote 启动完成后就进入 ZygoteServer.runSelectLoop()(新版本外层包装为 runServerLoop())的死循环,永不退出(除非整个系统重启)。这个循环本身就是一个标准的 poll 多路复用服务器

  • 维护一个 fd 列表:/dev/socket/zygote 监听 fd + 每个已接受连接的 fd + USAP 池状态管道的 fd;
  • OS.poll() 等待任意 fd 就绪:新连接就 accept() 加入列表;旧连接就交给 ZygoteConnection.processCommand() 处理(内部即前文的 forkAndSpecialize);
  • 单次请求异常只记日志继续循环,不会杀死孵化器。

另一个容易被忽略的细节是:Zygote 会给自己做 GC 。每次孵化都会让 Zygote 堆里的一些共享对象被"污染"(如触发过 finalize 的预加载对象),所以 Zygote 会周期性地执行 gcIfNecessary()(内部走 gcAndFinalize()System.gc() + runFinalization())把这些"脏"对象清走,保持堆的纯净------堆越纯净,下一次 fork 的 COW 共享比例就越高 。这与 preFork() 里的那次 GC 是同一个思想的两侧:fork 前净化自己,fork 后持续净化。


四、动手实践:两个可编译运行的示例

fork() 是 POSIX 接口,Windows 上没有,要在 Linux / WSL / Mac / Android 设备上运行。

4.1 示例 1:基础版

演示 fork 最核心的语义:一次调用两次返回、父子分支、COW 内存隔离、waitpid 回收子进程

arduino 复制代码
// fork_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
​
int g_count = 100;   // 全局变量,用来演示写时复制(COW)
​
void do_fork_demo(void)
{
    pid_t pid = fork();                // ★ 一次调用,两次返回
​
    if (pid < 0) {
        // fork 失败(比如超过进程数上限)
        perror("fork failed");
        exit(1);
​
    } else if (pid == 0) {
        // ===== 子进程分支:fork 返回 0 =====
        printf("[child ] pid=%d, ppid=%d, g_count=%d\n",
               getpid(), getppid(), g_count);
​
        g_count = 999;                 // 写内存 -> 触发 COW,父进程看不到
        printf("[child ] after write g_count=%d\n", g_count);
​
        sleep(1);                      // 模拟子进程干活
        exit(42);                      // 子进程退出码 42
​
    } else {
        // ===== 父进程分支:fork 返回子进程 PID =====
        printf("[parent] created child pid=%d, g_count=%d\n", pid, g_count);
​
        int status = 0;
        waitpid(pid, &status, 0);      // 等待并回收子进程(否则变僵尸进程)
​
        if (WIFEXITED(status)) {
            printf("[parent] child exited with code=%d\n", WEXITSTATUS(status));
        }
        printf("[parent] my g_count is still %d (父子内存已隔离)\n", g_count);
    }
}
​
int main(void)
{
    printf("before fork, pid=%d\n", getpid());
    do_fork_demo();
    return 0;
}

运行结果(顺序可能交错):

ini 复制代码
before fork, pid=12345
[parent] created child pid=12346, g_count=100
[child ] pid=12346, ppid=12345, g_count=100
[child ] after write g_count=999
[parent] child exited with code=42
[parent] my g_count is still 100 (父子内存已隔离)

4.2 示例 2:Zygote 风格(fork 循环孵化)

模拟 Zygote 模式:父进程像 Zygote 一样常驻循环,不断 fork 出子进程,子进程 fork 后立刻"specialize"(改进程名)再执行任务。

c 复制代码
// mini_zygote.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <sys/prctl.h>
​
static void sigchld_handler(int sig)
{
    (void)sig;  /* 参数是信号处理函数原型的要求,此处不使用 */
​
    // 异步回收所有已退出的子进程,避免僵尸进程占用 PID
    while (waitpid(-1, NULL, WNOHANG) > 0) { }
}
​
/* 模拟 ZygoteConnection.forkAndSpecialize:fork 出一个子进程并"特殊化" */
static pid_t fork_and_specialize(const char *nice_name, int exit_code)
{
    pid_t pid = fork();
​
    if (pid == 0) {
        /* ---- 子进程:fork 后立刻 specialize ---- */
        // 1. 改进程名(对应 Zygote 设置 nice_name,如 "com.android.settings")
        char comm[16];
        snprintf(comm, sizeof(comm), "%s", nice_name);
        prctl(PR_SET_NAME, comm, 0, 0, 0);
​
        // 2. 降权:真实 Android 里这里是 setresuid/setresgid + SELinux 切域
        printf("  [%s] spawned, pid=%d, doing work...\n", nice_name, getpid());
​
        sleep(2);
        exit(exit_code);
​
    } else if (pid > 0) {
        printf("[zygote] forked '%s' -> pid %d\n", nice_name, pid);
        return pid;
    }
    perror("fork");
    return -1;
}
​
int main(void)
{
    // Zygote 也做了同样的事:注册 SIGCHLD 处理器
    //(见 ForkCommon -> SetSignalHandlers)
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigchld_handler;
    sa.sa_flags   = SA_RESTART | SA_NOCLDSTOP;
    sigaction(SIGCHLD, &sa, NULL);
​
    const char *apps[] = { "app:browser", "app:music", "app:camera" };
​
    printf("[zygote] running, pid=%d\n", getpid());
    for (int i = 0; i < 3; i++) {
        fork_and_specialize(apps[i], i);
        sleep(1);   // 模拟 AMS 陆续发来的启动请求
    }
​
    printf("[zygote] all children spawned, keep listening...\n");
    sleep(4);       // 父进程常驻,就像 Zygote 永远监听 socket
    printf("[zygote] exit\n");
    return 0;
}

运行时另开一个终端执行 ps,会看到父进程旁边挂着三个改名后的子进程------这正是 Zygote 和所有 app 进程在设备上呈现的样子。

ini 复制代码
console:/ # epoll_server                                                   
[zygote] running, pid=19465
[zygote] forked 'app:browser' -> pid 19466
  [app:browser] spawned, pid=19466, doing work...
[zygote] forked 'app:music' -> pid 19472
  [app:music] spawned, pid=19472, doing work...
[zygote] forked 'app:camera' -> pid 19482
  [app:camera] spawned, pid=19482, doing work...
[zygote] all children spawned, keep listening...
[zygote] exit
​
console:/ # ps -A | grep epoll_
root         19465 14172    8730796   3292 hrtimer_nanosleep   0 S epoll_server
root         19472 19465    8730796    536 hrtimer_nanosleep   0 S epoll_server
root         19482 19465    8730796    536 hrtimer_nanosleep   0 S epoll_server

4.3 如何编译运行

在 Linux / WSL 上:

bash 复制代码
gcc fork_demo.c -o fork_demo && ./fork_demo
gcc mini_zygote.c -o mini_zygote && ./mini_zygote

在真实 Android 设备上(用 NDK 交叉编译,以 arm64 为例):

bash 复制代码
# 用 NDK 自带的 clang 编译
$NDK/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android33-clang \
    fork_demo.c -o fork_demo
​
adb push fork_demo /data/local/tmp/
adb shell chmod +x /data/local/tmp/fork_demo
adb shell /data/local/tmp/fork_demo

五、踩坑记录与注意事项

5.1 实战踩坑:-Werror,-Wunused-parameter

在 AOSP/NDK 环境编译上面的信号处理函数时,你大概率会遇到这个报错:

sql 复制代码
epoll_server.c:26:33: error: unused parameter 'sig' [-Werror,-Wunused-parameter]
static void sigchld_handler(int sig)
                                ^
1 error generated.
06:29:34 ninja failed with: exit status 1

原因 :信号处理函数的签名必须int sig 参数(POSIX 规定的原型),但函数体里确实用不到它。编译环境开了 -Werror -Wunused-parameter,"未使用的参数"就被升级成了编译错误。

修复 :在函数体第一行加 (void)sig;,显式告诉编译器"我知道这个参数没用到":

arduino 复制代码
static void sigchld_handler(int sig)
{
    (void)sig;  /* 参数是信号处理函数原型的要求,此处不使用 */
​
    while (waitpid(-1, NULL, WNOHANG) > 0) { }
}

这是 Linux 内核代码、AOSP 源码里处理同类问题的标准写法,任何编译器下都有效。备选方案:

  1. 只声明类型不命名(static void sigchld_handler(int),clang 接受但不完全规范);
  2. Android.bpcflags"-Wno-unused-parameter"(不推荐,会掩盖其他真正的问题)。

另外建议 sa_flags 写上 SA_RESTART | SA_NOCLDSTOP:前者让被信号打断的阻塞调用自动重启(避免 epoll_wait/read 返回 EINTR),后者避免子进程停止/继续时也触发 handler。

5.2 为什么 fork 前必须停掉所有线程?

fork() 只复制调用线程 ,其他线程直接消失,但它们持有的锁不会释放。如果某个后台线程恰好在 fork 瞬间持有 malloc/堆锁,子进程中这把锁永远处于锁定状态------子进程再调用 malloc 就死锁。所以:

  • POSIX 的做法是 pthread_atfork() 注册处理函数;
  • Android/ART 的做法是 VM_HOOKS.preFork() 先停守护线程,保证 fork 发生在单线程上下文中。

5.3 文件描述符的继承

子进程会继承 Zygote 打开的所有 fd(除了带 O_CLOEXEC 标记的)。这就是 fdsToClose / fdsToIgnore 参数的由来------每个子进程必须关闭 zygote 的监听 socket 等敏感 fd,否则 app 就能操纵 Zygote 本身,这是严重的安全漏洞。

5.4 fork 与 JNI/ART 的交互

  • COW 意味着子进程刚 fork 完时和 Zygote 内存几乎零成本,但任何写入(类去偏置、对象重映射)都会造成私有页拷贝------这是 app 首次启动内存上涨的原因之一。
  • Zygote 在 fork 前会对"带有 finalize 的预加载对象"做特殊处理,避免所有子进程被共享可变对象的写放大拖累。

5.5 调试角度

  • 看一个进程从哪来:/proc/<pid>/statps -o PPID;所有 app 的父进程是 zygote。
  • Zygote fork 计数/失败:logcat 中的 Zygote tag。
  • am start -W 观察 WaitTime,fork/specialize 耗时在 system_server 的 atrace am_proc_start 里。

六、一图总结调用链

scss 复制代码
[Java]  ZygoteInit.main
          └ Zygote.forkAndSpecialize ── VM_HOOKS.preFork()(停线程+GC)
[JNI]     └ nativeForkAndSpecialize
            ├ ForkCommon() ────────── fork()  ←------ Linux syscall(COW)
            └ SpecializeCommon() ─── setresuid/setresgid、SELinux 切域、
                                      drop capabilities、进程名
[Java]    子进程: handleChildProc → RuntimeInit.applicationInit
                    └ ActivityThread.main()  ← app 诞生
          父进程: VM_HOOKS.postForkCommon() → 继续监听 socket

一句话总结 :Android 的 fork 机制 = Linux fork() 的 COW 语义 + Zygote 预加载的类/资源遗产 + fork 后立即 specialize 降权------用极低的成本批量孵化安全隔离的应用进程。核心函数就是 Zygote.forkAndSpecialize()(及其 native 实现 ForkCommon/SpecializeCommon)和 Zygote.forkSystemServer()


参考资料:

  • AOSP 源码:frameworks/base/core/java/com/android/internal/os/Zygote.javaZygoteConnection.javaframeworks/base/core/jni/com_android_internal_os_Zygote.cppbionic/libc/bionic/fork.cpp
相关推荐
JMchen1 小时前
第 12 篇|项目整合与打包发布 —— 从 Demo 到可安装 APK 的完整收官指南
kotlin·android studio
曹牧1 小时前
Spring:HttpMessageConverter
java
许彰午1 小时前
47-MetaGrid元数据表格
java·低代码·架构
wang_shu_mo_ran1 小时前
Spring MVC的常用注解和用法(一)
java·spring·mvc
Android打工仔1 小时前
不要在 Data 层随意把 Cold Flow 转换成 Hot Flow
android·架构·kotlin
晴空蓝天1 小时前
Spring Boot 3.5 脚手架里的 JWT + Redis 双轨会话,双端 token 隔离我是这么设计的
java·spring boot·redis
IT枫斗者枫哥1 小时前
MyBatis 列表查询优化:一页20条数据,21次SQL改成2次
java
君顾12 小时前
本地电竞服务交易系统架构设计与实战:从同城服务撮合到订单履约
java·开发语言·电竞
小刘在重生~2 小时前
十六(3)、《集合扩充》CopyOnWriteArrayList 超详细解析(线程安全集合)
java·笔记·面试·职场和发展