Skip to content

崩溃恢复链

从 Java 异常、Watchdog 和 native crash 追踪 system_server 到 Zygote、init 的恢复链,以及 crash loop 的终止边界。

基于android-17.0.0_r1
AndroidSystemServerZygoteinitWatchdog崩溃恢复源码阅读

崩溃恢复链 ​

本文面向已经读过 SystemServer.run、ZygoteInit.main、SIGCHLD 进程死亡处理 和 首次启动与普通启动 的读者。前文分别介绍了启动入口、进程创建和启动场景;本文只回答一个更窄的问题:system_server 已经运行后发生 Java 异常、Watchdog 超时或 native crash,哪个对象先观察到退出,谁决定杀掉谁,谁负责再次启动,以及反复失败时何时停止重启。

这里的“恢复”不是一个统一的 restart API。Java 未捕获异常负责留下 crash 日志并终止当前进程;Watchdog 负责在 system_server 卡住时收集线程现场并终止进程;native Zygote 负责收割 system_server 子进程并让自己退出;init 负责依据 service 状态重新拉起 Zygote。每一层都有自己的 owner、状态和生效时机。把它们混为“AMS 收到崩溃后重启 system_server”,会漏掉真正的父子进程关系和 crash-loop 保护。

1. 恢复边界 ​

system_server 是由 primary Zygote fork 出来的子进程。它退出时,直接父进程不是 init,而是 Zygote;但是 Zygote 不能继续作为“正常父进程”运行,因为它的关键子进程已经消失。因此恢复链是:

图中的两个动作不要颠倒:system_server 退出只产生 SIGCHLD;是 Zygote 的 native handler 识别到保存的 system-server PID 后才主动杀掉 Zygote。init 看到的是 Zygote service 退出,而不是直接看到一次“重启 SystemServer”命令。

层owner观察对象关键输出是否负责再次启动
Java runtimeRuntimeInit未捕获异常crash log、sys.system_server.crash_java否
system_server watchdogWatchdogHandler/monitor 是否完成thread dump、DropBox、watchdog atom否,终止自己
Zygote nativeSigChldHandler子进程 waitpid() 状态Process ... exited、SIGKILL Zygote否,触发父级退出
init service managerService::ReapZygote service 退出onrestart、crash count、critical 处置是

2. Java异常 ​

Java 层的入口在 RuntimeInit.commonInit() 注册。这里的 pre-handler 只负责日志,default handler 才负责停止进程;它并不调用 init,也不直接创建新的 Zygote。

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

相关函数:RuntimeInit.commonInit

java
protected static final void commonInit() {
    if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");

    // 本文注:pre-handler 与 default handler 分工,前者不能替代后者。
    LoggingHandler loggingHandler = new LoggingHandler();
    RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
    Thread.setDefaultUncaughtExceptionHandler(
            new KillApplicationHandler(loggingHandler));

    // ... 设置时区、java.util.logging、HTTP User-Agent 和 socket tagging。
}

RuntimeHooks.setUncaughtExceptionPreHandler() 对所有线程生效,应用可以替换 default handler,但不能替换这个 pre-handler。SystemServer 通过 Zygote 进入同一套 runtime 初始化,因此 system_server 线程的未捕获异常也先经过 LoggingHandler。

相关类:RuntimeInit.LoggingHandler

java
private static class LoggingHandler
        implements Thread.UncaughtExceptionHandler {
    public volatile boolean mTriggered = false;

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        mTriggered = true;

        // 本文注:crash handler 重入时不重复写日志。
        if (mCrashing) return;

        // mApplicationObject == null 且 UID 为 SYSTEM 才是 system_server 特殊日志。
        if (mApplicationObject == null
                && Process.SYSTEM_UID == Process.myUid()) {
            Clog_e(TAG,
                    "*** FATAL EXCEPTION IN SYSTEM PROCESS: "
                            + t.getName(), e);
            mCrashCount = SystemProperties.getInt(
                    SYSPROP_CRASH_COUNT, 0) + 1;
            SystemProperties.set(
                    SYSPROP_CRASH_COUNT,
                    String.valueOf(mCrashCount));
        } else {
            logUncaught(t.getName(),
                    ActivityThread.currentProcessName(),
                    Process.myPid(), e);
        }
    }
}

