打开一个应用时,究竟是谁创建了它的进程?为什么应用进程的父进程通常是 Zygote,而不是负责启动它的 system_server?再往前追,Zygote、SystemServer 和第一个用户空间进程又从哪里来?
本章把两条相接的链路讲完整:系统怎样从 Kernel 进入 Android,应用又怎样从一个启动请求变成能够接收 Binder、处理消息的进程。 先看整体模型、运行一个可观察的程序,再沿 init、app_process、Zygote、SystemServer 和 ActivityThread 阅读源码。最后用启动日志、PID、线程和 Perfetto 把概念对应到真实执行。
4.1 先用整体模型区分启动顺序与进程关系
整体架构:各层模块怎样协作
横向层带沿用 AOSP 软件栈 的模块划分,框内注明运行该模块的进程。可以先找到 ActivityThread:它属于应用框架,运行在 App 进程里;AMS 和 ProcessList 则在 system_server 中协调应用进程创建。

沿棕色线看启动与派生:Kernel 启动 init,init 创建子进程并执行 app_process,后者初始化 ART,再进入 ZygoteInit。需要派生进程时,Zygote 经 JNI 和 Bionic 的 fork 进入内核,创建子任务。
绿色虚线传创建参数和 PID,蓝线传应用报到、绑定配置与完成确认。 前者是 Unix 域 socket;后者从进程内的框架端点进入 libbinder,再经内核中的 Binder Driver 到达对端。HAL 有自己的平台位置,这两条路径不会调用 HAL;Bootloader 与 SDK Emulator 标在软件栈之外。
总时序:从设备启动到应用初始化完成
把设备启动和应用启动放在同一条时间线上:先看 Kernel、init、Zygote 如何把 SystemServer 带起来,再看系统如何为一次组件启动请求创建 App 进程,交付初始化任务并收到完成确认。

图中选的是普通应用即时 fork、等待初始化完成确认开启、Activity 不提前交付的路径。读到并行区时,分别沿父进程和应用子进程看:PID 回执与应用报到没有固定先后;bindApplication 的入队也可以和 attachApplication 返回并行。 确认完成表示系统可以继续交付等待的组件,页面首帧是另一个节点。
下面的启动全景图只概括阶段;[4.7 的时序图](#4.7 的时序图 "#chapter4-7")细看一次应用进程创建,[4.8 的流程图](#4.8 的流程图 "#chapter4-8")展开创建分支,[4.10 的时序图](#4.10 的时序图 "#chapter4-10")细看 Binder 接收线程怎样把初始化交给主线程。
Android 启动不是一路调用 Java main
Kernel 是操作系统内核,负责调度、内存、文件系统和设备等基础能力;Android 的 init、Zygote、system_server 和应用运行在用户空间。它们需要内核提供能力,但内核不会解释 Android 的 Activity 或 Application。
物理设备上,Bootloader 负责选择和准备启动材料,并把执行交给 Kernel。SDK Emulator 则由运行器和虚拟设备环境加载配套 Kernel、ramdisk 与磁盘镜像,不能把它的启动过程当成某款手机的 Bootloader 实现。官方 Bootloader 概览
下图是主要依赖与交接关系,方向遵循实际启动过程。系统还并行启动其他 Native 服务,图中只保留本章主线。

创建进程、准备运行时、创建 Application 和显示页面,是不同阶段。 系统收到一个 PID,只能说明某次进程创建已有结果;它还需要等新进程报到,交付配置和组件事务,才能把页面真正启动起来。
再单独看谁是谁的父进程
进程树画的是父子关系,不能拿来表示 Binder 请求的方向。以单个主 Zygote 的普通场景为例:

system_server 提出创建要求,Zygote 执行派生,应用因此通常是 Zygote 的子进程。 普通 Java 应用不会因为由 ActivityManagerService 管理,就变成它的 Linux 子进程。WebView Zygote、App Zygote、隔离进程等有各自分支,后面会指出入口。
| 名称 | 它是什么 | 本章需要记住的边界 |
|---|---|---|
| init | Android 用户空间的早期初始化与 Native 服务管理进程 | 主 init 的 PID 是 1;它还能创建其他辅助进程 |
| app_process | 可以执行的 Native 程序 | 按参数进入 Zygote 等运行模式,文件名不等于运行后的进程名 |
| Zygote | 已准备 ART、预加载公共内容并接受创建请求的进程 | 它保留父进程职责,派生出的子进程继续进入各自入口 |
| SystemServer | system_server 内的 Java 启动类 | 多个系统服务共享该进程,启动一个服务通常不是再 fork 一次 |
| ActivityThread | 普通应用进程的 Framework 入口与生命周期调度对象 | 类名中的 Thread 不表示创建一个新的主线程 |
| Application / Activity | Framework 创建的 Java 对象与应用组件 | 一个 Application 对象不是一个 Linux 进程;Activity 可以重建而进程保留 |
fork exec 与普通方法调用的区别
fork() 创建子进程,父进程拿到子 PID,子进程拿到 0,两边从 fork 返回点继续执行;失败时父进程得到负值。exec 系列函数用一个新程序替换当前进程的程序映像,成功后继续执行新程序,PID 保持不变。普通 Java 方法调用既不 fork,也不替换进程映像。
因此,两种常见写法含义不同:init 为 Zygote 先创建子进程,再 exec app_process ;Zygote 为普通应用 派生并专门化子进程,继续执行 Java 入口,通常不为每个应用再次 exec app_process、重新从零创建 ART。
4.2 准备设备并找到源码与观测入口
沿用前面章节的 Emulator
本章用户空间沿用 android17-release 基线,运行场景是 Linux x86_64 Host 上的 SDK Emulator。Kernel 阅读采用与预编译 Kernel 版本线索对应的固定 ACK 提交;各项目提交及实验覆盖范围见 4.13。
在 Host 的 Bash 终端设置路径。选择自己的实验设备;后面的 force-stop 与 kill 都只针对本章示例应用。
bash
chapter4_top="$HOME/aosp"
chapter4_notes="$chapter4_top/Android源码学习笔记/章节学习/第四章-系统启动与进程"
chapter4_run=$(mktemp -d "$HOME/aosp-chapter4.XXXXXX")
chapter4_adb="$chapter4_top/out/host/linux-x86/bin/adb"
"$chapter4_adb" devices -l
read -r -p '输入本次实验设备 serial:' chapter4_serial
"$chapter4_adb" -s "$chapter4_serial" shell getprop sys.boot_completed
"$chapter4_adb" -s "$chapter4_serial" shell getprop ro.build.fingerprint
"$chapter4_adb" -s "$chapter4_serial" shell uname -a
declare -p chapter4_top chapter4_notes chapter4_run chapter4_adb chapter4_serial \
> "$chapter4_run/host-env.sh"
printf 'record=%s\n' "$chapter4_run"
最后保存的是本次自己生成的 host-env.sh,供终端 B 载入;保留打印出的 record 目录,不要在第二个终端重新运行 mktemp。
预期设备在线,boot_completed 为 1。设备尚未运行时,先完成第一章的 Emulator 启动。本章不要求修改 Framework、Kernel 或重新打包系统镜像。
用属性与进程表确认实际拓扑
bash
"$chapter4_adb" -s "$chapter4_serial" shell getprop ro.zygote
"$chapter4_adb" -s "$chapter4_serial" shell getprop ro.product.cpu.abilist
"$chapter4_adb" -s "$chapter4_serial" shell getprop init.svc.zygote
"$chapter4_adb" -s "$chapter4_serial" shell getprop dalvik.vm.usap_pool_enabled
"$chapter4_adb" -s "$chapter4_serial" shell \
'ps -A -o PID,PPID,UID,NAME' > "$chapter4_run/processes.txt"
rg 'init$|zygote|system_server' "$chapter4_run/processes.txt"
ro.zygote 决定主要 Zygote 配置,ABI 列表用于选择能执行目标代码的进程。64 位设备不一定同时运行 32 位 Zygote。属性中的 USAP 值是配置线索,还要结合 ZygoteProcess 的配置优先级、命令条件及实际事件判断选中的启动分支,不能只凭一个空属性断言功能开启或关闭。
本轮实际为 zygote64、ABI x86_64;init 为 PID 1,zygote64 为 424,system_server 为 646,其 PPID 为 424。它们只是本次开机的观测值,后续操作应重新查询,不能把 PID 写死进可复制命令。
源码按职责分布 不按图的层次放在同一目录
| 目录或文件 | 解决的问题 |
|---|---|
| system/core/init、system/core/rootdir | init 阶段、rc 语言、Native 服务创建与重启 |
| frameworks/base/cmds/app_process/app_main.cpp | Native 程序入口和运行模式选择 |
| frameworks/base/core/jni/AndroidRuntime.cpp | ART 创建、JNI 注册、调用 Java main |
| frameworks/base/core/java/com/android/internal/os | ZygoteInit、ZygoteServer、ZygoteConnection、RuntimeInit |
| frameworks/base/core/java/android/os/ZygoteProcess.java | system_server 一侧的 Zygote 通信与创建策略 |
| frameworks/base/services/java/com/android/server/SystemServer.java | 系统服务启动组织 |
| frameworks/base/services/core/java/com/android/server/am/ProcessList.java | 应用进程记录、启动参数和创建结果管理 |
| frameworks/base/core/java/android/app/ActivityThread.java | 应用进程 Java 入口、报到与主线程生命周期分派 |
| bionic/libc/bionic/fork.cpp、clone.cpp | 从 libc 的 fork 语义进入平台系统调用 |
| 匹配 Kernel 的 init/main.c、kernel/fork.c、mm/memory.c | PID 1、任务创建、地址空间与写时复制 |
Android 用户空间项目与 Kernel 源码需分别确认提交。顶层存在 kernel 目录,不表示完整 ACK 源码已经下载。本章的 Kernel 固定链接可直接阅读;本轮校验材料另保存了所选文件的摘要。
4.3 运行一个能看见进程生命周期的示例
先观察 再带着 PID 阅读源码
示例只有一个进程。它在 Application 与 Activity 生命周期中记录 PID、PPID、TID、UID 和时间;页面提供"关闭当前页面"和"结束当前进程"两个操作。这样可以直接比较组件结束和进程结束,而不依赖猜测。
完整代码也保存在示例目录,源码包方便复制到独立实验树。下面四个文件构成整个工程;展开目录使用 Android.bp.example,防止知识库被 Soong 扫描出一个重复模块,源码包内已改为 Android.bp。
Android.bp
bp
android_app {
name: "Chapter4ProcessDemo",
manifest: "AndroidManifest.xml",
srcs: ["src/**/*.java"],
sdk_version: "current",
min_sdk_version: "28",
certificate: "testkey",
optimize: { enabled: false },
}
它声明一个普通 android_app 模块;testkey 仅用于这个实验包,应用没有系统权限。后面采用已有 Host 工具独立构建 APK,无需把模块装进系统分区。
AndroidManifest.xml
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.processdemo">
<uses-sdk android:minSdkVersion="28" />
<application android:name=".DemoApplication"
android:label="进程观察" android:debuggable="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Application 指向我们自己的类,Activity 可被显式启动,debuggable 允许通过 run-as 读取本应用的 /proc 信息。未声明额外 process,所以两者位于同一个默认应用进程。
DemoApplication.java
java
package com.example.processdemo;
import android.app.Application;
import android.content.Context;
import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
import android.system.Os;
import android.util.Log;
public class DemoApplication extends Application {
static void record(String event) {
Log.i("Ch4Process", "event=" + event
+ " pid=" + Process.myPid() + " ppid=" + Os.getppid()
+ " tid=" + Process.myTid() + " uid=" + Process.myUid()
+ " elapsedMs=" + SystemClock.elapsedRealtime()
+ " uptimeMs=" + SystemClock.uptimeMillis());
}
@Override protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
record("Application.attachBaseContext");
}
@Override public void onCreate() {
Trace.beginSection("Ch4/Application.onCreate");
try {
super.onCreate();
record("Application.onCreate");
} finally {
Trace.endSection();
}
}
}
同一次进程存活期间,普通启动中 Application.onCreate 执行一次;重新打开一个 Activity 不要求再创建 Application。 attachBaseContext 比 onCreate 更早到达,但两者都已经晚于 fork、运行时初始化和 ActivityThread 入口。日志不能代表进程诞生的最早时刻。
MainActivity.java
java
package com.example.processdemo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Process;
import android.os.Trace;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override public void onCreate(Bundle state) {
Trace.beginSection("Ch4/Activity.onCreate");
try {
super.onCreate(state);
DemoApplication.record("Activity.onCreate");
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
TextView info = new TextView(this);
info.setText("当前进程 PID:" + Process.myPid()
+ "\n关闭页面后,可以重新打开并比较 PID。");
layout.addView(info);
Button finish = new Button(this);
finish.setText("关闭当前页面");
finish.setOnClickListener(view -> finish());
layout.addView(finish);
Button exit = new Button(this);
exit.setText("结束当前进程");
exit.setOnClickListener(view -> {
DemoApplication.record("killProcess.request");
Process.killProcess(Process.myPid());
});
layout.addView(exit);
setContentView(layout);
} finally {
Trace.endSection();
}
}
@Override protected void onResume() {
super.onResume();
DemoApplication.record("Activity.onResume");
}
@Override protected void onDestroy() {
DemoApplication.record("Activity.onDestroy");
super.onDestroy();
}
}
Trace 标记只包围相应 Java 回调,便于在 Perfetto 中定位。Ch4/Activity.onCreate 的持续时间不包含整个应用启动过程,更不能把它当作屏幕显示首帧的耗时。
构建 安装并执行第一次启动
独立构建脚本复用前几章已经生成的 javac、D8、aapt2、signapk 和公共 android.jar:Java 编译成 class,D8 转为 dex,aapt2 处理 Manifest,最后打包签名。它会记录实际执行命令;缺少工具时返回第一章补齐 Host 构建产物。
bash
python3 "$chapter4_notes/示例/构建示例APK.py" \
--aosp-top "$chapter4_top" \
--module "$chapter4_notes/示例/Chapter4ProcessDemo" \
--name Chapter4ProcessDemo --out "$chapter4_run/build"
"$chapter4_adb" -s "$chapter4_serial" install -r \
"$chapter4_run/build/Chapter4ProcessDemo.apk"
"$chapter4_adb" -s "$chapter4_serial" shell am force-stop com.example.processdemo
"$chapter4_adb" -s "$chapter4_serial" shell am start -W \
-n com.example.processdemo/.MainActivity
"$chapter4_adb" -s "$chapter4_serial" logcat -d -v threadtime 'Ch4Process:I' '*:S' \
> "$chapter4_run/application.log"
cat "$chapter4_run/application.log"
构建脚本成功退出、APK 已生成且安装返回 Success 后,再继续启动。独立脚本要求输出目录为空;重复构建时换一个新的输出目录并相应修改安装路径,不把失败轮次留下的旧 APK 当成本次产物。
预期安装成功,依次看到 attachBaseContext、Application.onCreate、Activity.onCreate 与 onResume。每行的 PID 和主线程 TID 应一致,PPID 应能与相应 Zygote 对上。logcat 可能包含之前运行的同 tag 日志,按本次 PID 和时间区分;不要把历史行算作重复初始化。
am start -W 返回的启动状态用于检查 Activity 启动结果;其 TotalTime、WaitTime 等字段有工具定义的测量范围,不能直接等同于 fork 或 onCreate 的耗时。重复实验前 force-stop 本示例可排除旧进程;它不会清空应用的数据。
如需走完整 Soong,先把源码包解压到一个不存在的 packages/apps/Chapter4ProcessDemo 目录,加载第一章的构建环境后执行 m Chapter4ProcessDemo,再安装该轮 APK。本轮实际通过的是上述独立构建路径,未执行全量系统或 Soong 模块构建。

