Android源码第四章:Android 系统启动——App 的第一行代码从哪来?

0 阅读1小时+

打开一个应用时,究竟是谁创建了它的进程?为什么应用进程的父进程通常是 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 中协调应用进程创建。

Android 系统启动整体架构:标准层级中的模块、进程归属及启动、socket 和 Binder 通道

沿棕色线看启动与派生: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 进程,交付初始化任务并收到完成确认。

Android 启动全章总时序:从启动环境、Kernel、init 与 Zygote 到 SystemServer 和 App 初始化确认

图中选的是普通应用即时 fork、等待初始化完成确认开启、Activity 不提前交付的路径。读到并行区时,分别沿父进程和应用子进程看:PID 回执与应用报到没有固定先后;bindApplication 的入队也可以和 attachApplication 返回并行。 确认完成表示系统可以继续交付等待的组件,页面首帧是另一个节点。

下面的启动全景图只概括阶段;4.7 的时序图细看一次应用进程创建,4.8 的流程图展开创建分支,4.10 的时序图细看 Binder 接收线程怎样把初始化交给主线程。

Android 启动不是一路调用 Java main

Kernel 是操作系统内核,负责调度、内存、文件系统和设备等基础能力;Android 的 init、Zygote、system_server 和应用运行在用户空间。它们需要内核提供能力,但内核不会解释 Android 的 Activity 或 Application。

物理设备上,Bootloader 负责选择和准备启动材料,并把执行交给 Kernel。SDK Emulator 则由运行器和虚拟设备环境加载配套 Kernel、ramdisk 与磁盘镜像,不能把它的启动过程当成某款手机的 Bootloader 实现。官方 Bootloader 概览

下图是主要依赖与交接关系,方向遵循实际启动过程。系统还并行启动其他 Native 服务,图中只保留本章主线。

Android启动全景

创建进程、准备运行时、创建 Application 和显示页面,是不同阶段。 系统收到一个 PID,只能说明某次进程创建已有结果;它还需要等新进程报到,交付配置和组件事务,才能把页面真正启动起来。

再单独看谁是谁的父进程

进程树画的是父子关系,不能拿来表示 Binder 请求的方向。以单个主 Zygote 的普通场景为例:

进程父子关系

system_server 提出创建要求,Zygote 执行派生,应用因此通常是 Zygote 的子进程。 普通 Java 应用不会因为由 ActivityManagerService 管理,就变成它的 Linux 子进程。WebView Zygote、App Zygote、隔离进程等有各自分支,后面会指出入口。

名称它是什么本章需要记住的边界
initAndroid 用户空间的早期初始化与 Native 服务管理进程主 init 的 PID 是 1;它还能创建其他辅助进程
app_process可以执行的 Native 程序按参数进入 Zygote 等运行模式,文件名不等于运行后的进程名
Zygote已准备 ART、预加载公共内容并接受创建请求的进程它保留父进程职责,派生出的子进程继续进入各自入口
SystemServersystem_server 内的 Java 启动类多个系统服务共享该进程,启动一个服务通常不是再 fork 一次
ActivityThread普通应用进程的 Framework 入口与生命周期调度对象类名中的 Thread 不表示创建一个新的主线程
Application / ActivityFramework 创建的 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 都只针对本章示例应用。

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 或重新打包系统镜像。

用属性与进程表确认实际拓扑

"$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/rootdirinit 阶段、rc 语言、Native 服务创建与重启
frameworks/base/cmds/app_process/app_main.cppNative 程序入口和运行模式选择
frameworks/base/core/jni/AndroidRuntime.cppART 创建、JNI 注册、调用 Java main
frameworks/base/core/java/com/android/internal/osZygoteInit、ZygoteServer、ZygoteConnection、RuntimeInit
frameworks/base/core/java/android/os/ZygoteProcess.javasystem_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.cPID 1、任务创建、地址空间与写时复制

Android 用户空间项目与 Kernel 源码需分别确认提交。顶层存在 kernel 目录,不表示完整 ACK 源码已经下载。本章的 Kernel 固定链接可直接阅读;本轮校验材料另保存了所选文件的摘要。

4.3 运行一个能看见进程生命周期的示例

先观察 再带着 PID 阅读源码

示例只有一个进程。它在 Application 与 Activity 生命周期中记录 PID、PPID、TID、UID 和时间;页面提供“关闭当前页面”和“结束当前进程”两个操作。这样可以直接比较组件结束和进程结束,而不依赖猜测。

完整代码也保存在示例目录,源码包方便复制到独立实验树。下面四个文件构成整个工程;展开目录使用 Android.bp.example,防止知识库被 Soong 扫描出一个重复模块,源码包内已改为 Android.bp。