这里的 owner 是 runtime 进程内的静态字段。mApplicationObject == null 是区分 system_server 特殊日志格式的条件,不能简化成“UID 为 1000 就一定是 system_server”,因为 system UID 的其他 Java 程序也可能满足部分条件。sys.system_server.crash_java 只在这条日志路径递增,保存的是 Java crash 次数,不是所有 crash 的总数。

相关类:RuntimeInit.KillApplicationHandler

java
private static class KillApplicationHandler
        implements Thread.UncaughtExceptionHandler {
    private final LoggingHandler mLoggingHandler;

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        try {
            ensureLogging(t, e);

            // 本文注:先置位再执行报告,防止报告路径再次触发无限递归。
            if (mCrashing) return;
            mCrashing = true;

            if (ActivityThread.currentActivityThread() != null) {
                // profiling buffer 在进程被杀前尽量刷新。
                ActivityThread.currentActivityThread()
                        .stopProfiling();
            }

            // system_server 的 ActivityManager Binder 可能已不可用。
            ActivityManager.getService()
                    .handleApplicationCrash(
                            mApplicationObject,
                            new ApplicationErrorReport
                                    .ParcelableCrashInfo(e));
        } catch (Throwable t2) {
            if (!(t2 instanceof DeadObjectException)) {
                try {
                    Clog_e(TAG,
                            "Couldn't report crash. Here's the crash:", e);
                    Clog_e(TAG,
                            "Error reporting crash. Here's the error:", t2);
                } catch (Throwable ignored) {
                    // 日志系统也失败时不能再依赖报告链。
                }
            }
        } finally {
            // 无论 AMS 报告是否成功,都必须让 system_server 消失。
            Process.killProcess(Process.myPid());
            System.exit(10);
        }
    }
}

这段代码有两个提交顺序。第一,ensureLogging() 保障日志先于报告;第二,finally 无条件执行进程终止。handleApplicationCrash() 不是恢复入口,它最多显示/记录 crash;如果 AMS 本身已死,DeadObjectException 会被忽略,不能阻止 killProcess()。进程退出后,恢复责任转移到 Zygote 和 init。

3. Watchdog ​

Java 异常是“线程主动结束”,Watchdog 是“线程没有在期限内完成”。Watchdog 线程本身位于 system_server,但它不直接修复阻塞的服务;它在收集现场后决定是否杀死 system_server。

源码文件:frameworks/base/services/core/java/com/android/server/Watchdog.java

相关函数:Watchdog.run

java
private void run() {
    boolean preWatchdogTriggered = false;
    Throttler preWatchdogThrottler = new Throttler(
            SystemClock.uptimeClock(),
            PRE_WATCHDOG_COOL_OFF_MILLIS);

    while (true) {
        // 本文注:每轮使用同一个 timeout 快照,避免运行中配置变化造成混合判断。
        final long watchdogTimeoutMillis =
                mWatchdogTimeoutMillis;
        final long checkIntervalMillis =
                watchdogTimeoutMillis
                        / PRE_WATCHDOG_TIMEOUT_RATIO;
        List<HandlerChecker> blockedCheckers =
                Collections.emptyList();
        boolean allowRestart = true;

        synchronized (mLock) {
            long timeout = checkIntervalMillis;
            for (int i = 0;
                    i < mHandlerCheckers.size(); i++) {
                HandlerCheckerAndTimeout hc =
                        mHandlerCheckers.get(i);
                hc.checker().scheduleCheckLocked(
                        hc.customTimeoutMillis().orElse(
                                watchdogTimeoutMillis
                                        * Build.HW_TIMEOUT_MULTIPLIER));
            }

            // uptime 不计算 deep sleep,避免休眠期间误杀。
            long start = SystemClock.uptimeMillis();
            while (timeout > 0) {
                try {
                    mLock.wait(timeout);
                } catch (InterruptedException e) {
                    Log.wtf(TAG, e);
                }
                timeout = checkIntervalMillis
                        - (SystemClock.uptimeMillis() - start);
            }

            final int waitState =
                    evaluateCheckerCompletionLocked();
            if (waitState == COMPLETED
                    || waitState == WAITING) {
                if (waitState == COMPLETED) {
                    preWatchdogTriggered = false;
                }
                continue;
            }

            blockedCheckers = getCheckersWithStateLocked(
                    waitState == OVERDUE
                            ? OVERDUE
                            : WAITED_UNTIL_PRE_WATCHDOG);
            allowRestart = mAllowRestart;
        }

        // ... 锁外记录 critical event、DropBox 和线程 dump。
    }
}