4.4 从 Kernel 进入 init 并启动 Native 服务
Kernel 怎样找到第一个用户空间入口
Kernel 启动先完成必要初始化。rest_init 创建进入 kernel_init 的任务,并确保它取得 PID 1:
来源:main.c · static noinline void __ref __noreturn rest_init(void)。以下为该函数内的连续原文摘录。
c
pid = user_mode_thread(kernel_init, NULL, CLONE_FS);
这里还不是 Java 或 Android rc。 kernel_init 后续选择 init 程序;该基线默认 ramdisk 入口是 /init,显式启动参数和不同启动形态可能改变选择。最终交接点是:
来源:main.c · static int run_init_process(const char *init_filename)。以下为该函数内的连续原文摘录。
c
return kernel_execve(init_filename, argv_init, envp_init);
kernel_execve 在 PID 1 中装载用户空间程序。本轮串口实际出现 Run /init as init process,因此可以把 Kernel 交接与 Android init 对应起来。失败时先查 Kernel 是否找到 init、ELF 与解释器是否可装载、ramdisk 与架构是否匹配,而不是直接查 Zygote。Kernel 对 init 装载失败的说明
Android init 分阶段 但 PID 1 没有因此变成三个进程
第一阶段负责为后续系统提供最早的环境和挂载条件,包括找到必需设备、初始挂载等;SELinux 阶段准备策略与域切换;第二阶段继续属性、脚本、事件和服务管理。实际任务受设备启动形态影响,不能把所有挂载都归到某一个固定阶段。