Android.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

<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

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

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 构建产物。

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)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

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 阶段准备策略与域切换;第二阶段继续属性、脚本、事件和服务管理。实际任务受设备启动形态影响,不能把所有挂载都归到某一个固定阶段。

init阶段与exec

main 根据参数分派,而非为每个阶段都 fork:

来源:main.cpp · int main(int argc, char** argv)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};

同一函数后面调用 execv;SetupSelinux 完成自己的工作后,再以新的参数 exec:

来源:selinux.cpp · int SetupSelinux(char** argv)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

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 选择配置。先看实际导入语句:

import /system/etc/init/hw/init.${ro.zygote}.rc

本轮 ro.zygote=zygote64,因此展开后导入 init.zygote64.rc;其他设备可能采用不同配置,不能只凭文件名猜测实际拓扑。late-init 动作依次触发文件系统、data、BPF、zygote-start 等阶段;下面是 zygote-start 配置的连续原文:

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 开头的连续原文:

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()。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

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。

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。

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。

可在设备上查看:

"$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)。以下为该函数内的连续原文摘录。

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[])。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

env->CallStaticVoidMethod(startClass, startMeth, strArray);

JNI 调用没有创建第二个 Zygote 进程,也没有自动更换线程。 同一个进程沿 Native 入口进入 Java 的 ZygoteInit.main;运行时、类和方法已经准备好,才能执行这次 Java 调用。

预加载为什么放在 fork 之前

主 Zygote 通常先预加载部分公共类与资源。ZygoteInit.main 的非延迟预加载分支会执行:

来源:ZygoteInit.java · public static void main(String[] argv)。以下为该函数内的连续原文摘录。

preload(bootTimingsTraceLog);

preload 的具体内容以该版本函数为准,包含类、资源和其他共享初始化。这样子进程可继承准备好的运行时状态,并在适当条件下共享未修改页面。它减少重复工作,但预加载本身也有开机时间与内存成本;应用私有类和全部应用业务不会因此提前执行。

Zygote 之后创建 ZygoteServer。其主构造分支通过 Zygote.createManagedSocketFromInitSocket 接收并包装前面 init 准备的 Zygote 与 USAP socket 描述符,连接起 rc 声明和 Java 接收端。对应构造方法

需要启动 SystemServer 时先派生它,父进程随后进入接收循环。整体结构的关键片段是:

来源:ZygoteInit.java · public static void main(String[] argv)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

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,。以下为该函数内的连续原文摘录。

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,。以下为该函数内的连续原文摘录。

if (pid == 0) {
    if (hasSecondZygote(abiList)) {
        waitForSecondaryZygote(socketName);
    }

    zygoteServer.closeServerSocket();
    return handleSystemServerProcess(parsedArgs);
}

handleSystemServerProcess 继续处理名称、classpath 和 ClassLoader,再交给共用的运行时入口:

来源:ZygoteInit.java · private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

public static void main(String[] args) {
    new SystemServer().run();
}

run 里准备系统环境、主 Looper、SystemContext 和 SystemServiceManager,再分阶段启动服务:

来源:SystemServer.java · private void run()。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

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,。以下为该函数内的连续原文摘录。

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。以下为该函数内的连续原文摘录。

mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);

PowerManagerService.onStart 发布跨进程 Binder 接口和进程内 LocalService,并登记 Watchdog:

来源:PowerManagerService.java · onStart。以下为该函数内的连续原文摘录。

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()。以下为该函数内的连续原文摘录。

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,。以下为该函数内的连续原文摘录。

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,。以下为该函数内的连续原文摘录。

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(。以下为该函数内的连续原文摘录。

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(。以下为该函数内的连续原文摘录。

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 常规创建收到创建请求后 forkZygoteConnection.processCommand → Zygote.forkAndSpecialize
Native 快速创建收到符合条件的请求后 fork,可减少反复回到 Java 的开销forkSimpleApps → ZygoteCommandBuffer.forkRepeatedly → nativeForkRepeatedly
USAP 池池补充时先派生,接到应用请求时完成身份与运行时专门化ZygoteServer 补池、Zygote.forkUsap、childMain

三者是备选分支。USAP 是 Unspecialized App Process,即尚未针对具体应用完成身份等配置的应用进程。取出池中进程时已有 PID,不能在那一刻再画一次必经 fork;池补充会另行创建任务。官方 Zygote 介绍

应用进程创建流程:Java 常规、Native 快速、USAP 池、回退及失败出口

沿图先看 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)。以下为该函数内的连续原文摘录。

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(。以下为该函数内的连续原文摘录。

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(。以下为该函数内的连续原文摘录。

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,。以下为该函数内的连续原文摘录。

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,。以下为该函数内的连续原文摘录。

pid_t pid = fork();

fork 的父子返回语义由 libc 与 Kernel 配合实现。Bionic 在 fork 周围执行 atfork 协调:

来源:fork.cpp · int fork()。以下为该函数内的连续原文摘录。

__bionic_atfork_run_prepare();
int result = _Fork();

_Fork 继续进入:

来源:fork.cpp · int _Fork()。以下为该函数内的连续原文摘录。

int _Fork() {
  return __clone_for_fork();
}

__clone_for_fork 使用 clone 并传入子任务 TID 相关标志:

来源:fork.cpp · int __clone_for_fork()。以下为该函数内的连续原文摘录。

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, ...)。以下为该函数内的连续原文摘录。

  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)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

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(。以下为该函数内的连续原文摘录。

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 复制并更新映射。不能笼统说“所有缺页都复制”或“每次写入都复制”。

COW映射变化

这张图表示映射关系变化,不是两个进程共享同一个可直接互相修改的 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,。以下为该函数内的连续原文摘录。

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,。以下为该函数内的连续原文摘录。

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 不表示同一个东西。

本示例可用自己的身份查看:

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,。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{
    gCurRuntime->onZygoteInit();
}

AppRuntime::onZygoteInit 准备 Binder 进程状态和接收线程池:

来源:app_main.cpp · virtual void onZygoteInit()。以下为该函数内的连续原文摘录。

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,。以下为该函数内的连续原文摘录。

return findStaticMain(args.startClass, args.startArgs, classLoader);

findStaticMain 查找该类的静态 main 方法,并返回一个 Runnable 包装:

来源:RuntimeInit.java · protected static Runnable findStaticMain(String className, String[] argv,。以下为该函数内的连续原文摘录。

return new MethodAndArgsCaller(m, argv);

这个包装最终被执行时,通过反射调用入口:

来源:RuntimeInit.java · public void run()。以下为该函数内的连续原文摘录。

mMethod.invoke(null, new Object[] { mArgs });

因此,创建请求里的字符串、RuntimeInit 的类与方法查找、Runnable 的执行共同组成了这次 Java 入口交接。这里用 Runnable 表示稍后执行的工作,并没有因为它实现了 Runnable 就创建线程。

这个 Runnable 最后由谁执行? 选 Java 常规创建、普通应用且没有 wrapper 的路径往回看:processCommand 的子进程分支返回 handleChildProc 的结果;handleChildProc 先调用 ZygoteInit.zygoteInit,完成前述 Binder 准备并取得应用入口包装:

来源:ZygoteConnection.java · handleChildProc。以下为连续原文摘录。

return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
        parsedArgs.mDisabledCompatChanges,
        parsedArgs.mEnabledCompatChanges,
        parsedArgs.mRemainingArgs, null /* classLoader */);

这个包装沿调用栈回到 ZygoteServer.runSelectLoop。它识别出当前已经是子进程,在 mIsForkChild 分支返回 command:

来源:ZygoteServer.java · runSelectLoop。以下为连续原文摘录。

return command;

返回到 ZygoteInit.main 后,真正执行入口包装的是下面的 caller.run:

来源:ZygoteInit.java · main。以下为连续原文摘录。

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)。以下为该函数内的连续原文摘录。

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

随后同一线程进入:

来源:ActivityThread.java · public static void main(String[] args)。以下为该函数内的连续原文摘录。

Looper.loop();

ActivityThread 是管理对象;构造它不等于 new Thread().start()。主线程本来就是新进程继续运行的线程。Looper.prepareMainLooper 把消息队列准备好,attach 把应用连接到系统,Looper.loop 再持续分派消息。

attach 中通过 ActivityManager 的 Binder 接口交出 mAppThread:

来源:ActivityThread.java · private void attach(boolean system, long startSeq)。以下为该函数内的连续原文摘录。

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(。以下为该函数内的连续原文摘录。

sendMessage(H.BIND_APPLICATION, data);

应用主线程的 H.handleMessage 取出并处理:

来源:ActivityThread.java · public void handleMessage(Message msg)。以下为该函数内的连续原文摘录。

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)。以下为该函数内的连续原文摘录。

app = data.info.makeApplicationInner(data.restrictedBackupMode, null);

普通启动过程中,相关 ContentProvider 的安装可能先于 Application.onCreate;构造、静态初始化和 attachBaseContext 也可能更早执行。因此 Application.onCreate 适合作为一个应用初始化观察点,但不是“整个应用所有 Java 代码的绝对第一行”。

随后调用:

来源:ActivityThread.java · private void handleBindApplication(AppBindData data)。以下为该函数内的连续原文摘录。

mInstrumentation.callApplicationOnCreate(app);

Application 初始化完成后 系统怎样解除等待

attachApplication 只说明应用进程已经报到,bindApplication 是系统向应用交付初始化任务;oneway 调用返回并不等于主线程已经执行完初始化。 因此当前版本还需要从应用回到系统的一次完成确认。

handleBindApplication 在执行 Application.onCreate、恢复线程策略及处理字体预加载等后续工作后,调用:

来源:ActivityThread.java · handleBindApplication。以下为该函数内的连续原文摘录。

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。以下为该函数内的连续原文摘录。

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。以下为该函数内的连续原文摘录。

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。以下为该函数内的连续原文摘录。

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*/)。以下为教学性删节,保留回收循环与通知动作;省略位置已用注释标明。

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() {。以下为该函数内的连续原文摘录。

synchronized (mGlobalLock) {
    appDiedLocked(mApp, mPid, mAppThread, true, null);
}

appDiedLocked 还需要核对 PID、线程接口和当前进程记录,避免把过期死亡事件当成新进程退出;在有效死亡处理路径中继续调用:

来源:ActivityManagerService.java · final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,。以下为该函数内的连续原文摘录。

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()。以下为该函数内的连续原文摘录。

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。以下为该函数内的连续原文摘录。

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 对照新的回调:

"$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:

"$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 日志。已有设备可能没有保留早期日志;不要为了补这份证据去重启正在使用的设备。

"$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 sRun /init as init processKernel 开始执行初始用户空间入口
3.384432 sinit second stage started已进入 Android 第二阶段 init
8.538349 sprocessing action (zygote-start)init 开始执行相应启动动作
8.546874 sstarting service 'zygote'init 提交 Zygote 服务创建,不表示 Java 预加载已完成

这些值来自一次独立 Emulator 冷启动,不能作为其他设备的性能基准。详细原始输出、环境和命令见本轮设备记录。

十二秒 Trace 怎样采集一次应用创建

配套Perfetto 配置采集进程 fork/exec/exit、调度事件、Framework 的 am/wm/dalvik 标记和本应用 Trace。先安装 APK,在 Host 终端 B 载入终端 A 保存的变量,粘贴 4.2 打印的 record 目录,不要再执行 mktemp:

read -r -p '粘贴终端 A 的 record 目录:' chapter4_run
source "$chapter4_run/host-env.sh"

载入的是本次自己生成的文件;找不到它时回看 4.2 的路径和变量保存步骤。先在 B 准备好下面的强停与启动命令,再回到终端 A 开始十二秒采集;A 开始后及时切回 B 执行:

"$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 本示例再启动,并记录这个条件:

"$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 终端拉取:

"$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;首次执行该包装器可能下载官方解析器:

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 链构建并安装,未修改系统镜像。下面是实测日志的关键内容摘录,省略了日志前缀和时间字段:

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 墙钟直接相减。

同一次 Trace 中的进程创建、页面重建与进程重建

数据:应用回调、Kernel 事件、进程实例。

读图时按三个问题检查:

  1. 谁创建进程? 顶部 fork 事件的 parent_tid 为 424,child_tid 为 2521;再与进程表、应用 PID/TID 和 PPID 交叉确认,才能对应到 Zygote 派生应用主任务,而非某次线程创建。
  2. 重开页面是否创建新进程? 中间在相对 1251.810 ms 再次出现同 PID 的 Activity.onCreate,未出现第二次 Application.onCreate。结合日志里的 onDestroy,才能解释为原进程中的页面重建;单凭 Trace 上两个 onCreate 不能猜测中间用户操作。
  3. 结束进程后发生了什么? 相对 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_forkZygote 主任务 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 foundro.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 回报完成;系统据配置解除等待、取消超时并继续组件交付。页面结束与进程退出在不同层次发生,后续请求可能复用进程,也可能需要再次创建。

自测 先回答再对照

  1. system_server 发起创建请求,为什么应用 PPID 通常是 Zygote?
  2. app_process、ZygoteInit、zygote64 分别是文件、类还是进程角色?
  3. init 的 first stage、SELinux stage、second stage 为什么可以拥有同一个 PID?
  4. 为什么 init.rc 中写了 service,不表示读到该行就创建进程?
  5. startSystemServer 分支返回 Runnable 时,父子进程怎样继续?
  6. SystemServiceManager 和 ServiceManager 的区别是什么?
  7. Zygote socket 返回了 PID,能否认为 Application.onCreate 已经完成?
  8. Java 常规、Native 快速与 USAP 三个分支能否画成连续三步?
  9. 为什么源码调用 fork,系统调用证据却可能显示 clone?
  10. COW 为什么既能节省内存又能保持私有写入隔离?
  11. ActivityThread 为什么不是“新开一条主线程”?
  12. bindApplication 为什么需要从 Binder 接收线程向主线程发消息?
  13. 调用 finish 销毁 Activity 后 PID 未变、再次进入 Activity.onCreate,是否矛盾?
  14. 如何证明一条 sched_process_fork 表示新应用进程,而不是新线程?
  15. 为什么不能拿 Ch4/Activity.onCreate 的 Trace 时长直接比较整次冷启动性能?
  16. bindApplication 是 oneway,系统怎样知道应用初始化已经完成?迟迟没有完成会怎样?
  17. system_server 退出为什么可能引起整个 Android 运行时重建,而 Kernel 仍继续运行?
  18. 从 Launcher 进入后按返回键,再次打开是否一定会执行 Activity.onCreate?

参考答案:

  1. 管理端通过 socket 委托 Zygote 派生;谁提出要求与谁执行 fork 是两回事。见 4.1、4.7。

  2. app_process 是 Native 可执行程序;ZygoteInit 是其中运行的 Java 入口类;zygote64 是该模式下的进程名称。见 4.5。

  3. 通过 exec 替换程序映像与参数,成功后 PID 不变。见 4.4。

  4. service 是声明,匹配 action 与 start/class_start 等动作才推动启动。见 4.4。

  5. 父进程保留 Zygote 接收职责;子进程返回并运行 SystemServer 入口包装。见 4.5、4.6。

  6. 前者组织 Java 系统服务生命周期与 boot phase;后者登记和查找 Binder 服务。见 4.6。

  7. 不能;父进程返回与子进程初始化可并行,还要经过 attach、bind 与初始化完成确认;组件交付还受配置影响。见 4.7、4.10。

  8. 不能;它们是条件分支,USAP 使用前可能早已存在 PID。见 4.8。

  9. Bionic 用 clone 实现 fork 的库函数语义;注意架构与标志。见 4.8。

  10. 独立地址空间可暂时映射相同物理页,适用的私有写入经保护异常处理后维持隔离;不是整个 mm 永久共享。见 4.9。

  11. 主线程是子进程继续运行的线程;ActivityThread 是 Framework 管理对象,main 为它准备 Looper。见 4.10。

  12. Binder 负责跨进程入口,应用初始化与组件调度需要在主线程约定下执行,Handler 提供线程交接。见 4.10。

  13. 不矛盾;Activity 可以销毁和重建,缓存进程和 Application 仍然存活。见 4.11、4.12。

  14. 关联父/子 TID、TGID、/proc、进程 upid、应用日志与后续主线程;不能只看事件名。见 4.8、4.12。

  15. 区间只覆盖该方法,完整启动还包括创建、调度、运行时、attach、其他初始化和绘制。见 4.3、4.12。

  16. 应用主线程在 handleBindApplication 后部调用 finishAttachApplication;等待开启时,管理端校验启动身份并取消初始化超时。迟迟不完成则进入初始化软、硬超时及启动 ANR;提前调度 Activity 是另一个开关。见 4.10。

  17. Zygote 的 SIGCHLD 处理发现 system_server 退出后终止自身,init 再按 Zygote service 策略恢复;常规运行时重建不要求 Kernel 重启,连续失败另受退出策略控制。见 4.11。

  18. 不一定;Android 12 及以后的根 Launcher Activity 默认可能只把任务移到后台,原 Activity 与进程都还在。用 onStop/onDestroy、启动入口及任务状态判断。见 4.11。

按问题寻找源码

问题关键入口
Kernel 怎样进入 PID 1init/main.c:rest_init、kernel_init、run_init_process
init 怎样换阶段system/core/init:main、FirstStageMain、SetupSelinux、SecondStageMain
rc 怎样变成服务进程LoadBootScripts、Service::Start、RunService、ExpandArgsAndExecv
Native 怎样进入 Javaapp_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/base94b4c163b7dfe5ce3607f7bb8456f9573f7de57dZygote、RuntimeInit、SystemServer、应用创建与生命周期
system/core545d2487e38192a2ce25040897ced877cf6b4f53init、rc、服务创建与回收
bionic06356e41c5ed7b12220b24c05ba4fdb873b126a7fork 与 clone 的 libc 实现
Kernel commonccafb60de224d35a8bc14de33e1cd47caaaa786bPID 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

#!/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

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

-- 同时用进程名与应用 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

-- 示例 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

-- 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;