检查器状态由 mLock 保护,实际的 Handler/monitor 回调在各自线程执行。WAITED_UNTIL_PRE_WATCHDOG 只触发预警和现场收集,循环继续等待完整 timeout;只有 OVERDUE 才进入终止决策。把预警当成已经重启,会把两次 dump 和一次 kill 的时机看错。

相关函数:Watchdog.run 的终止分支

java
// 本文注:debugger 或测试 controller 可以阻止 kill,生产超时不一定必然退出。
if (Debug.isDebuggerConnected()) {
    debuggerWasConnected = 2;
}
if (debuggerWasConnected >= 2) {
    Slog.w(TAG,
            "Debugger connected: Watchdog is *not* killing the system process");
} else if (!allowRestart) {
    Slog.w(TAG,
            "Restart not allowed: Watchdog is *not* killing the system process");
} else {
    Slog.w(TAG,
            "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
    WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);
    Slog.w(TAG, "*** GOODBYE!");
    if (!Build.IS_USER && isCrashLoopFound()
            && !WatchdogProperties
                    .should_ignore_fatal_count()
                    .orElse(false)) {
        breakCrashLoop();
    }
    Process.killProcess(Process.myPid());
    System.exit(10);
}

Watchdog 的 owner 仍然是 system_server 内的 watchdog 线程;allowRestart 来自 mAllowRestart,debugger 连接和 activity controller 都可能把“发现阻塞”与“终止进程”分开。真正 kill 前会写 watchdog 事件并收集诊断,kill 之后不会在本进程内执行恢复逻辑。

4. Zygote接管 ​

SystemServer 的 Java 进程退出后,primary Zygote 通过 native SIGCHLD handler 收割子进程。这个 handler 必须使用 waitpid(),否则退出的 system_server 会变成 zombie,Zygote 也无法准确知道哪个 PID 已经结束。

源码文件:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

相关函数:SigChldHandler

cpp
static void SigChldHandler(int /*signal_number*/,
                           siginfo_t* info,
                           void* /*ucontext*/) {
    pid_t pid;
    int status;
    int64_t usaps_removed = 0;
    int saved_errno = errno;

    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        // 本文注:先把退出状态发送给 system_server 的 AppExitInfo 记录器。
        sendSigChildStatus(pid, info->si_uid, status);

        if (WIFEXITED(status)) {
            async_safe_format_log(
                    ANDROID_LOG_INFO, LOG_TAG,
                    "Process %d exited cleanly (%d)",
                    pid, WEXITSTATUS(status));
            if (RemoveUsapTableEntry(pid)) {
                ++usaps_removed;
            }
        } else if (WIFSIGNALED(status)) {
            async_safe_format_log(
                    ANDROID_LOG_INFO, LOG_TAG,
                    "Process %d exited due to signal %d (%s)%s",
                    pid, WTERMSIG(status),
                    strsignal(WTERMSIG(status)),
                    WCOREDUMP(status) ? "; core dumped" : "");
            if (WTERMSIG(status) != SIGTERM
                    && RemoveUsapTableEntry(pid)) {
                ++usaps_removed;
            }
        }

        // 本文注:gSystemServerPid 是 Zygote native owner 保存的目标 PID。
        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);
        }
    }

    // ECHILD 对没有子进程的 secondary zygote 是正常情况。
    if (pid < 0 && errno != ECHILD) {
        async_safe_format_log(
                ANDROID_LOG_WARN, LOG_TAG,
                "Zygote SIGCHLD error in waitpid: %s",
                strerror(errno));
    }
    // ... 如果有 USAP 被收割,通知 ZygoteServer 更新资源池。
    errno = saved_errno;
}

