深入理解 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 不这么做,因为:
- 免重复加载 :Zygote 启动时
preload()了数千个核心类(framework.jar)、系统资源(drawables、字符串等),fork 出的子进程天然拥有这些已加载好的类,JVM/ART 运行时直接就绪,不需要每个 app 都重新启动一个虚拟机。 - 启动速度:app 冷启动只需几毫秒的 fork + 少量初始化,而不是数百毫秒的 JVM 启动 + 类加载。
- 内存共享 :预加载的类通过 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 继承自 AndroidRuntime,start() 只做两件事:
- 通过 JNI 调用
JNI_CreateJavaVM()启动 ART 虚拟机; - 注册好 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/zygote(LocalSocket/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 源码里处理同类问题的标准写法,任何编译器下都有效。备选方案:
- 只声明类型不命名(
static void sigchld_handler(int),clang 接受但不完全规范); Android.bp的cflags加"-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>/stat、ps -o PPID;所有 app 的父进程是 zygote。 - Zygote fork 计数/失败:logcat 中的
Zygotetag。 am start -W观察WaitTime,fork/specialize 耗时在 system_server 的 atraceam_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.java、ZygoteConnection.java、frameworks/base/core/jni/com_android_internal_os_Zygote.cpp、bionic/libc/bionic/fork.cpp