main 根据参数分派,而非为每个阶段都 fork:
来源:main.cpp · int main(int argc, char** argv)。以下为该函数内的连续原文摘录。
cpp
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
FirstStageMain 准备下一阶段的程序与参数:
来源:first_stage_init.cpp · int FirstStageMain(int argc, char** argv)。以下为该函数内的连续原文摘录。
cpp
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
同一函数后面调用 execv;SetupSelinux 完成自己的工作后,再以新的参数 exec:
来源:selinux.cpp · int SetupSelinux(char** argv)。以下为该函数内的连续原文摘录。
cpp
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
exec 替换程序映像,保留 PID。 进程表里还可能出现名字也叫 init 的辅助进程,例如处理受限上下文的子进程;不能仅凭两个 init 名称就认定两个初始化阶段并行运行。
rc 是事件与服务声明 不只是从上到下执行的脚本
第二阶段会加载启动脚本。没有 ro.boot.init_rc 覆盖时,主入口是下面这份配置,随后还解析 system、system_ext、vendor、odm、product 等目录:
来源:init.cpp · static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list)。以下为该函数内的连续原文摘录。
cpp
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/system/etc/init/hw/init.rc");
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
// late_import is available only in Q and earlier release. As we don't
// have system_ext in those versions, skip late_import for system_ext.
parser.ParseConfig("/system_ext/etc/init");
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
} else {
parser.ParseConfig(bootscript);
}
rc 中有两种需要先区分的结构:service 声明一个可管理的 Native 服务及其参数;on 声明匹配事件或属性条件后执行的 action。声明 service 不等于此时立刻创建进程;trigger 将事件交给 action 调度,也不能理解为同步调用另一个 Java 函数。
本版本 init.rc 根据 ro.zygote 选择配置。先看实际导入语句:
text
import /system/etc/init/hw/init.${ro.zygote}.rc
本轮 ro.zygote=zygote64,因此展开后导入 init.zygote64.rc;其他设备可能采用不同配置,不能只凭文件名猜测实际拓扑。late-init 动作依次触发文件系统、data、BPF、zygote-start 等阶段;下面是 zygote-start 配置的连续原文:
text
on zygote-start
wait_for_prop odsign.verification.done 1
start statsd
start zygote
start zygote_secondary
wait_for_prop 是条件等待;它没有满足时,这里的后续动作无法照常推进。分析卡启动时,应先找当前动作和等待条件,而不是只看最后一条"启动 Zygote"日志。
service zygote 怎样变成真实进程
本轮 ro.zygote=zygote64,对应 init.zygote64.rc 开头的连续原文:
text
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
第一行给出可执行文件和参数;class 是 init 的服务分组;user/group 指定启动身份。socket 声明要求 init 创建相应 Unix 域 socket,后续进程按约定接收描述符。Zygote 启动时需要进行子进程身份配置,不能因此推论普通应用也以 root 执行。
执行 start zygote 最终进入 init 的 Service::Start。它先准备 socket 等描述符,再选择创建子进程的方式:
来源:service.cpp · Result<void> Service::Start()。以下为该函数内的连续原文摘录。
cpp
pid_t pid = -1;
if (namespaces_.flags) {
pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr);
} else {
pid = fork();
}
if (pid == 0) {
umask(077);
cgroups_activated.CloseWriteFd();
setsid_finished.CloseReadFd();
RunService(descriptors, std::move(cgroups_activated), std::move(setsid_finished));
_exit(127);
} else {
cgroups_activated.CloseReadFd();
setsid_finished.CloseWriteFd();
}
子进程 RunService 处理环境、描述符、身份和 task profile 等,再经 ExpandArgsAndExecv 进入可执行程序:
来源:service.cpp · static bool ExpandArgsAndExecv(const std::vector<std::string>& args, bool sigstop)。以下为该函数内的连续原文摘录。
cpp
return execv(c_strings[0], c_strings.data()) == 0;
从这一刻起,子进程运行的是 app_process 的 Native 入口;父 init 继续管理服务。 服务是否真正准备好,还取决于它后续初始化和所需接口,不由 fork 返回成功保证。
init 准备的 socket,Java 又怎样找到? RunService 会调用 Descriptor::Publish,在 exec 前清除该描述符的 FD_CLOEXEC 标志,使它能在程序替换后继续保留;再把描述符的编号写入环境变量。以下是保存编号的连续原文:
来源:service_utils.cpp · Descriptor::Publish。
cpp
std::string val = std::to_string(fd);
setenv(published_name.c_str(), val.c_str(), 1);
对名为 zygote 的 socket,变量名是 ANDROID_SOCKET_zygote。ZygoteServer 随后调用 createManagedSocketFromInitSocket,读取这个编号:
来源:Zygote.java · createManagedSocketFromInitSocket。
java
String env = System.getenv(fullSocketName);
fileDesc = Integer.parseInt(env);
随后将 fileDesc 设置到 FileDescriptor 对象,再包装成 LocalServerSocket。环境变量提供的是编号,socket 的实际可用性来自跨 exec 保留的描述符。 从 rc 的 socket 声明、init 的创建与发布,到 Zygote 的 Java 接收循环,这条交接才闭合。Java 接收循环的处理过程见 4.7。
这条启动链与设备 Driver 在哪里相接
启动材料最初需要存储或其他加载后端;挂载文件系统和读取后续程序又依赖 Kernel 的块设备、文件系统与映射机制。本轮 /sys/class/block/vda/device/driver 指向 virtio_blk,表明 guest 的该块设备由 VirtIO block Driver 绑定;实际挂载还出现 dm 设备,不能把所有分区都简单等同于裸 vda。
可在设备上查看:
bash
"$chapter4_adb" -s "$chapter4_serial" shell \
'readlink /sys/class/block/vda/device/driver; ls /sys/class/block; cat /proc/mounts' \
> "$chapter4_run/storage-backend.txt"
在匹配 Kernel 的 drivers/block/virtio_blk.c 中,virtblk_probe 初始化 VirtIO 队列:
来源:virtio_blk.c · static int virtblk_probe(struct virtio_device *vdev)。以下为该函数内的连续原文摘录。
c
err = init_vq(vblk);
后续块请求由该文件的 virtio_queue_rq 等路径交给 VirtIO 队列。这里建立的是"已观察到的块设备 → Kernel Driver"的对应;首阶段挂载的完整 dm、文件系统、Host 后端 I/O 时序还需要专门跟踪。本章的 fork 系统调用不要求经过一个 HAL,也不会每次 fork 都调用 virtio_blk。
4.5 app_process 怎样准备 ART 并进入 Zygote
同一个进程先运行 C++ 再调用 Java
app_process 的 main 解析 --zygote 等参数,构造 AppRuntime,并选择 Java 入口:
来源:app_main.cpp · int main(int argc, char* const argv[])。以下为该函数内的连续原文摘录。
cpp
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (!className.empty()) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}
AndroidRuntime::start 在这个刚 exec 后的进程内准备虚拟机。JniInvocation 选择并接入 JNI 运行时实现;startVm 再创建 ART 的虚拟机环境:
来源:AndroidRuntime.cpp · void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)。以下为该函数内的连续原文摘录。
cpp
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
return;
}
onVmCreated(env);
随后注册 Framework 的 Native 方法:
来源:AndroidRuntime.cpp · void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)。以下为该函数内的连续原文摘录。
cpp
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
在找到 Java 类与 main(String[]) 后,调用点是:
来源:AndroidRuntime.cpp · void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)。以下为该函数内的连续原文摘录。
cpp
env->CallStaticVoidMethod(startClass, startMeth, strArray);
JNI 调用没有创建第二个 Zygote 进程,也没有自动更换线程。 同一个进程沿 Native 入口进入 Java 的 ZygoteInit.main;运行时、类和方法已经准备好,才能执行这次 Java 调用。
预加载为什么放在 fork 之前
主 Zygote 通常先预加载部分公共类与资源。ZygoteInit.main 的非延迟预加载分支会执行:
来源:ZygoteInit.java · public static void main(String[] argv)。以下为该函数内的连续原文摘录。
java
preload(bootTimingsTraceLog);
preload 的具体内容以该版本函数为准,包含类、资源和其他共享初始化。这样子进程可继承准备好的运行时状态,并在适当条件下共享未修改页面。它减少重复工作,但预加载本身也有开机时间与内存成本;应用私有类和全部应用业务不会因此提前执行。
Zygote 之后创建 ZygoteServer。其主构造分支通过 Zygote.createManagedSocketFromInitSocket 接收并包装前面 init 准备的 Zygote 与 USAP socket 描述符,连接起 rc 声明和 Java 接收端。对应构造方法
需要启动 SystemServer 时先派生它,父进程随后进入接收循环。整体结构的关键片段是:
来源:ZygoteInit.java · public static void main(String[] argv)。以下为该函数内的连续原文摘录。
java
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
if (r != null) {
r.run();
return;
}
}
父进程继续等待创建请求:
来源:ZygoteInit.java · public static void main(String[] argv)。以下为该函数内的连续原文摘录。
java
caller = zygoteServer.runSelectLoop(abiList);
子进程返回的 Runnable 则最终由 ZygoteInit.main 执行。Java 方法堆栈的退回不是"回到父进程";fork 以后,同样的返回路径可分别在两个地址空间中继续。
4.6 Zygote 怎样创建 SystemServer 并启动系统服务
SystemServer 的入口参数与普通应用不同
ZygoteInit.forkSystemServer 准备 UID/GID、组、能力、运行时参数和 com.android.server.SystemServer 入口。常见的 UID 1000 是系统身份,不是每个 Java 进程都继承 Zygote 的 UID 0。
实际创建请求从 Java 进入 Zygote 的专用入口:
来源:ZygoteInit.java · private static Runnable forkSystemServer(String abiList, String socketName,。以下为该函数内的连续原文摘录。
java
pid = Zygote.forkSystemServer(
parsedArgs.mUid, parsedArgs.mGid,
parsedArgs.mGids,
parsedArgs.mRuntimeFlags,
null,
parsedArgs.mPermittedCapabilities,
parsedArgs.mEffectiveCapabilities);
返回 0 的一方是新子进程。它关闭自己继承的 Zygote 服务 socket,并进入 SystemServer 分支:
来源:ZygoteInit.java · private static Runnable forkSystemServer(String abiList, String socketName,。以下为该函数内的连续原文摘录。
java
if (pid == 0) {
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
zygoteServer.closeServerSocket();
return handleSystemServerProcess(parsedArgs);
}
handleSystemServerProcess 继续处理名称、classpath 和 ClassLoader,再交给共用的运行时入口:
来源:ZygoteInit.java · private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs)。以下为该函数内的连续原文摘录。
java
return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
parsedArgs.mDisabledCompatChanges,
parsedArgs.mEnabledCompatChanges,
parsedArgs.mRemainingArgs, cl);
这说明"Zygote → SystemServer"既包含一次进程创建,也包含子进程中的 Java 入口选择。不能只画成两个 Java 类之间的一次普通调用。
启动多个系统服务通常是在同一个进程里创建对象
SystemServer 的 Java main 本身很短:
来源:SystemServer.java · public static void main(String[] args)。以下为该函数内的连续原文摘录。
java
public static void main(String[] args) {
new SystemServer().run();
}
run 里准备系统环境、主 Looper、SystemContext 和 SystemServiceManager,再分阶段启动服务:
来源:SystemServer.java · private void run()。以下为该函数内的连续原文摘录。
java
startBootstrapServices(t);
startCoreServices(t);
startOtherServices(t);
startApexServices(t);
Bootstrap、Core、Other、APEX 是本版本的启动组织方式,内部还有依赖与条件。比如 ActivityManagerService、PackageManagerService 等在相应位置建立并互相连接;一些服务位于其他 Native 或 Java 进程,需要各自的管理入口。
SystemServiceManager 管理继承 SystemService 的服务生命周期。注册实例后调用 onStart:
来源:SystemServiceManager.java · public void startService(@NonNull final SystemService service)。以下为该函数内的连续原文摘录。
java
mServices.add(service);
// Start it.
long time = SystemClock.elapsedRealtime();
try {
service.onStart();
} catch (RuntimeException ex) {
throw new RuntimeException("Failed to start service " + service.getClass().getName()
+ ": onStart threw an exception", ex);
}
这个 onStart 是 SystemService 的生命周期方法,不是应用 Service.onStartCommand,也不表示创建了一个新 Linux 进程。 服务需要提供跨进程接口时,可以发布自己的 Binder;SystemService 提供的包装最终进入:
来源:SystemService.java · protected final void publishBinderService(String name, IBinder service,。以下为该函数内的连续原文摘录。
java
protected final void publishBinderService(String name, IBinder service,
boolean allowIsolated, int dumpPriority) {
ServiceManager.addService(name, service, allowIsolated, dumpPriority);
}
SystemServiceManager 与 ServiceManager 是不同对象:前者组织 system_server 内的服务启动及 boot phase,后者登记和查找 Binder 服务引用。第二章中的服务发现机制在这里找到了实际使用场景。
用 PowerManagerService 看发布接口与系统就绪的区别
SystemServer 在 bootstrap 阶段通过 SystemServiceManager 创建 PowerManagerService。下面这一行执行后,同一个 system_server 中已有服务对象,并调用了它的 onStart:
来源:SystemServer.java · startBootstrapServices。以下为该函数内的连续原文摘录。
java
mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
PowerManagerService.onStart 发布跨进程 Binder 接口和进程内 LocalService,并登记 Watchdog:
来源:PowerManagerService.java · onStart。以下为该函数内的连续原文摘录。
java
public void onStart() {
publishBinderService(Context.POWER_SERVICE, mBinderService, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL);
publishLocalService(PowerManagerInternal.class, mLocalService);
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
}
发布接口表示可以找到服务入口,不等于所有依赖都已初始化。 后面收到 PHASE_SYSTEM_SERVICES_READY 时,PowerManagerService 才进入 systemReady;收到 PHASE_BOOT_COMPLETED 时,还会更新启动完成状态。这里无需展开电源策略,先理解对象创建、接口发布、依赖就绪和启动完成是分开的生命周期节点。
onStart boot phase 与 boot_completed 分别说明什么
onStart 表示服务开始提供自己的初始化或接口。后续 boot phase 通知表示系统又满足了某个阶段条件;服务可在 onBootPhase 中继续工作。当前源码区分串行与可并行的 phase 回调,因此不能把旧版"所有回调绝对串行"的描述直接套用。
当前版本的阶段定义见 SystemService.java。阶段编号用于排序,不是毫秒,也不是服务数量;各阶段并非等间隔计时点。
| 阶段 | 主要触发位置或条件 | 接收通知的服务可以据此做什么 |
|---|---|---|
PHASE_WAIT_FOR_DEFAULT_DISPLAY(100) |
SystemServer 启动显示相关依赖时 | 完成默认显示的早期准备与等待 |
PHASE_WAIT_FOR_SENSOR_SERVICE(200) |
SystemServer 等待异步启动的 SensorService | 等待 SensorService 可用 |
PHASE_LOCK_SETTINGS_READY(480) |
SystemServer 推进锁屏设置就绪阶段 | 获取锁屏设置数据 |
PHASE_SYSTEM_SERVICES_READY(500) |
SystemServer 推进核心服务就绪阶段 | 安全调用 PowerManager、PackageManager 等核心服务 |
PHASE_DEVICE_SPECIFIC_SERVICES_READY(520) |
SystemServer 启动设备专属服务之后 | 使用设备专属服务 |
PHASE_ACTIVITY_MANAGER_READY(550) |
ActivityManagerService.systemReady 的后续启动回调中 | 发送广播 Intent |
PHASE_THIRD_PARTY_APPS_CAN_START(600) |
同一后续回调推进到允许第三方应用阶段 | 启动或绑定第三方应用;应用可以调用相关服务 |
PHASE_BOOT_COMPLETED(1000) |
ActivityManagerService.finishBooting 推进完成阶段 | 处理启动完成;此阶段按契约发生在 Home 应用已启动之后 |
阶段推动的具体位置见 SystemServer 启动后续流程 与 ActivityManagerService.finishBooting。不能拿"服务在 onStart 发布了 Binder"推导"此刻已经允许启动第三方应用"。 PowerManagerService 在不同阶段的处理可对照 onBootPhase。
SystemServer 的主线程最终进入消息循环:
来源:SystemServer.java · private void run()。以下为该函数内的连续原文摘录。
java
Looper.loop();
系统完成启动的一处关键信号是 ActivityManagerService.finishBooting 设置 sys.boot_completed=1,并继续用户相关启动完成流程。这个属性、某个 SystemService 的阶段通知、某个用户的 BOOT_COMPLETED 广播和应用首帧,测量范围不同;不能互相替代。
4.7 应用启动请求怎样从 system_server 到达 Zygote
先判断进程是否已经存在
显式启动 Activity 会经过组件管理与任务调度。当目标应用进程已经存在,系统可复用它;不存在时,ActivityManagerService / ProcessList 负责安排创建。完整的 Activity 解析、任务栈、窗口和绘制在第六、七章展开,这里从"需要一个新进程"开始。
ProcessList 确定普通应用进程的 Java 入口:
来源:ProcessList.java · boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,。以下为该函数内的连续原文摘录。
java
final String entryPoint = "android.app.ActivityThread";
return startProcessLocked(hostingRecord, entryPoint, app, uid, gids,
runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, requiredAbi,
instructionSet, invokeWith, startUptime, startElapsedTime);
随后准备 UID、GID、ABI、SEInfo、挂载和兼容性等参数,在普通 Zygote 分支调用:
来源:ProcessList.java · private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,。以下为该函数内的连续原文摘录。
java
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
isTopApp, app.getDisabledCompatChanges(), app.getEnabledCompatChanges(),
useDeliQueue, pkgDataInfoMap,
allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
bindOverrideSysprops, app.getStartSeq(),
new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
system_server 调用 Process.start,不等于 system_server 自己执行 fork。 这里进入的是 ZygoteProcess 的通信流程。ProcessList 还会维护启动序号 startSeq、待启动记录和异步执行路径;多个启动请求并发时,不能只凭一个 PID 或相近日志时间对应请求。
在当前 Process.start 实现中,普通 Java 进程使用 ZYGOTE_PROCESS 的 start,随后进入 ZygoteProcess.startViaZygote。源码另有 nativeFrameworkPrototype 与 Native 进程策略标志选择 NATIVE_ZYGOTE_PROCESS 的实验分支;本章未设置该 Native 进程标志,不把它混入普通 Java 应用链路。
Zygote 通信用 Unix 域 socket
ZygoteProcess 根据 ABI 与策略选择合适连接。它把参数组织成"参数个数 + 多行参数字符串"的协议,并判断是否尝试 USAP:
来源:ZygoteProcess.java · private Process.ProcessStartResult zygoteSendArgsAndGetResult(。以下为该函数内的连续原文摘录。
java
String msgStr = args.size() + "\n" + String.join("\n", args) + "\n";
if (shouldAttemptUsapLaunch(zygotePolicyFlags, args)) {
try {
return attemptUsapSendArgsAndGetResult(zygoteState, msgStr);
} catch (IOException ex) {
// If there was an IOException using the USAP pool we will log the error and
// attempt to start the process through the Zygote.
Log.e(LOG_TAG, "IO Exception while communicating with USAP pool - "
+ ex.getMessage());
}
}
未选中 USAP 或相应通信失败回退时,普通 Zygote socket 路径写入命令,并读取创建结果:
来源:ZygoteProcess.java · private Process.ProcessStartResult attemptZygoteSendArgsAndGetResult(。以下为该函数内的连续原文摘录。
java
zygoteWriter.write(msgStr);
zygoteWriter.flush();
// Always read the entire result from the input stream to avoid leaving
// bytes in the stream for future process starts to accidentally stumble
// upon.
Process.ProcessStartResult result = new Process.ProcessStartResult();
result.pid = zygoteInputStream.readInt();
result.usingWrapper = zygoteInputStream.readBoolean();
PID 后还有 usingWrapper 标记,用于表示是否使用了 wrapper。这里的结果是创建结果,不是应用回调已经执行完毕的确认。 子进程初始化和父进程返回 PID 可以并发推进;后面还存在应用向 ActivityManagerService 的 attach。
下面是普通即时 fork 路径的机制时序,省略参数校验与失败分支;完成确认部分沿等待开启的路径展开,Activity 提前调度的差异见 4.10。Binder 和 Unix 域 socket 在图中分开标注。

只看这张图也应能说清三件事:创建请求交给谁、由谁派生、应用怎样重新连接系统管理端。实际 attach 可能早于管理端处理完创建结果,ProcessList 与启动序号相关校验负责协调这类竞态,图中的并行区不表示固定全局先后。
4.8 沿创建分支从 Java 与 JNI 读到 Kernel
先抓住共同职责 再对照三个实现分支
第一次阅读先沿这条主线:system_server 提出创建要求 → Zygote 派生进程或交付池中进程 → 子进程完成 specialize → 进入 ActivityThread.main。 下面三条路径解决的是同一个问题,区别是进程何时产生、创建逻辑在哪一层执行。先读分支表和总图,再选 Java 常规分支理解父子返回;Native 快速分支与 USAP 用来对照差异,不能把它们连续串起来。
当前版本不能只记住 forkAndSpecialize 一条路
| 分支 | 新进程何时产生 | 当前源码中的关键入口 |
|---|---|---|
| Java 常规创建 | 收到创建请求后 fork | ZygoteConnection.processCommand → Zygote.forkAndSpecialize |
| Native 快速创建 | 收到符合条件的请求后 fork,可减少反复回到 Java 的开销 | forkSimpleApps → ZygoteCommandBuffer.forkRepeatedly → nativeForkRepeatedly |
| USAP 池 | 池补充时先派生,接到应用请求时完成身份与运行时专门化 | ZygoteServer 补池、Zygote.forkUsap、childMain |
三者是备选分支。USAP 是 Unspecialized App Process,即尚未针对具体应用完成身份等配置的应用进程。取出池中进程时已有 PID,不能在那一刻再画一次必经 fork;池补充会另行创建任务。官方 Zygote 介绍

沿图先看 USAP:策略和参数允许时,ZygoteProcess 会尝试把请求发给池中进程。USAP 通信抛出 IOException 才回退到普通 Zygote socket;如果收到了负 PID,直接抛出启动异常。 读到有效 PID 也不等于应用已初始化,池中进程还要完成 specialize、进入 Java 入口。
进入普通 socket 路径后,再由 Zygote 判断是否走 Native 快速创建;Java 常规与 Native 快速都经 ForkCommon 落到 Kernel。管理端接收 PID,子进程继续准备运行环境。应用报到和初始化超时见 4.10。
本章选择普通应用、无 wrapper、无额外 App Zygote 的场景。代码阅读把两条即时 fork 路径都接到 ForkCommon;设备日志与 Trace 则按实际能证明的范围解释,不能仅凭"USAP 关闭"就断言一定执行了 Java 常规分支。
常规 Java 分支怎样进入 Native
ZygoteConnection.processCommand 校验参数和请求方身份。对需要常规 Java fork 的条件,执行:
来源:ZygoteConnection.java · Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK)。以下为该函数内的连续原文摘录。
java
pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,
parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,
parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,
fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,
parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,
parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
parsedArgs.mBindMountAppStorageDirs,
parsedArgs.mBindMountSyspropOverrides);
Zygote.forkAndSpecialize 经 Native 方法进入 com_android_internal_os_Zygote_nativeForkAndSpecialize。后者调用 ForkCommon;返回 0 的子进程继续执行 SpecializeCommon。父进程处理结果并保留 Zygote 角色,子进程则进入 handleChildProc 与自己的 RuntimeInit。
Native 快速分支怎样接回相同的创建机制
另一分支调用 Zygote.forkSimpleApps:
来源:Zygote.java · static @Nullable Runnable forkSimpleApps(。以下为该函数内的连续原文摘录。
java
boolean in_child =
argBuffer.forkRepeatedly(zygoteSocket, expectedUid, minUid, firstNiceName,
isTopApp);
if (in_child) {
return childMain(argBuffer, /*usapPoolSocket=*/null, /*writePipe=*/null);
} else {
return null;
}
forkRepeatedly 的 Native 实现在 ZygoteCommandBuffer.cpp,创建动作交给:
来源:com_android_internal_os_ZygoteCommandBuffer.cpp · jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly(。以下为该函数内的连续原文摘录。
cpp
int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds,
/*args_known=*/ true, /*is_priority_fork=*/ true,
is_top_app == JNI_TRUE, use_fifo_ui == JNI_TRUE,
/*purge=*/ first_time);
这里的 zygote::forkApp 是同一套 Zygote Native 代码中的 C++ 辅助函数,不是调用 Java 的应用 main。它整理要关闭、忽略的描述符,并进入:
来源:com_android_internal_os_Zygote.cpp · int zygote::forkApp(JNIEnv* env,。以下为该函数内的连续原文摘录。
cpp
return zygote::ForkCommon(env, /* is_system_server= */ false, fds_to_close,
fds_to_ignore, is_priority_fork, is_top_app, use_fifo_ui, purge);
快速分支的子进程回到 childMain 读取参数并 specialize,然后进入 ZygoteInit.zygoteInit。不同路径在创建与专门化的组织方式上有区别,但没有绕过内核进程创建机制。
ForkCommon 怎样落到 Bionic
ForkCommon 负责 fork 前后的信号、描述符和运行环境协调,真正创建的交接语句是:
来源:com_android_internal_os_Zygote.cpp · pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server,。以下为该函数内的连续原文摘录。
cpp
pid_t pid = fork();
fork 的父子返回语义由 libc 与 Kernel 配合实现。Bionic 在 fork 周围执行 atfork 协调:
来源:fork.cpp · int fork()。以下为该函数内的连续原文摘录。
cpp
__bionic_atfork_run_prepare();
int result = _Fork();
_Fork 继续进入:
来源:fork.cpp · int _Fork()。以下为该函数内的连续原文摘录。
cpp
int _Fork() {
return __clone_for_fork();
}
__clone_for_fork 使用 clone 并传入子任务 TID 相关标志:
来源:fork.cpp · int __clone_for_fork()。以下为该函数内的连续原文摘录。
cpp
int result = clone(nullptr, nullptr, (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD),
nullptr, nullptr, nullptr, &(self->tid));
注意这里函数指针参数为空。clone.cpp 因而选择对应系统调用分支,本轮 x86_64 的交接是:
来源:clone.cpp · int clone(int (*fn)(void*), void* child_stack, int flags, void* arg, ...)。以下为该函数内的连续原文摘录。
cpp
if (fn != nullptr) {
clone_result = __bionic_clone(flags, child_stack, parent_tid, new_tls, child_tid, fn, arg);
} else {
#if defined(__x86_64__) // sys_clone's last two arguments are flipped on x86-64.
clone_result = syscall(__NR_clone, flags, child_stack, parent_tid, child_tid, new_tls);
#else
clone_result = syscall(__NR_clone, flags, child_stack, parent_tid, new_tls, child_tid);
#endif
}
源码写 fork,跟踪时看到 clone,并不矛盾。 fork 是库函数提供的语义,Bionic 可通过 clone 系统调用实现。x86_64 和其他架构的部分参数次序不同,不能把这里的汇编或 syscall 参数顺序直接套到 ARM64。
Kernel 建立任务并让它可以运行
匹配 Kernel 的 clone 系统调用入口在 kernel/fork.c,它把参数整理为 kernel_clone_args 后调用 kernel_clone。内核创建核心任务的交接点是:
来源:fork.c · pid_t kernel_clone(struct kernel_clone_args *args)。以下为该函数内的连续原文摘录。
c
p = copy_process(NULL, trace, NUMA_NO_NODE, args);
copy_process 处理 task_struct、PID、地址空间、文件、信号等资源的复制或共享规则,失败时返回错误,不能把一个结构体复制看作全部工作。成功后,kernel_clone 让新任务进入调度:
来源:fork.c · pid_t kernel_clone(struct kernel_clone_args *args)。以下为该函数内的连续原文摘录。
c
wake_up_new_task(p);
可运行不等于马上占用 CPU。父子谁先继续、子进程何时完成 Java 初始化,由调度与各自工作共同决定。在 Trace 中,sched_process_fork 能标出任务创建,sched_waking、sched_switch 则帮助判断其何时实际获得执行机会。
同一个 sched_process_fork 事件也可来自线程创建;应结合 TID、TGID、父子关系和后续 /proc 或 Trace 信息判断,不能把每条 fork 事件都计为一个新应用进程。
4.9 子进程怎样获得内存 身份与资源分组
fork 后的两个地址空间为什么还能节省内存
从 Java 看,父子进程可以各自访问继承的对象;从 Kernel 看,普通 fork 的子进程拥有自己的地址空间管理结构,部分底层物理页最初仍可共享。写时复制 Copy-on-Write,简称 COW,负责在适当的私有映射写入时维持隔离。
copy_mm 的关键分支说明,是否共享整个 mm 取决于 CLONE_VM:
来源:fork.c · static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)。以下为该函数内的连续原文摘录。
c
if (clone_flags & CLONE_VM) {
mmget(oldmm);
mm = oldmm;
} else {
mm = dup_mm(tsk, current->mm);
if (!mm)
return -ENOMEM;
}
前面的 __clone_for_fork 未指定 CLONE_VM,因此进入 dup_mm。它复制地址空间描述与相关页表映射,而不是立即复制整个 Java 堆的每一页内容。线程创建使用另一组共享标志;普通应用不能因此读取另一个应用的私有 Java 堆。
mm/memory.c 复制适用的页表项时,会处理 COW 写保护:
来源:memory.c · static __always_inline void __copy_present_ptes(。以下为该函数内的连续原文摘录。
c
if (is_cow_mapping(src_vma->vm_flags) && pte_write(pte)) {
wrprotect_ptes(src_mm, addr, src_pte, nr);
pte = pte_wrprotect(pte);
}
对于适用映射,父子页表可先映射同一物理页;写入时发生写保护异常,Kernel 的 do_wp_page 等路径根据引用与映射状态决定复用或复制,必要时由 wp_page_copy 复制并更新映射。不能笼统说"所有缺页都复制"或"每次写入都复制"。

这张图表示映射关系变化,不是两个进程共享同一个可直接互相修改的 Java 引用。文件共享映射、只读映射、大页、运行时 GC 等各有细节,深入内存统计放在第九章。
预加载让可复用内容在派生之前准备好,COW 则让适用页面避免立即物理复制。 应用后续改写、GC、运行时维护都会改变共享情况,所以省内存的效果必须结合 PSS 等数据评估,不能简单把 RSS 相加当成独占内存。
specialize 把子进程变成某个具体应用
specialize 表示按目标应用配置子进程。除了运行时标志,它还涉及 UID/GID 与补充组、能力、SELinux、文件描述符、命名空间与应用目录挂载,以及资源分组等。不同入口共享部分实现,但具体顺序与条件应以函数为准。
SpecializeCommon 设置应用 UID 的关键节点是:
来源:com_android_internal_os_Zygote.cpp · static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid,。以下为该函数内的连续原文摘录。
cpp
if (setresuid(uid, uid, uid) == -1) {
fail_fn(CREATE_ERROR("setresuid(%d) failed: %s", uid, strerror(errno)));
}
安全域转换又有单独的操作:
来源:com_android_internal_os_Zygote.cpp · static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid,。以下为该函数内的连续原文摘录。
cpp
if (selinux_android_setcontext(uid, is_system_server, se_info_ptr, nice_name_ptr) == -1) {
fail_fn(CREATE_ERROR("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
is_system_server, se_info_ptr, nice_name_ptr));
}
UID、SELinux 域和命名空间分别约束不同问题,不能仅凭"UID 已改变"就认为隔离步骤已经全部完成。 fork 继承了许多状态,Zygote 必须清理不该由应用持有的描述符、调整信号和运行时状态;多线程环境中的锁也需要正确协调,不能照着一行 fork 在任意 Java 线程里复制同样流程。
普通 fork 的子进程从执行 fork 的线程继续,父进程的其他线程不会各自复制一份继续运行。但那些线程曾经使用的锁和内存状态可能被继承,因此 Zygote 与 ART 需要 fork 前后的协调;应用的 Binder 接收线程池等也要在子进程中准备,不能假设父进程的全部线程自动可用。
cgroup 与 task profile 怎样关联到进程
cgroup 把任务放入可进行资源控制或统计的分组;Android 的 task profile 把若干配置动作组织成有名字的策略。Zygote、libprocessgroup 与 Framework 会在创建及运行状态变化时应用相关配置。Linux 的进程组、会话和 cgroup 是不同机制,名字里有 group 不表示同一个东西。
本示例可用自己的身份查看:
bash
chapter4_pid=$("$chapter4_adb" -s "$chapter4_serial" shell pidof com.example.processdemo | tr -d '\r')
if [ -n "$chapter4_pid" ]; then
"$chapter4_adb" -s "$chapter4_serial" shell run-as com.example.processdemo \
cat "/proc/$chapter4_pid/status"
"$chapter4_adb" -s "$chapter4_serial" shell run-as com.example.processdemo \
cat "/proc/$chapter4_pid/cgroup"
else
echo "未找到示例进程,请先启动应用;本次未查询 /proc"
fi
预期 status 给出 TGID、PID、PPID、UID 等,cgroup 给出该时刻的分组。前台应用可能处于 top-app 相关组;具体路径受设备、cgroup 版本与 Android 策略影响,以实际输出为准。run-as 失败先检查包名、安装状态与 debuggable,不要用改变系统安全策略来掩盖这个前置条件。

4.10 ActivityThread 怎样报到并执行应用代码
有了 PID 之后 仍要准备应用运行环境
各创建分支在普通应用子进程中接回 ZygoteInit.zygoteInit。它先进行通用初始化,再进入 Native 子进程初始化与 applicationInit:
来源:ZygoteInit.java · public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,。以下为该函数内的连续原文摘录。
java
RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit();
return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges,
enabledCompatChanges, argv, classLoader);
nativeZygoteInit 的 JNI 桥接继续调用 AppRuntime 的钩子:
来源:AndroidRuntime.cpp · static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)。以下为该函数内的连续原文摘录。
cpp
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{
gCurRuntime->onZygoteInit();
}
AppRuntime::onZygoteInit 准备 Binder 进程状态和接收线程池:
来源:app_main.cpp · virtual void onZygoteInit()。以下为该函数内的连续原文摘录。
cpp
virtual void onZygoteInit()
{
sp<ProcessState> proc = ProcessState::self();
ALOGV("App process: starting thread pool.\n");
proc->startThreadPool();
}
Binder 接收能力在应用 onCreate 之前就已准备;不是 Application 创建后才临时加上的功能。 子进程继承 ART 的基础状态,但 Binder 及其他运行时资源要按子进程规则重新建立或调整。
入口字符串怎样真正调用到 ActivityThread.main
前面 ProcessList 设置了 android.app.ActivityThread。RuntimeInit.applicationInit 解析参数后,把入口类交给:
来源:RuntimeInit.java · protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,。以下为该函数内的连续原文摘录。
java
return findStaticMain(args.startClass, args.startArgs, classLoader);
findStaticMain 查找该类的静态 main 方法,并返回一个 Runnable 包装:
来源:RuntimeInit.java · protected static Runnable findStaticMain(String className, String[] argv,。以下为该函数内的连续原文摘录。
java
return new MethodAndArgsCaller(m, argv);
这个包装最终被执行时,通过反射调用入口:
来源:RuntimeInit.java · public void run()。以下为该函数内的连续原文摘录。
java
mMethod.invoke(null, new Object[] { mArgs });
因此,创建请求里的字符串、RuntimeInit 的类与方法查找、Runnable 的执行共同组成了这次 Java 入口交接。这里用 Runnable 表示稍后执行的工作,并没有因为它实现了 Runnable 就创建线程。
这个 Runnable 最后由谁执行? 选 Java 常规创建、普通应用且没有 wrapper 的路径往回看:processCommand 的子进程分支返回 handleChildProc 的结果;handleChildProc 先调用 ZygoteInit.zygoteInit,完成前述 Binder 准备并取得应用入口包装:
来源:ZygoteConnection.java · handleChildProc。以下为连续原文摘录。
java
return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
parsedArgs.mDisabledCompatChanges,
parsedArgs.mEnabledCompatChanges,
parsedArgs.mRemainingArgs, null /* classLoader */);
这个包装沿调用栈回到 ZygoteServer.runSelectLoop。它识别出当前已经是子进程,在 mIsForkChild 分支返回 command:
来源:ZygoteServer.java · runSelectLoop。以下为连续原文摘录。
java
return command;
返回到 ZygoteInit.main 后,真正执行入口包装的是下面的 caller.run:
来源:ZygoteInit.java · main。以下为连续原文摘录。
java
if (caller != null) {
caller.run();
}
于是,构造入口包装 → 沿子进程调用栈返回 → caller.run → 反射调用 ActivityThread.main 连成完整路径。这里始终是应用子进程继续执行;父 Zygote 留在接收循环中,Native 快速创建与 USAP 的前段路径则按 4.8 的分支分别阅读。
主线程先准备 Looper 再向系统报到
ActivityThread.main 在应用进程的主线程中执行,关键初始化为:
来源:ActivityThread.java · public static void main(String[] args)。以下为该函数内的连续原文摘录。
java
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
随后同一线程进入:
来源:ActivityThread.java · public static void main(String[] args)。以下为该函数内的连续原文摘录。
java
Looper.loop();
ActivityThread 是管理对象;构造它不等于 new Thread().start()。主线程本来就是新进程继续运行的线程。Looper.prepareMainLooper 把消息队列准备好,attach 把应用连接到系统,Looper.loop 再持续分派消息。
attach 中通过 ActivityManager 的 Binder 接口交出 mAppThread:
来源:ActivityThread.java · private void attach(boolean system, long startSeq)。以下为该函数内的连续原文摘录。
java
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
mAppThread 是 ActivityThread.ApplicationThread 的 Binder 入口,系统之后可通过它向应用交付配置和生命周期事务。startSeq 用于把本次报到与管理端的待启动记录联系起来;它不是 PID 的另一种写法。
bindApplication 怎样从 Binder 线程回到主线程
ActivityManagerService.attachApplication 校验身份及启动记录,保存应用的 Binder 接口,并向它调用 bindApplication。应用端 ApplicationThread.bindApplication 收到配置,构造 AppBindData,最后提交消息:
来源:ActivityThread.java · public final void bindApplication(。以下为该函数内的连续原文摘录。
java
sendMessage(H.BIND_APPLICATION, data);
应用主线程的 H.handleMessage 取出并处理:
来源:ActivityThread.java · public void handleMessage(Message msg)。以下为该函数内的连续原文摘录。
java
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
if (DEBUG_STORE_ENABLED) {
debugStoreId =
DebugStore.recordBindApplication();
}
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
if (DEBUG_STORE_ENABLED) {
DebugStore.recordEventEnd(debugStoreId);
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;


这张图是普通启动的简化机制图。系统能在 attach 处理期间发起反向 Binder 调用;bindApplication 是 oneway,接收线程入队与 attach 返回可以并发推进。消息可以先入队等待主线程,也可以在主线程进入循环后再到达。Binder 负责跨进程交付,Handler 负责把工作交给应用主线程,前两章的机制在这里接在一起。
Application.onCreate 之前还可能执行哪些代码
handleBindApplication 建立资源、ClassLoader、Instrumentation 等环境,再创建或取得 Application:
来源:ActivityThread.java · private void handleBindApplication(AppBindData data)。以下为该函数内的连续原文摘录。
java
app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
普通启动过程中,相关 ContentProvider 的安装可能先于 Application.onCreate;构造、静态初始化和 attachBaseContext 也可能更早执行。因此 Application.onCreate 适合作为一个应用初始化观察点,但不是"整个应用所有 Java 代码的绝对第一行"。
随后调用:
来源:ActivityThread.java · private void handleBindApplication(AppBindData data)。以下为该函数内的连续原文摘录。
java
mInstrumentation.callApplicationOnCreate(app);
Application 初始化完成后 系统怎样解除等待
attachApplication 只说明应用进程已经报到,bindApplication 是系统向应用交付初始化任务;oneway 调用返回并不等于主线程已经执行完初始化。 因此当前版本还需要从应用回到系统的一次完成确认。
handleBindApplication 在执行 Application.onCreate、恢复线程策略及处理字体预加载等后续工作后,调用:
来源:ActivityThread.java · handleBindApplication。以下为该函数内的连续原文摘录。
java
try {
mgr.finishAttachApplication(mStartSeq, timestampApplicationOnCreateNs);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
这个调用由应用主线程 经 IActivityManager Binder 进入 system_server 的处理线程。mStartSeq 关联本次启动实例;timestampApplicationOnCreateNs 记录 onCreate 调用前取得的时间点,不是 onCreate 的耗时或结束时间。
ActivityManagerService.finishAttachApplication 读取 Binder 调用者的 PID、UID,再进入 finishAttachApplicationInner。管理端先把报到实例和 ProcessRecord 对上:
来源:ActivityManagerService.java · finishAttachApplicationInner。以下为该函数内的连续原文摘录。
java
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
只有 app != null、app.getStartUid() == uid 且 app.getStartSeq() == startSeq 时,才会移除初始化软、硬超时;不匹配则按异常启动实例清理,不能确认给另一次启动。
随后清除 pendingFinishAttach,并处理等待该进程的 Activity、Service、广播等。这里的"完成"是应用初始化协议上的完成,既不保证业务初始化都结束,也不表示页面已经显示首帧。
有两个开关会影响具体交付时机,读源码时要分开:
| 条件 | 当前实现的行为 |
|---|---|
mEnableWaitForFinishAttachApplication 为 true(源码默认值) |
等待应用完成确认后再解除相应等待;确认时取消初始化超时 |
| 上述等待开关关闭 | attach 处理中直接进入完成处理;后续应用发来的确认会被忽略 |
expediteActivityLaunchOnColdStart() 开启 |
Activity 交付可提前到 attach 处理中,不再等到完成确认;这不表示 Activity 回调会抢在当前主线程的初始化消息中间执行 |
依据:默认等待配置、提前交付 Activity 与关闭等待的处理、完成确认入口。因此,图中画的是等待开启的主线;不能把"所有组件事务一律在确认后才发送"当成所有配置下的规则。
Activity 创建与显示仍需系统交付相应组件事务,再由应用主线程处理。示例使用普通无 Provider 应用,日志展示的是这次场景中的回调顺序;组件加载、生命周期事务和窗口首帧在后续章节继续展开。
有 PID 但应用没有启动起来 系统怎样处理
排查启动卡顿时,先判断停在哪个阶段。下图是启动状态与超时流程图:从创建结果、应用报到、初始化完成三个判断点,分别走向继续、超时清理或 ANR。

上图沿普通应用、无 wrapper、等待完成确认开启的路径展开;若应用报到早于创建结果登记,管理端不会再机械地为已报到实例补设等待 attach 的超时。
| 停在哪一步 | 设置与解除条件 | 失败时到哪里看 |
|---|---|---|
| 尚未得到有效 PID | 普通 Zygote socket 路径遇到负 PID,抛出 ZygoteStartFailedEx;通信异常也会作为启动失败返回 |
ZygoteProcess、ProcessList 的异常和启动清理,不属于"应用 onCreate 超时" |
| 已登记 PID,但尚未报到 | ProcessList 在尚未 attach 时设置 PROC_START_TIMEOUT_MSG;attach 处理推进后移除 |
ActivityManagerService.handleProcessStartOrKillTimeoutLocked,检查 failed to attach 和 am_process_start_timeout |
| 已发送 bindApplication,但未确认完成 | 设置 BIND_APPLICATION_TIMEOUT_SOFT_MSG;完成确认校验通过后移除软、硬超时 |
handleBindApplicationTimeoutSoft / Hard,以及 failed to complete startup 的应用启动 ANR |
先看初始化超时的设置与旧超时的移除,它们发生在同一段 attach 处理代码中:
来源:ActivityManagerService.java · attachApplicationLocked。以下为该函数内的连续原文摘录。
java
Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG);
msg.obj = app;
msg.arg1 = BIND_APPLICATION_TIMEOUT;
mHandler.sendMessageDelayed(msg, msg.arg1 /* BIND_APPLICATION_TIMEOUT */);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
当前基线中,普通进程报到超时常量为 10 秒 × Build.HW_TIMEOUT_MULTIPLIER;初始化软超时为 15 秒 × 同一倍率。wrapper、早报到竞态、调试和目标设备条件会影响具体路径,不能据此宣称所有 Android 应用都在固定秒数后被杀。
初始化软超时处理还会计算这段时间里进程 runnable 但没有获得 CPU 的累计等待增量,用它决定一次延长,上限是软超时长度;没有可延长额度时直接进入硬超时。最终调用:
来源:ActivityManagerService.java · handleBindApplicationTimeoutHard。以下为该函数内的连续原文摘录。
java
mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
未报到的进程启动超时清理,与已报到后初始化超时触发 ANR,是不同处理。 排查时先确定状态,再判断是 specialize、运行时、Binder 报到、Provider、Application 初始化还是 CPU 调度阻塞。
源码定位:创建结果与报到超时登记、未报到的超时清理、初始化软、硬超时、超时常量。这些失败分支是源码讲解,本章普通运行实验没有故意制造超时。

4.11 页面关闭 进程退出与系统回收有什么区别
Activity 销毁不要求进程同时退出
页面调用 finish 后,系统可结束该 Activity;进程还可能保留为缓存进程,以便下一次启动复用。反过来,进程因信号或内存压力退出时,也不保证每个 Activity 都收到 onDestroy,更不保证 Application 收到 onTerminate。
Android 真机的 Application.onTerminate 不作为常规进程退出回调使用。需要持久化的数据应在合适的业务和生命周期时机保存;不能把关键数据全寄托在最后一次销毁通知中。

"可能保留"是策略与资源条件下的行为,不保证每次关闭页面都缓存。进程中的服务、其他组件、内存压力和用户操作都会影响结果。
Linux 回收与 Android 记录清理分属不同层
Linux 处理线程组退出、地址空间和描述符释放,并向父进程提供退出状态。Zygote 对自己派生的子进程有信号与回收处理;init 管理自己登记的 Native 服务,还承担 PID 1 的相应职责。Android 管理端则通过 Binder 死亡、进程通知与启动记录等路径清理 ProcessRecord 和组件状态。
先看本示例真正的父进程。Zygote 的 Native SIGCHLD 处理循环会回收已退出子任务,并向 system_server 发送相关状态:
来源:com_android_internal_os_Zygote.cpp · static void SigChldHandler(int /*signal_number*/, siginfo_t* info, void* /*ucontext*/)。以下为教学性删节,保留回收循环与通知动作;省略位置已用注释标明。
cpp
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
// Notify system_server that we received a SIGCHLD
sendSigChildStatus(pid, info->si_uid, status);
// 笔记删节:退出日志、USAP 维护和 system_server 退出处理,后者在下文单独展开。
}
waitpid 回收的是 Kernel 提供的子任务退出状态;它不会调用应用的 Activity.onDestroy。 这里的 sendSigChildStatus 又把退出事件带回 Android 管理侧,和 Binder 死亡通知一起参与后续管理。
应用先前通过 attach 交出了 ApplicationThread Binder。该 Binder 的死亡通知到达 ActivityManagerService.AppDeathRecipient 时,进入:
来源:ActivityManagerService.java · public void binderDied() {。以下为该函数内的连续原文摘录。
java
synchronized (mGlobalLock) {
appDiedLocked(mApp, mPid, mAppThread, true, null);
}
appDiedLocked 还需要核对 PID、线程接口和当前进程记录,避免把过期死亡事件当成新进程退出;在有效死亡处理路径中继续调用:
来源:ActivityManagerService.java · final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,。以下为该函数内的连续原文摘录。
java
handleAppDiedLocked(app, pid, false, true, fromBinderDied);
handleAppDiedLocked 再清理应用与组件相关状态。Linux 父进程的退出状态回收,与 system_server 的 Framework 记录清理,是两条相互配合的路径;不能画成一个 Java 对象析构函数。 Binder 死亡和 Zygote 退出通知也不应画成固定先后,管理端按身份和当前状态处理。
对 init 管理的子进程,sigchld_handler.cpp 的 ReapOneProcess 则在观察到退出状态后安排 waitpid 回收:
来源:sigchld_handler.cpp · static pid_t ReapOneProcess()。以下为该函数内的连续原文摘录。
cpp
auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });
这是 Kernel 退出状态回收与 init 服务管理相接的节点。普通应用的 Linux 父进程通常是 Zygote,不能把所有应用退出都画成 init 直接 waitpid;父进程退出后的收养关系还需单独判断。
init 的 Native 服务是否重启受 disabled、oneshot、critical、重启策略和退出原因等控制;普通应用被终止后是否再次启动,由组件需求与系统策略决定。两类管理机制不同,不要把 init 的 onrestart 当作所有应用的恢复逻辑。
system_server 退出后 为什么 Zygote 也会退出
普通应用退出,Zygote 通常继续服务其他创建请求;但 system_server 是一条特殊分支。Zygote 保存了它派生的 system_server PID,SIGCHLD 回收循环发现退出的是这个子进程时,会终止自身:
来源:com_android_internal_os_Zygote.cpp · SigChldHandler。以下为该函数内的连续原文摘录。
cpp
if (pid == gSystemServerPid) {
async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
"Exit zygote because system server (pid %d) has terminated", pid);
kill(getpid(), SIGKILL);
}
这条恢复关系跨越了两个父子管理边界:Zygote 管理 system_server 的退出,init 管理 Zygote 服务的退出和重启。 不是 ActivityManagerService 在退出后还能负责"把自己启动回来"。