这里的 sendSigChildStatus() 是给 system_server 内部的退出信息通道;当 system_server 自己已经死掉时,消息可能无法被消费,但这不影响后面的 PID 比较和 kill(getpid(), SIGKILL)。WNOHANG 让 handler 一次收割当前已经结束的多个子进程;保存并恢复 errno 是 signal handler 的并发约束。

Zygote 发送的三个整数字段在 system_server 由 ProcessList 消费。它使用 native parser 先校验消息类型和长度,再将 PID、UID 和 wait status 传给 AppExitInfoTracker。

源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java

相关函数:ProcessList.handleZygoteMessages

java
private int handleZygoteMessages(
        FileDescriptor fd, int events) {
    final int eventFd = fd.getInt$();
    if ((events & EVENT_INPUT) != 0) {
        try {
            int len = Os.read(fd,
                    mZygoteUnsolicitedMessage, 0,
                    mZygoteUnsolicitedMessage.length);
            if (len > 0
                    && mZygoteSigChldMessage.length
                    == Zygote.nativeParseSigChld(
                            mZygoteUnsolicitedMessage,
                            len,
                            mZygoteSigChldMessage)) {
                // 本文注:只有消息格式正确才更新进程退出信息。
                mAppExitInfoTracker.handleZygoteSigChld(
                        mZygoteSigChldMessage[0] /* pid */,
                        mZygoteSigChldMessage[1] /* uid */,
                        mZygoteSigChldMessage[2] /* status */);
            }
        } catch (Exception e) {
            Slog.w(TAG,
                    "Exception in reading unsolicited zygote message: "
                            + e);
        }
    }
    return EVENT_INPUT;
}

ProcessList 只记录子进程的退出信息,不参与决定 Zygote 是否自杀。因此它的消费时机可能晚于 SIGCHLD,也可能因 system_server 已经退出而无法保存;这不会改变 Zygote 的恢复决策。

相关函数:forkSystemServer

cpp
static jint com_android_internal_os_Zygote_nativeForkSystemServer(
        JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
        jint runtime_flags, jlong permitted_capabilities,
        jlong effective_capabilities) {
    // ... 准备 fd、rlimit、mount 和 capability 参数。
    SetSignalHandlers();
    BlockSignal(SIGCHLD, fail_fn);

    pid_t pid = zygote::ForkCommon(
            env, true, fds_to_close, fds_to_ignore, true);
    if (pid == 0) {
        SpecializeCommon(
                env, uid, gid, gids, runtime_flags,
                rlimits, permitted_capabilities,
                effective_capabilities, 0,
                MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr,
                true, false, nullptr, nullptr,
                false, nullptr, nullptr, false, false, false);
    } else if (pid > 0) {
        ALOGI("System server process %d has been created", pid);
        gSystemServerPid = pid;

        // 本文注:发布 PID 后立即复查极窄的 fork/记录竞态。
        int status;
        if (waitpid(pid, &status, WNOHANG) == pid) {
            ALOGE("System server process %d has died. Restarting Zygote!", pid);
            RuntimeAbort(env, __LINE__,
                    "System server process has died. Restarting Zygote!");
        }
    }
    return pid;
}

gSystemServerPid 的 owner 是每个 Zygote 进程的 native 全局状态,值在 parent 分支写入;child 分支继续执行 SystemServer specialization。SetSignalHandlers() 之所以在 fork 前设置,是为了让 Zygote 能收割后续子进程;fork 窗口又临时 block SIGCHLD,避免 fd 清理阶段被 handler 插入。发布 PID 后的 waitpid(WNOHANG) 是补偿检查,用来覆盖“子进程已死但 handler 尚未观察到”的窗口。

5. init重启 ​

init 只认识 service。primary Zygote 的 rc 定义包含 onrestart 和 critical;它没有一条“SystemServer 崩溃后直接启动 SystemServer”的规则。因为 SystemServer 是 Zygote 的子进程,init 的重启对象是 zygote service。

源码文件:system/core/rootdir/init.zygote64.rc

相关配置:service zygote

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
    onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
    onrestart write /sys/power/state on
    onrestart write /sys/power/wake_lock zygote_kwl
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart --only-if-running media.tuner
    onrestart restart netd
    onrestart restart wificond
    task_profiles ProcessCapacityHigh MaxPerformance
    critical window=${zygote.critical_window.minute:-off} target=zygote-fatal

onrestart 命令在 service 被标记为 restarting 后执行,消费方是 init 的 Action 执行器;它们会恢复 FUSE、电源唤醒和一组依赖的 native 服务。critical 是独立的 crash-loop 策略,不表示每次退出都立即重启设备。

secondary Zygote 的定义还包含 onrestart restart zygote。因此 32 位 secondary Zygote 异常退出时,init 会请求 primary Zygote 重启;这条规则和 system_server 的 primary 子进程退出路径不能混为一谈。

相关配置:service zygote_secondary

text
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote_secondary stream 660 root system
    socket usap_pool_secondary stream 660 root system
    onrestart restart zygote
    task_profiles ProcessCapacityHigh MaxPerformance

primary Zygote 重新启动后,app_process64 再次进入 ZygoteInit.main(),因为 rc 命令行仍带 --start-system-server,它会重新走 forkSystemServer()。新的 SystemServer 构造函数会把 sys.system_server.start_count 加一,因此可用 启动场景判定 的计数区分完整物理 boot 与 runtime restart。

6. 重启状态机 ​

init 的 Service::Reap() 同时处理资源清理、失败回调、crash-loop 计数和状态转换。关键状态不是“进程死了”这一瞬间,而是 pid_ = 0、SVC_RESTARTING 和下一次 Start() 之间的顺序。

源码文件:system/core/init/service.cpp

相关函数:Service::Reap

cpp
void Service::Reap(const siginfo_t& siginfo) {
    // 本文注:非 oneshot 或正在重启的 service 先清理整个进程组。
    if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
        KillProcessGroup(SIGKILL);
    }

    for (const auto& socket : sockets_) {
        if (socket.persist) continue;
        auto path = ANDROID_SOCKET_DIR "/" + socket.name;
        unlink(path.c_str());
    }

    for (const auto& f : reap_callbacks_) {
        f(siginfo);
    }

    if ((siginfo.si_code != CLD_EXITED
            || siginfo.si_status != 0)
            && on_failure_reboot_target_) {
        trigger_shutdown(*on_failure_reboot_target_);
    }

    if (flags_ & SVC_TEMPORARY) return;

    pid_ = 0;
    flags_ &= (~SVC_RUNNING);
    start_order_ = 0;
    was_last_exit_ok_ = siginfo.si_code == CLD_EXITED
            && siginfo.si_status == 0;

    if (flags_ & (SVC_DISABLED | SVC_RESET)) {
        NotifyStateChange("stopped");
        return;
    }

    flags_ &= (~SVC_RESTART);
    flags_ |= SVC_RESTARTING;
    onrestart_.ExecuteAllCommands();
    NotifyStateChange("restarting");
}

清理顺序有实际含义:先杀进程组,避免 service 派生的工作进程遗留;再删除非持久 socket;然后调用 reap callback;最后清掉 PID 并进入 SVC_RESTARTING。onrestart 在状态已经变成 restarting 后执行,因此命令看到的是“旧实例已被收割、等待新实例”的状态。

下一次启动由 init 主循环中的 HandleProcessActions() 触发。

相关函数:HandleProcessActions

cpp
static std::optional<boot_clock::time_point>
HandleProcessActions() {
    std::optional<boot_clock::time_point>
            next_process_action_time;
    for (const auto& s : ServiceList::GetInstance()) {
        if ((s->flags() & SVC_RUNNING)
                && s->timeout_period()) {
            // ... 处理 timeout service。
        }

        if (!(s->flags() & SVC_RESTARTING)) continue;

        auto restart_time = s->time_started()
                + s->restart_period();
        if (boot_clock::now() > restart_time) {
            if (auto result = s->Start(); !result.ok()) {
                LOG(ERROR) << "Could not restart process '"
                           << s->name() << "': "
                           << result.error();
            }
        } else {
            if (!next_process_action_time
                    || restart_time
                            < *next_process_action_time) {
                next_process_action_time = restart_time;
            }
        }
    }
    return next_process_action_time;
}

SVC_RESTARTING 是等待状态,Start() 才是新的进程创建动作;若 restart_period 尚未到期,init 不忙等,而是把最近的时间点交还主循环。启动失败不会把旧 PID 恢复为 running,下一轮仍依据 service 状态处理。