本章的 init.zygote64.rc 给 Zygote 配置了 --start-system-server,并列出 audioserver、cameraserver、netd 等 onrestart 动作。init 的 Service::Reap 会处理重启状态和连续失败计数;critical 是否生效、时间窗口和 fatal 目标还受 rc 参数及属性影响,不能简化成"崩溃几次一定整机重启"。
常规恢复分支中,Kernel 与 PID 1 可以继续运行,Android 的核心用户空间进程重新建立,这就是理解运行时重启的关键;整机重启则会重新经过 Kernel 启动。界面重新出现开机动画本身不足以区分二者,需要结合 Kernel uptime、init 与 Zygote/system_server 的身份和启动日志。
本节通过源码解释恢复机制,不安排杀 system_server 或 Zygote 的动手实验;4.12 的真实实验只终止无业务数据的示例应用。
本示例怎样对照两类结束
第一组固定在示例页面点"关闭当前页面"。这个按钮调用 finish(),明确请求结束 Activity。先执行导出和显示 finish.log 的两条命令,按本次 PID 与时间确认 Activity.onDestroy 已出现,再查询 PID 并重新启动;随后读 reopen.log 对照新的回调:
bash
"$chapter4_adb" -s "$chapter4_serial" logcat -d -v threadtime \
'Ch4Process:I' '*:S' > "$chapter4_run/finish.log"
cat "$chapter4_run/finish.log"
"$chapter4_adb" -s "$chapter4_serial" shell pidof com.example.processdemo
"$chapter4_adb" -s "$chapter4_serial" shell am start -W \
-n com.example.processdemo/.MainActivity
"$chapter4_adb" -s "$chapter4_serial" logcat -d -v threadtime \
'Ch4Process:I' '*:S' > "$chapter4_run/reopen.log"
cat "$chapter4_run/reopen.log"
预期可能观察到 PID 保留、Application.onCreate 没有再出现,而 Activity.onCreate 再次出现;若进程已被回收,需要按新进程解释。只有确认原 Activity 已销毁,才能把再次 onCreate 与"仅从后台恢复"区分开。
返回键单独作为变化条件观察,不能直接替代 finish 实验。Android 12 及以后,从 Launcher 启动的根 Activity 默认返回行为通常是把任务移到后台,未必销毁 Activity;再进入时可能只恢复原实例。目标版本还可能受设备配置、任务栈和自定义返回处理影响。依据:ActivityClientController.shouldMoveTaskToBack。对照时记录启动入口、onStop/onDestroy 与 PID,避免只凭按了返回键判断销毁。
2026-10-19 的历史实测通过 am start -n 显式启动示例后发送返回键,当时确实记录到 onDestroy、PID 保留和 Activity 重建;该观察保留在实测记录中,适用范围就是那次启动入口与设备条件。修订后的跟做主路径使用按钮 finish,避免把历史返回行为泛化。
第二组在前台点"结束当前进程"。若要从终端复现相同的强制终止语义,可以在本示例身份下给自己发送 SIGKILL:
bash
"$chapter4_adb" -s "$chapter4_serial" logcat -d -v threadtime \
'Ch4Process:I' '*:S' > "$chapter4_run/before-kill.log"
chapter4_pid=$("$chapter4_adb" -s "$chapter4_serial" shell pidof com.example.processdemo | tr -d '\r')
if [ -n "$chapter4_pid" ]; then
"$chapter4_adb" -s "$chapter4_serial" shell run-as com.example.processdemo kill -9 "$chapter4_pid"
else
echo "未找到示例进程;本次未发送信号"
fi
"$chapter4_adb" -s "$chapter4_serial" shell am start -W \
-n com.example.processdemo/.MainActivity
"$chapter4_adb" -s "$chapter4_serial" logcat -d -v threadtime \
'Ch4Process:I' '*:S' > "$chapter4_run/after-kill.log"
cat "$chapter4_run/after-kill.log"
先读 after-kill.log 中本轮新出现的时间与 PID,确认新的 Application 与 Activity 初始化记录,再与 before-kill.log 对照。若 run-as 报错,先处理报错,不把随后的启动当作杀进程成功。
这会丢失该进程尚未保存的内存状态,只用于本章无业务数据的示例。预期重启获得新进程,并重新执行 Application.onCreate;强制终止不会先执行一遍 Java 清理。PID 可能在长时间后被复用,因此真实分析还需结合启动时间或 Trace 的进程身份,而不是仅比较数值。
恢复就是重新启动本示例;结束实验后可卸载包。不要用杀 Zygote 或 system_server 的方式验证普通应用退出,这会把系统级恢复也混进当前实验。
4.12 用日志与 Trace 验证启动 创建和退出
为系统启动与应用启动分别采集证据
系统启动关注 Kernel 到 init、Zygote 和 system_server 的阶段;应用启动关注一次具体进程从创建到报到与回调的过程。运行中才开始抓取的十二秒 Trace 不会自动包含开机最早阶段。
在独立实验实例中,从第一章的冷启动命令增加 -show-kernel,保存 Emulator 的串口输出,再于启动后保存属性、进程表和 events 日志。已有设备可能没有保留早期日志;不要为了补这份证据去重启正在使用的设备。
bash
"$chapter4_adb" -s "$chapter4_serial" shell getprop \
> "$chapter4_run/properties.txt"
"$chapter4_adb" -s "$chapter4_serial" shell logcat -b events -d -v threadtime \
> "$chapter4_run/boot-events.log"
rg 'boot_progress|am_proc_start|am_proc_died' "$chapter4_run/boot-events.log"
预期能取得仍保留在缓冲区内的事件;是否包含完整开机时间线取决于采集时机和缓冲区。Kernel 串口的启动相对时间、logcat 的墙钟时间、应用的 uptime/elapsed 时间不是同一种时钟,不能把不同列直接相减。
本轮冷启动串口的关键点为:
| Kernel 启动相对时间 | 实际记录 | 能说明什么 |
|---|---|---|
| 1.002611 s | Run /init as init process | Kernel 开始执行初始用户空间入口 |
| 3.384432 s | init second stage started | 已进入 Android 第二阶段 init |
| 8.538349 s | processing action (zygote-start) | init 开始执行相应启动动作 |
| 8.546874 s | starting service 'zygote' | init 提交 Zygote 服务创建,不表示 Java 预加载已完成 |
这些值来自一次独立 Emulator 冷启动,不能作为其他设备的性能基准。详细原始输出、环境和命令见本轮设备记录。
十二秒 Trace 怎样采集一次应用创建
配套Perfetto 配置采集进程 fork/exec/exit、调度事件、Framework 的 am/wm/dalvik 标记和本应用 Trace。先安装 APK,在 Host 终端 B 载入终端 A 保存的变量,粘贴 4.2 打印的 record 目录,不要再执行 mktemp:
bash
read -r -p '粘贴终端 A 的 record 目录:' chapter4_run
source "$chapter4_run/host-env.sh"
载入的是本次自己生成的文件;找不到它时回看 4.2 的路径和变量保存步骤。先在 B 准备好下面的强停与启动命令,再回到终端 A 开始十二秒采集;A 开始后及时切回 B 执行:
bash
"$chapter4_adb" -s "$chapter4_serial" shell perfetto --txt -c - \
-o /data/misc/perfetto-traces/chapter4-start.perfetto-trace \
< "$chapter4_notes/示例/追踪配置/第四章进程创建.pbtxt"
配置通过标准输入传入,避免把 Host 路径当成设备文件路径。命令保持到采集结束。终端 B 需要设置相同路径与 serial,在十二秒窗口中启动示例;若追踪已启动应用,可先 force-stop 本示例再启动,并记录这个条件:
bash
"$chapter4_adb" -s "$chapter4_serial" shell am force-stop com.example.processdemo
"$chapter4_adb" -s "$chapter4_serial" shell am start -W \
-n com.example.processdemo/.MainActivity
采集结束后在任一已配置变量的 Host 终端拉取:
bash
"$chapter4_adb" -s "$chapter4_serial" pull \
/data/misc/perfetto-traces/chapter4-start.perfetto-trace "$chapter4_run/"
预期 Perfetto 返回成功且文件非空。Kernel 事件支持、采集权限或缓冲区丢失会影响可见性;先检查采集输出和实际事件表,再解释缺失。一次没有 sched_process_exec 的记录,不能无条件证明设备上从未 exec。
可以在 Perfetto UI 中打开 Trace,按应用 PID 和 Ch4/ 标记找回调。也可用工程的 trace_processor;首次执行该包装器可能下载官方解析器:
bash
cd "$chapter4_top"
external/perfetto/tools/trace_processor \
-q "$chapter4_notes/示例/追踪配置/查询进程关系.sql" \
"$chapter4_run/chapter4-start.perfetto-trace"
external/perfetto/tools/trace_processor \
-q "$chapter4_notes/示例/追踪配置/查询应用回调.sql" \
"$chapter4_run/chapter4-start.perfetto-trace"
external/perfetto/tools/trace_processor \
-q "$chapter4_notes/示例/追踪配置/查询进程事件.sql" \
"$chapter4_run/chapter4-start.perfetto-trace"
三份 SQL 分开执行,各自返回一个结果集。进程表中 upid 是解析器区分进程实例的身份,PID 是系统显示的数值;线程另有 utid。若名称尚未更新或发生 PID 复用,应使用应用回调所属线程和 upid 交叉定位。
本轮实测怎样与源码相互印证
本轮设备是 SDK Emulator、Android 17 userdebug、x86_64,串口对应 Kernel 6.12.58 的 gccafb60de224 版本线索。示例通过独立 Host 链构建并安装,未修改系统镜像。下面是实测日志的关键内容摘录,省略了日志前缀和时间字段:
text
event=Application.attachBaseContext pid=2521 ppid=424 tid=2521 uid=10156
event=Application.onCreate pid=2521 ppid=424 tid=2521 uid=10156
event=Activity.onCreate pid=2521 ppid=424 tid=2521 uid=10156
event=Activity.onDestroy pid=2521 ppid=424 tid=2521 uid=10156
event=Activity.onCreate pid=2521 ppid=424 tid=2521 uid=10156
关闭页面后重新打开:PID 仍为 2521,Application 只创建一次,Activity 创建两次。 再用本应用身份 SIGKILL 终止前台进程,重新打开后得到 PID 2590,Application 与 Activity 均重新创建;终止时段没有 Activity.onDestroy 日志。
把真实事件放回时间线上
下图由本轮 Trace 的查询 CSV 按真实时间刻度绘制,属于实测事件图 ,不是 Perfetto UI 截图,也不是前面的机制时序图。以首轮 fork 的 ts=142831801036 ns 为零点,其他位置统一换算为毫秒;不要和串口时间或 logcat 墙钟直接相减。