7. CrashLoop保护 ​

critical 的含义是“连续失败达到阈值后升级处置”,不是“每次失败都触发 bootloader”。在 Android 17 的 init 实现中,关键 service 在 boot 未完成前或时间窗口内反复非正常退出,超过 4 次才进入 fatal 分支;并且 24 小时内已经执行过一次 fatal reboot 时会节流。

源码文件:system/core/init/service.cpp

相关函数:Service::Reap 的 crash-loop 分支

cpp
// 本文注:只有 critical 或 updatable service 的非正常退出进入计数。
if (((flags_ & SVC_CRITICAL) || is_process_updatable)
        && !(flags_ & SVC_RESTART)
        && !was_last_exit_ok_) {
    bool boot_completed =
            GetBoolProperty("sys.boot_completed", false);
    if (now < time_crashed_ + fatal_crash_window_
            || !boot_completed) {
        if (++crash_count_ > 4) {
            if (flags_ & SVC_CRITICAL) {
                if (!GetBoolProperty(
                        "init.svc_debug.no_fatal."
                                + name_, false)) {
                    uint64_t epoch_time =
                            std::chrono::duration_cast<
                                    std::chrono::seconds>(
                                    std::chrono::system_clock::now()
                                            .time_since_epoch())
                                    .count();
                    if (epoch_time
                            - GetIntProperty(
                                    "persist.init.svc.last_fatal_reboot_epoch",
                                    0)
                            > 24 * 60 * 60) {
                        SetProperty(
                                "persist.init.svc.last_fatal_reboot_epoch",
                                std::to_string(epoch_time));
                        SetFatalRebootTarget(
                                fatal_reboot_target_);
                        LOG(FATAL)
                                << "critical process '"
                                << name_
                                << "' exited 4 times";
                    }
                }
            } else {
                // updatable service 通知 update_verifier/apexd,而非立即 fatal reboot。
                SetProperty(
                        "sys.init.updatable_crashing_process_name",
                        name_);
                SetProperty(
                        "sys.init.updatable_crashing", "1");
            }
        }
    } else {
        time_crashed_ = now;
        crash_count_ = 1;
    }
}

这里的计数 owner 是 init 内存中的 Service 对象,time_crashed_ 和 crash_count_ 随 init 生命周期存在;persist.init.svc.last_fatal_reboot_epoch 只用于跨重启节流。init.svc_debug.no_fatal_<service> 可关闭 fatal 处置,开发和测试环境因此可能看到 crash loop 继续重启而不进入 bootloader。上面的 updatable 分支也说明 APEX 可更新 service 的失败后果和 critical 不同。

8. 诊断输出 ​

一次完整诊断要把“根因现场”和“恢复动作”分开采集。Java crash、Watchdog 和 native crash 的输出位置不同,不能只看 logcat | grep FATAL。

8.1 Java与Watchdog ​

RuntimeInit.LoggingHandler 写入 crash log,并在 system UID 的 system_server 路径递增 sys.system_server.crash_java;Watchdog 另写 WATCHDOG EventLog、SYSTEM_SERVER_WATCHDOG_OCCURRED atom,并把线程 dump 放入 DropBox。

bash
# 输入:当前设备的 system_server 属性。输出:Java crash 累计值与本次 runtime restart 次数。
adb shell getprop sys.system_server.crash_java
adb shell getprop sys.system_server.start_count

# 输入:system_server dump 服务。输出:本次进程的 Runtime restart、start count 和时钟。
adb shell dumpsys system_server_dumper --name SystemServer

# 输入:system 与 main buffer。输出:Java fatal、Watchdog kill 和 Zygote 收割日志。
adb logcat -b system -b main -v threadtime -d \
  | rg 'FATAL EXCEPTION IN SYSTEM PROCESS|WATCHDOG KILLING SYSTEM PROCESS|Exit zygote because system server|Process [0-9]+ exited'

# 输入:DropBox 服务。输出:watchdog、system_server crash 现场条目。
adb shell dumpsys dropbox --print system_server_native_crash
adb shell dumpsys dropbox --print watchdog