数据:应用回调、Kernel 事件、进程实例。
读图时按三个问题检查:
- 谁创建进程? 顶部 fork 事件的 parent_tid 为 424,child_tid 为 2521;再与进程表、应用 PID/TID 和 PPID 交叉确认,才能对应到 Zygote 派生应用主任务,而非某次线程创建。
- 重开页面是否创建新进程? 中间在相对
1251.810 ms再次出现同 PID 的 Activity.onCreate,未出现第二次 Application.onCreate。结合日志里的 onDestroy,才能解释为原进程中的页面重建;单凭 Trace 上两个 onCreate 不能猜测中间用户操作。 - 结束进程后发生了什么? 相对
1709.564 ms记录任务 2521 退出,随后在2365.212 ms由同一 Zygote 派生 2590,新进程重新执行应用初始化。
下半图只放大首轮回调。Application.onCreate 开始于 fork 后 68.198 ms,自己的区间仅 0.113 ms;Activity.onCreate 开始于 105.669 ms,区间为 14.304 ms。"fork 到 onCreate 的间隔"与"onCreate 本身耗时"回答的是不同问题。 其间还包含调度、specialize、运行时和 Framework 初始化,不能把整段间隔都归给某一个函数。
这张图没有为 attachApplication、bindApplication 或 finishAttachApplication 填入推测时间。要验证这些节点的实际顺序与耗时,应在 Perfetto UI 展开应用主线程、Binder 线程和 system_server 对应轨道,检查可用的 Framework 标记,必要时增加针对节点的插桩再采集。当前图中的空白不能解释成节点未执行。
| Trace 或 /proc 证据 | 本轮结果 | 解释范围 |
|---|---|---|
| sched_process_fork | Zygote 主任务 424 派生任务 2521 | 与应用 PPID、主线程身份共同确认进程来源 |
| 应用进程身份 | 首轮 PID/TID=2521,重启 PID/TID=2590,PPID=424 | 入口和这些 Java 回调在各自主线程执行 |
| Java Trace 区间 | 首轮 Application.onCreate 0.113 ms;首轮 Activity.onCreate 14.304 ms;复用进程后的 Activity.onCreate 1.798 ms | 这些是回调区间,不是完整冷、热启动总耗时 |
| cgroup | 前台时 cpu、cpuset 为 /top-app;统一层级出现 /apps/uid_10156/pid_2521 | 表示该时刻的资源分组,不是所有设备的固定路径 |
| 进程名采样 | 第二个进程在 process 表中仍显示 zygote64,但应用 Trace 与日志已确认 PID 2590 | 创建初期继承名称与后续采样不完整会影响显示,不能只按名字过滤 |
本轮只证明实际创建与运行行为,没有通过逐函数插桩确认每次请求选择了 Native 快速分支还是 Java 常规分支,也没有主动切换 USAP 配置。因此三条源码分支的说明属于固定源码阅读,不能全部写成本轮路径实测。
看见异常日志时先判断是否阻断主线
| 现象 | 先核对什么 | 对应机制 |
|---|---|---|
| zygote_secondary not found | ro.zygote 和实际导入的 rc 是否只有主 64 位配置,主 Zygote 与 boot_completed 是否正常 | 公共 action 中可以出现此设备没有定义的服务名;本轮单 64 位配置出现该日志但继续启动 |
| 一直没有应用 PID | 组件、安装包、启动限制、ProcessList / Zygote 错误 | 请求可能尚未进入 fork,或创建失败 |
| 有 PID 但没有 Application.onCreate | 先按 4.10 判断未 attach 还是已 bind;检查 failed to attach、failed to complete startup 及主线程堆栈 | PID 已出现不代表初始化完成;启动超时清理与初始化 ANR 要区分 |
| 第二次打开没有新 Application 日志 | 旧进程是否仍在;本次是不是只重建 Activity | 进程复用可能完全符合预期 |
| Trace 有很多 fork | 检查 child TID 所属 TGID 与进程身份 | 其中还包含线程创建和系统其他任务 |
| /proc 权限拒绝 | 是否在本示例的 run-as 身份下,设备是否有额外限制 | 不能因无法读取而断言资源不存在 |
本轮实验结束后已关闭自己创建的隔离 Emulator;示例、APK 构建命令、关键日志与查询结果仍保留供复习。完整证据与验证边界见文末链接。
4.13 回顾 自测与源码速查
把两条链重新接起来
系统启动先把执行交给 Kernel,Kernel 装载 PID 1 的 init。Android init 分阶段建立挂载与安全环境,在第二阶段按 rc 动作管理 Native 服务;其中的 Zygote 服务通过 app_process 准备 ART、进入 ZygoteInit 并预加载公共内容。
主 Zygote 派生 system_server,子进程进入 SystemServer,在同一进程内组织多项系统服务。需要新应用进程时,system_server 准备身份和入口,通过 Unix 域 socket 请求 Zygote 或 USAP。即时派生最终进入 Bionic 和 Kernel 的任务创建;USAP 则把预先派生与使用时的 specialize 分开。
应用子进程完成隔离和运行时准备,进入 ActivityThread.main,建立主 Looper 并通过 Binder 向 ActivityManagerService 报到。系统再经 ApplicationThread 交付 bindApplication,由 Handler 转到主线程创建 Application、安装相关 Provider 并执行初始化,再用 finishAttachApplication 回报完成;系统据配置解除等待、取消超时并继续组件交付。页面结束与进程退出在不同层次发生,后续请求可能复用进程,也可能需要再次创建。
自测 先回答再对照
- system_server 发起创建请求,为什么应用 PPID 通常是 Zygote?
- app_process、ZygoteInit、zygote64 分别是文件、类还是进程角色?
- init 的 first stage、SELinux stage、second stage 为什么可以拥有同一个 PID?
- 为什么 init.rc 中写了 service,不表示读到该行就创建进程?
- startSystemServer 分支返回 Runnable 时,父子进程怎样继续?
- SystemServiceManager 和 ServiceManager 的区别是什么?
- Zygote socket 返回了 PID,能否认为 Application.onCreate 已经完成?
- Java 常规、Native 快速与 USAP 三个分支能否画成连续三步?
- 为什么源码调用 fork,系统调用证据却可能显示 clone?
- COW 为什么既能节省内存又能保持私有写入隔离?
- ActivityThread 为什么不是"新开一条主线程"?
- bindApplication 为什么需要从 Binder 接收线程向主线程发消息?
- 调用 finish 销毁 Activity 后 PID 未变、再次进入 Activity.onCreate,是否矛盾?
- 如何证明一条 sched_process_fork 表示新应用进程,而不是新线程?
- 为什么不能拿 Ch4/Activity.onCreate 的 Trace 时长直接比较整次冷启动性能?
- bindApplication 是 oneway,系统怎样知道应用初始化已经完成?迟迟没有完成会怎样?
- system_server 退出为什么可能引起整个 Android 运行时重建,而 Kernel 仍继续运行?
- 从 Launcher 进入后按返回键,再次打开是否一定会执行 Activity.onCreate?
参考答案:
-
管理端通过 socket 委托 Zygote 派生;谁提出要求与谁执行 fork 是两回事。见 4.1、4.7。
-
app_process 是 Native 可执行程序;ZygoteInit 是其中运行的 Java 入口类;zygote64 是该模式下的进程名称。见 4.5。
-
通过 exec 替换程序映像与参数,成功后 PID 不变。见 4.4。
-
service 是声明,匹配 action 与 start/class_start 等动作才推动启动。见 4.4。
-
父进程保留 Zygote 接收职责;子进程返回并运行 SystemServer 入口包装。见 4.5、4.6。
-
前者组织 Java 系统服务生命周期与 boot phase;后者登记和查找 Binder 服务。见 4.6。
-
不能;父进程返回与子进程初始化可并行,还要经过 attach、bind 与初始化完成确认;组件交付还受配置影响。见 4.7、4.10。
-
不能;它们是条件分支,USAP 使用前可能早已存在 PID。见 4.8。
-
Bionic 用 clone 实现 fork 的库函数语义;注意架构与标志。见 4.8。
-
独立地址空间可暂时映射相同物理页,适用的私有写入经保护异常处理后维持隔离;不是整个 mm 永久共享。见 4.9。
-
主线程是子进程继续运行的线程;ActivityThread 是 Framework 管理对象,main 为它准备 Looper。见 4.10。
-
Binder 负责跨进程入口,应用初始化与组件调度需要在主线程约定下执行,Handler 提供线程交接。见 4.10。
-
不矛盾;Activity 可以销毁和重建,缓存进程和 Application 仍然存活。见 4.11、4.12。
-
关联父/子 TID、TGID、/proc、进程 upid、应用日志与后续主线程;不能只看事件名。见 4.8、4.12。
-
区间只覆盖该方法,完整启动还包括创建、调度、运行时、attach、其他初始化和绘制。见 4.3、4.12。
-
应用主线程在 handleBindApplication 后部调用 finishAttachApplication;等待开启时,管理端校验启动身份并取消初始化超时。迟迟不完成则进入初始化软、硬超时及启动 ANR;提前调度 Activity 是另一个开关。见 4.10。
-
Zygote 的 SIGCHLD 处理发现 system_server 退出后终止自身,init 再按 Zygote service 策略恢复;常规运行时重建不要求 Kernel 重启,连续失败另受退出策略控制。见 4.11。
-
不一定;Android 12 及以后的根 Launcher Activity 默认可能只把任务移到后台,原 Activity 与进程都还在。用 onStop/onDestroy、启动入口及任务状态判断。见 4.11。
按问题寻找源码
| 问题 | 关键入口 |
|---|---|
| Kernel 怎样进入 PID 1 | init/main.c:rest_init、kernel_init、run_init_process |
| init 怎样换阶段 | system/core/init:main、FirstStageMain、SetupSelinux、SecondStageMain |
| rc 怎样变成服务进程 | LoadBootScripts、Service::Start、RunService、ExpandArgsAndExecv |
| Native 怎样进入 Java | app_main.cpp:main;AndroidRuntime::start、startVm、startReg |
| SystemServer 从哪里来 | ZygoteInit.forkSystemServer、handleSystemServerProcess、SystemServer.run |
| 哪一层提出应用创建请求 | ProcessList.startProcessLocked、startProcess、ZygoteProcess.startViaZygote |
| 哪些条件选择创建分支 | shouldAttemptUsapLaunch、ZygoteConnection.processCommand、forkSimpleApps、childMain |
| fork 的底层实现 | zygote::ForkCommon、Bionic fork/_Fork/__clone_for_fork/clone、Kernel kernel_clone/copy_process |
| 内存与身份怎样隔离 | copy_mm、dup_mm、__copy_present_ptes、do_wp_page;SpecializeCommon |
| 应用怎样向系统报到 | ZygoteInit.zygoteInit、RuntimeInit、ActivityThread.main/attach、ActivityManagerService.attachApplication |
| 进程死亡怎样交回管理端 | Zygote SigChldHandler / sendSigChildStatus、ActivityManagerService.AppDeathRecipient / appDiedLocked / handleAppDiedLocked |
| Application 怎样开始 | ApplicationThread.bindApplication、H.handleMessage、handleBindApplication、Instrumentation |
| 系统怎样确认初始化完成 | ActivityThread.handleBindApplication → finishAttachApplication → ActivityManagerService.finishAttachApplicationInner |
| 启动卡住怎样处理 | PROC_START_TIMEOUT_MSG、handleProcessStartOrKillTimeoutLocked、handleBindApplicationTimeoutSoft / Hard |
| 核心运行时怎样恢复 | Zygote SigChldHandler 的 gSystemServerPid 分支、init.zygote64.rc、init Service::Reap |
固定版本源码入口:init、rootdir、app_process、Zygote 与 RuntimeInit、SystemServer、ProcessList、ActivityThread、Bionic。
固定阅读基线与实验材料
| 项目 | 固定提交 | 本章用途 |
|---|---|---|
| frameworks/base | 94b4c163b7dfe5ce3607f7bb8456f9573f7de57d | Zygote、RuntimeInit、SystemServer、应用创建与生命周期 |
| system/core | 545d2487e38192a2ce25040897ced877cf6b4f53 | init、rc、服务创建与回收 |
| bionic | 06356e41c5ed7b12220b24c05ba4fdb873b126a7 | fork 与 clone 的 libc 实现 |
| Kernel common | ccafb60de224d35a8bc14de33e1cd47caaaa786b | PID 1、任务复制、地址空间、COW 与 VirtIO block |
Kernel 是从预编译版本线索选择的公开 ACK 阅读基线;本轮 uname 与该线索吻合,但未重建 Kernel 或逐个验证编译配置。手机 Bootloader、其他 ABI、App/WebView Zygote、USAP 切换、完整启动存储 I/O 与内存压力杀进程不在本轮设备实验的覆盖范围内。
- 示例源码包 · 独立 APK 构建脚本
- 初版源码与构建校验 · 2026-10-20 修订校验:源码摘录、示例与当前图文检查。
- 设备与 Trace 实测:本轮隔离 Emulator 的环境、命令、日志、事件与结论。
- 第二章 Binder · 第三章 Handler · 第六章组件管理阅读入口 · 第九章ART与内存阅读入口
附录:可独立保存的构建脚本与 Trace 配置
正文 4.3 已给出示例的 Android.bp、AndroidManifest.xml 和两个 Java 文件。没有本地配套目录的读者,先按正文设置 chapter4_notes,建立对应目录并保存这些文件;下面把独立构建脚本、Perfetto 配置和三份 SQL 也完整列出。文件名与正文命令一致,保存后再回到 4.3 构建,或回到 4.12 采集。这里是可复制的配套文件,不是本次重新运行实验的输出。
示例/构建示例APK.py
python
#!/usr/bin/env python3
"""用已构建的 AOSP Host 工具打包本章无资源示例,不修改系统镜像。"""
import argparse
import json
from pathlib import Path
import subprocess
import zipfile
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--aosp-top", type=Path, required=True)
parser.add_argument("--module", type=Path, required=True)
parser.add_argument("--name", required=True)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--target-sdk", default="36")
args = parser.parse_args()
top = args.aosp_top.resolve()
module = args.module.resolve()
output = args.out.resolve()
if not (module / "AndroidManifest.xml").is_file():
parser.error("module 需要指向包含 AndroidManifest.xml 的示例目录")
if output.exists() and any(output.iterdir()):
parser.error("out 目录需为空;请为本轮构建使用新目录")
output.mkdir(parents=True, exist_ok=True)
host = top / "out/host/linux-x86"
sdk = top / "prebuilts/sdk/current/public/android.jar"
java = top / "prebuilts/jdk/jdk21/linux-x86/bin/java"
javac = java.with_name("javac")
jar = java.with_name("jar")
required = [host / "bin/aidl", host / "bin/aapt2", host / "framework/d8.jar",
host / "framework/signapk.jar", sdk, java, javac, jar,
host / "lib64/libconscrypt_openjdk_jni.so",
top / "build/make/target/product/security/testkey.x509.pem",
top / "build/make/target/product/security/testkey.pk8"]
missing = [str(path) for path in required if not path.is_file()]
if missing:
parser.error("缺少已构建的 Host 工具或材料:\n" + "\n".join(missing))
commands = []
def run(command):
command = [str(value) for value in command]
subprocess.run(command, check=True, cwd=top)
commands.append(command)
generated, classes, dex = [output / name for name in ("generated", "classes", "dex")]
for directory in (generated, classes, dex):
directory.mkdir()
for aidl in sorted((module / "src").rglob("*.aidl")):
run([host / "bin/aidl", "--lang=java", "--min_sdk_version=28",
"-I", module / "src", "-o", generated, aidl])
sources = sorted((module / "src").rglob("*.java")) + sorted(generated.rglob("*.java"))
run([javac, "--release", "17", "-cp", sdk, "-d", classes, *sources])
run([jar, "cf", output / "classes.jar", "-C", classes, "."])
run([java, "-cp", host / "framework/d8.jar", "com.android.tools.r8.D8",
"--lib", sdk, "--min-api", "28", "--output", dex, output / "classes.jar"])
run([host / "bin/aapt2", "link", "-I", sdk, "--manifest", module / "AndroidManifest.xml",
"--min-sdk-version", "28", "--target-sdk-version", args.target_sdk,
"-o", output / "unsigned.apk"])
with zipfile.ZipFile(output / "unsigned.apk", "a", zipfile.ZIP_DEFLATED) as archive:
for path in sorted(dex.glob("*.dex")):
archive.write(path, path.name)
apk = output / (args.name + ".apk")
run([java, "-Djava.library.path=" + str(host / "lib64"), "-jar",
host / "framework/signapk.jar",
top / "build/make/target/product/security/testkey.x509.pem",
top / "build/make/target/product/security/testkey.pk8", output / "unsigned.apk", apk])
(output / "build-commands.json").write_text(json.dumps(commands, ensure_ascii=False, indent=2))
print(apk)
if __name__ == "__main__":
main()
示例/追踪配置/第四章进程创建.pbtxt
text
buffers { size_kb: 16384 fill_policy: RING_BUFFER }
duration_ms: 12000
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_process_fork"
ftrace_events: "sched/sched_process_exec"
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
atrace_categories: "am"
atrace_categories: "wm"
atrace_categories: "dalvik"
atrace_apps: "com.example.processdemo"
}
}
}
data_sources {
config {
name: "linux.process_stats"
process_stats_config { scan_all_processes_on_start: true proc_stats_poll_ms: 100 }
}
}
示例/追踪配置/查询进程关系.sql
sql
-- 同时用进程名与应用 Trace 区间定位,避免刚 fork 时的 zygote64 名称造成漏查。
WITH app_upids AS (
SELECT DISTINCT t.upid
FROM slice s
JOIN thread_track tt ON tt.id = s.track_id
JOIN thread t ON t.utid = tt.utid
WHERE s.name GLOB 'Ch4/*'
)
SELECT p.upid, p.pid, p.name, p.parent_upid, parent.pid AS parent_pid,
parent.name AS parent_name, p.start_ts, p.end_ts
FROM process p
LEFT JOIN process parent ON parent.upid = p.parent_upid
WHERE p.name GLOB '*processdemo*' OR p.upid IN (SELECT upid FROM app_upids)
ORDER BY p.upid;
示例/追踪配置/查询应用回调.sql
sql
-- 示例 Java 回调的实际执行线程与持续时间。
SELECT p.pid, t.tid, t.name AS thread_name, s.name,
s.ts, ROUND(s.dur / 1000000.0, 3) AS duration_ms
FROM slice s
JOIN thread_track tt ON tt.id = s.track_id
JOIN thread t ON t.utid = tt.utid
JOIN process p ON p.upid = t.upid
WHERE s.name GLOB 'Ch4/*'
ORDER BY s.ts;
示例/追踪配置/查询进程事件.sql
sql
-- Kernel 创建/执行/退出事件。child_pid 是创建出的任务 TID,未必是新进程 PID。
SELECT f.ts, f.name, f.utid,
EXTRACT_ARG(f.arg_set_id, 'parent_pid') AS parent_tid,
EXTRACT_ARG(f.arg_set_id, 'child_pid') AS child_tid,
EXTRACT_ARG(f.arg_set_id, 'pid') AS event_tid,
EXTRACT_ARG(f.arg_set_id, 'filename') AS exec_filename
FROM ftrace_event f
WHERE f.name IN ('sched_process_fork', 'sched_process_exec', 'sched_process_exit')
ORDER BY f.ts;