这些命令的输入分别是 system properties、SystemServerDumper Binder service、log buffers 和 DropBoxManager;输出只能证明对应记录是否存在,不能单独证明 init 已经完成重启。要确认恢复动作,还需要读取 init service 状态和后续 SYSTEM_SERVER_START。

8.2 Native与init ​

native crash 的 tombstone 由 debuggerd 生成,BootReceiver 在启动后的后台线程把包含 >>> system_server <<< 的 tombstone 额外复制为 system_server_native_crash。init 的服务状态则通过 init.svc.zygote 和 crash-loop 属性观察。

源码文件:frameworks/base/services/core/java/com/android/server/BootReceiver.java

相关函数:BootReceiver.addFileWithFootersToDropBox

java
String fileContents = FileUtils.readTextFile(
        file, maxSize, TAG_TRUNCATED);
String text = headers + fileContents + footers;

// 本文注:只有 tombstone 且内容标记为 system_server 才使用专用 tag。
if (tag.equals(TAG_TOMBSTONE)
        && fileContents.contains(">>> system_server <<<")) {
    addTextToDropBox(db,
            "system_server_native_crash",
            text, filename, maxSize);
}
if (tag.equals(TAG_TOMBSTONE)) {
    FrameworkStatsLog.write(
            FrameworkStatsLog.TOMB_STONE_OCCURRED);
}
addTextToDropBox(db, tag, text, filename, maxSize);

BootReceiver 是诊断输出的后处理者,不是 native crash 的终止者。它只是根据 tombstone 文本内容增加一个精确 tag;如果 DropBox tag 未启用、文件时间戳已记录或文件不存在,该副作用可以缺失,但不影响 debuggerd 和 init 的进程处理。

bash
# 输入:init property service。输出:zygote 当前状态及 crash-loop 节流时间。
adb shell getprop init.svc.zygote
adb shell getprop persist.init.svc.last_fatal_reboot_epoch

# 输入:kernel/debuggerd tombstone 目录。输出:最近 system_server native 崩溃的线程和 signal。
adb shell ls -lt /data/tombstones | head
adb shell grep -R -n '>>> system_server <<<' /data/tombstones

# 输入:启动时间线。输出:旧 zygote 退出、重启和新 system_server start 的相邻事件。
adb logcat -b all -v threadtime -d \
  | rg 'system_server.*terminated|Exit zygote|init.*zygote|SYSTEM_SERVER_START|BOOT_PROGRESS_SYSTEM_RUN'

如果 sys.system_server.crash_java 增长但没有 tombstone,优先检查 Java 异常路径;如果有 tombstone 但没有 FATAL EXCEPTION IN SYSTEM PROCESS,说明根因可能在 native;如果 Zygote 日志出现退出但 init.svc.zygote 长时间不是 running,应转向 init rc、SELinux 或 executable 启动失败,而不是继续分析 AMS。

9. 调用时序 ​

下图把三个进程边界放在一条时间轴上。消息 SIGCHLD 是内核到 Zygote 的信号,onrestart 是 init 内部 Action,不是 Binder 调用。

system_server 的 Java 报告动作和 Zygote 的退出动作之间没有同步等待关系:Java handler 只等待 handleApplicationCrash() 的 Binder 调用返回或失败,然后立即终止;Zygote 收到的只是最终进程状态。init 的 Start() 发生在之后的主循环时间点,因此“crash 日志出现”和“新 SystemServer 已经开始”不是同一个时间戳。

10. 失败矩阵 ​

现象第一观察者关键状态可能的下一步
Java FATAL EXCEPTION IN SYSTEM PROCESSRuntimeInit.LoggingHandlersys.system_server.crash_java 增加查原始 Java stack,再看 Zygote 是否退出
WATCHDOG KILLING SYSTEM PROCESSWatchdogchecker OVERDUE、allowRestart查 blocked checker、thread dump、debugger/controller 条件
tombstone 含 >>> system_server <<<debuggerd/BootReceiversignal、native backtrace、DropBox tag查 native 库和对应 service 调用
Zygote 日志 Exit zygote because system server...Zygote SigChldHandlerpid == gSystemServerPid查 init zygote restart 与 onrestart
zygote 反复 restartinginit Service::Reapcrash_count_、time_crashed_检查 critical、fatal 节流和启动失败
新 SystemServer 启动但计数持续增加SystemServer 构造函数mStartCount > 1区分 runtime restart 与物理 reboot,回到启动场景判定

有两个常见误判。第一,sys.boot_completed=1 只说明某次启动曾到达 boot complete,不能替代 Zygote 的 gSystemServerPid 或 init 的 crash count。第二,sys.system_server.start_count 只统计当前 property 生命周期内的 SystemServer 实例,不告诉你根因是 Java、Watchdog 还是 native。

11. 源码验证 ​

源码测试不能完整模拟真实 init、内核信号和 debuggerd,但可以验证状态转移中的局部契约。阅读时应把“测试断言覆盖的范围”和“必须依赖设备现场的部分”分开。

源码文件:frameworks/base/services/tests/servicestests/src/com/android/server/WatchdogTest.java

相关测试:WatchdogTest.blockedThread

java
@Test
public void blockedThread() {
    // 输入:10ms 的 checker 超时,但被检查的 Handler 不回调。
    mChecker.scheduleCheckLocked(TIMEOUT_MS);
    assertEquals(Watchdog.WAITING,
            mChecker.getCompletionStateLocked());

    mClock.advanceBy(6);
    // 断言:预警时间到达,只能收集信息,不应立即 kill。
    assertEquals(Watchdog.WAITED_UNTIL_PRE_WATCHDOG,
            mChecker.getCompletionStateLocked());

    mClock.advanceBy(6);
    // 断言:完整 timeout 后才进入 OVERDUE。
    assertEquals(Watchdog.OVERDUE,
            mChecker.getCompletionStateLocked());
}

这个用例用 TestClock 分两次推进时间:第一次到达 pre-watchdog 状态,第二次才到达 OVERDUE。它证明了时间状态机和本文所说的“先收集、后终止”;不证明真实设备上的线程 dump 内容或 kill 后的 init 重启。

ART Service 的测试则验证 staged artifact 失败和延迟提交,说明恢复链中“新进程已启动”不等于“所有启动期资源都已完成消费”。

源码文件:art/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java

相关测试:testCommitPreRebootStagedFiles、testCommitPreRebootStagedFilesObsolete

java
when(mArtd.checkPreRebootStagedFilesStatus())
        .thenReturn(TestingUtils.createPreRebootStagedFilesStatus(
                true /* isCommittable */, 200 /* createdAtMillis */));

mArtManagerLocal.onBoot(
        ReasonMapping.REASON_BOOT_AFTER_OTA,
        null /* progressCallbackExecutor */,
        null /* progressCallback */);

InOrder inOrder = inOrder(mArtd);
inOrder.verify(mArtd).deletePreRebootStagedMetadata();
// 本文注:开机阶段先提交 primary;BOOT_COMPLETED 后才提交 secondary。
verify(mArtd, times(1))
        .commitPreRebootStagedFiles(any(), any());

simulateBroadcast(Intent.ACTION_BOOT_COMPLETED);
verify(mArtd, times(2))
        .commitPreRebootStagedFiles(any(), any());

isCommittable=true 的输入覆盖成功提交和生命周期延迟;对应的 testCommitPreRebootStagedFilesObsolete() 将输入改为 false,断言 cleanUpPreRebootStagedFiles() 被调用且从未 commit。它们不能证明 Zygote 或 init 的重启,但能防止把“启动恢复”叙述成一次无条件的全量资源恢复。

12. 阅读收束 ​

可以用下面的顺序复述一次真正的 SystemServer crash:先指出根因属于 Java、Watchdog 还是 native;再说明当前进程怎样退出;然后指出 primary Zygote 如何通过 gSystemServerPid 判断必须自杀;最后追踪 init 的 Service::Reap、SVC_RESTARTING 和 HandleProcessActions() 如何重新拉起 Zygote。若失败连续发生,还要补上 critical 的 4 次阈值、boot-completed 条件和 24 小时 fatal reboot 节流。

做到这一步,就不会把三种不同的“计数”混在一起:sys.system_server.crash_java 是 Java 日志路径的计数,mStartCount 是当前 property 生命周期内的 SystemServer 实例数,init 的 crash_count_ 是某个 service 的非正常退出窗口。它们的 owner、持久性和消费者都不同,诊断结论也必须分别建立。