Android 7系统异常问题排查(四)Framework层(上)—System Server Watchdog

系列目录第一篇:异常机制全景图 | [第二篇:Kernel Panic 与系统重启](#第二篇:Kernel Panic 与系统重启) | [第三篇:Tombstone 机制](#第三篇:Tombstone 机制) | [第四篇:System Server Watchdog](#第四篇:System Server Watchdog) | [第五篇:System Server 崩溃](#第五篇:System Server 崩溃) | [第六篇:ANR 机制](#第六篇:ANR 机制) | [第七篇:Java 层崩溃](#第七篇:Java 层崩溃) | [第八篇:Trace 机制](#第八篇:Trace 机制) | 第九篇:日志系统 | 第十篇:实战方法论


一、为什么需要 System Server Watchdog?

你可能遇到过这些场景:

  • 手机使用中突然卡死,按什么键都没反应,几十秒后自动重启
  • 系统界面消失,短暂黑屏后又恢复
  • 充电时设备无响应,过一会儿自动重启

这些现象的背后,可能是 System Server 中的关键线程被阻塞了。内核 watchdog 能检测到 CPU 级别的死锁,但无法检测到用户态的"逻辑死锁"------比如:

  • AMS 持有一个锁后等待 Binder 调用返回,但目标进程也因等待 AMS 而阻塞
  • 某个关键线程在 while(true) 中无限循环
  • IO 操作阻塞过久(如 eMMC 异常)

System Server Watchdog 正是为了解决这类用户态卡死问题而设计的。


二、Watchdog 源码架构

源码路径frameworks/base/services/core/java/com/android/server/Watchdog.java

2.1 两大检测机制

Watchdog 通过两种方式检测系统健康状态:

检测机制 检测对象 超时阈值 原理
Monitor 关键服务的锁持有状态 60s 尝试获取被监控的锁,若超时说明锁被长期持有
HandlerChecker 关键线程的 Handler 消息处理 60s 向目标线程 Handler 发送空消息,等待处理完成
java 复制代码
public final class Watchdog extends Thread {
    // ...
    static final long DEFAULT_TIMEOUT = DB ? 10*1000 : 60*1000;  // 60秒
    static final long CHECK_INTERVAL = DEFAULT_TIMEOUT / 2;       // 30秒

    static final int COMPLETED = 0;
    static final int WAITING = 1;
    static final int WAITED_HALF = 2;
    static final int BLOCKED = 3;

    final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<>();
    final HandlerChecker mMonitorChecker;  // 专门用于 Monitor 检查
    // ...
}

关键设计DEFAULT_TIMEOUT 是 60 秒而非 30 秒------CHECK_INTERVAL(30 秒)是检测间隔,DEFAULT_TIMEOUT(60 秒)才是超时阈值。

2.2 被监控的 HandlerChecker 线程

java 复制代码
public final class Watchdog extends Thread {
    // ...
    final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<>();
    final HandlerChecker mMonitorChecker;

    public Watchdog() {
        super("watchdog");

        // Monitor 检查器:在 FgThread 上运行
        mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
                "foreground thread", DEFAULT_TIMEOUT);
        mHandlerCheckers.add(mMonitorChecker);

        // 以下线程的 Handler 都被监控
        mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
                "main thread", DEFAULT_TIMEOUT));
        mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
                "ui thread", DEFAULT_TIMEOUT));
        mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
                "i/o thread", DEFAULT_TIMEOUT));
        mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
                "display thread", DEFAULT_TIMEOUT));
    }
}

关键设计 :共 5 个 HandlerChecker------foreground(兼做 Monitor 载体)、main、ui、i/o、display。Monitor 被添加到 mMonitorChecker 中,在 FgThread 上执行锁检查。

2.3 被监控的 Monitor 锁

各系统服务通过 Watchdog.getInstance().addMonitor() 注册自己的锁检查点:

java 复制代码
// Watchdog.java
public final class Watchdog extends Thread {
    // ...
    public void addMonitor(Monitor monitor) {
        synchronized (this) {
            if (isAlive()) {
                throw new RuntimeException("Monitors must be added before starting");
            }
            mMonitorChecker.addMonitor(monitor);
        }
    }
}

各服务在 onStart() 中注册:AMS、WMS、PowerManagerService 等都通过 addMonitor() 将自己的锁注册为健康检查点。Watchdog 通过尝试获取这些锁来判断对应服务是否处于死锁状态。


三、运行机制详解

3.1 HandlerChecker 数据结构

java 复制代码
public final class Watchdog extends Thread {
    // ...
    public final class HandlerChecker implements Runnable {
        private final Handler mHandler;
        private final String mName;
        private final long mWaitMax;
        private final ArrayList<Monitor> mMonitors = new ArrayList<>();
        private boolean mCompleted;
        private Monitor mCurrentMonitor;
        private long mStartTime;

        HandlerChecker(Handler handler, String name, long waitMaxMillis) {
            mHandler = handler;
            mName = name;
            mWaitMax = waitMaxMillis;
            mCompleted = true;
        }
    }
}

关键设计 :每个 HandlerChecker 持有目标线程的 Handler、名称、超时阈值和一组 Monitor。mCompleted 标记上一轮检查是否完成。

3.2 scheduleCheckLocked ------ 发送心跳

java 复制代码
public final class Watchdog extends Thread {
    // ...
    public final class HandlerChecker implements Runnable {
        // ...
        public void scheduleCheckLocked() {
            // 如果没有 Monitor 且 Looper 是 polling 模式,跳过检查
            if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) {
                mCompleted = true;
                return;
            }
            // 如果上一轮还没完成,不重复发送
            if (!mCompleted) {
                return;
            }
            mCompleted = false;
            mStartTime = SystemClock.uptimeMillis();
            mHandler.postAtFrontOfQueue(this);  // 向目标线程发送检查任务
        }
    }
}

关键设计 :通过 postAtFrontOfQueue(this) 将检查任务插入目标线程消息队列头部。如果目标线程的消息队列被阻塞(比如持有锁后在等 Binder),这个任务就无法被处理。

3.3 HandlerChecker.run() ------ Monitor 执行

java 复制代码
public final class Watchdog extends Thread {
    // ...
    public final class HandlerChecker implements Runnable {
        // ...
        public void run() {
            final int size = mMonitors.size();
            for (int i = 0; i < size; i++) {
                synchronized (Watchdog.this) {
                    mCurrentMonitor = mMonitors.get(i);
                }
                mCurrentMonitor.monitor();  // 尝试获取锁
            }
            synchronized (Watchdog.this) {
                mCompleted = true;
                mCurrentMonitor = null;
            }
        }
    }
}

关键设计monitor() 方法内部会 synchronized 获取目标服务的锁。如果锁被其他线程长期持有,monitor() 就会阻塞,导致 mCompleted 无法被设为 true

3.4 Watchdog.run() ------ 主循环

java 复制代码
public final class Watchdog extends Thread {
    // ...
    @Override
    public void run() {
        boolean waitedHalf = false;
        while (true) {
            ArrayList<Monitor> monitors;
            ArrayList<HandlerChecker> handlerCheckers;

            synchronized (this) {
                long timeout = CHECK_INTERVAL;  // 30秒
                long start = SystemClock.uptimeMillis();

                // 对所有 HandlerChecker 发送心跳
                for (int i = mHandlerCheckers.size() - 1; i >= 0; i--) {
                    mHandlerCheckers.get(i).scheduleCheckLocked();
                }

                // 等待 CHECK_INTERVAL(30秒)
                // 期间如果有 checker 提前完成会 notify
                while (waitState == COMPLETED) {
                    // ...
                    wait(timeout);
                    // ...
                }
            }
            // ... 后续检查逻辑
        }
    }
}

3.5 超时处理流程

复制代码
Watchdog 每 30 秒(CHECK_INTERVAL)执行一次检测
    │
    ├─ 第一次检测(30s 后)
    │   ├─ 所有 checker 完成 → 正常,继续下一轮
    │   └─ 有 checker 未完成但未超时 → 正常(给 60s 宽限)
    │
    ├─ 第二次检测(60s 后,即又过了 30s)
    │   ├─ 所有 checker 完成 → 正常
    │   ├─ 有 checker 超过 60s 未完成(WAITED_HALF)
    │   │   ├─ 收集线程堆栈(dumpStackTraces)
    │   │   ├─ 写入日志
    │   │   └─ 继续等待(给系统一次自救机会)
    │   │
    │   └─ 有 checker 仍然阻塞(BLOCKED)
    │       ├─ 收集所有阻塞 checker 的描述
    │       ├─ dumpStackTraces 保存完整堆栈
    │       ├─ 写入 DropBox (system_server_watchdog)
    │       ├─ Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: ...")
    │       └─ Process.killProcess(Process.myPid()) → 系统自杀

关键设计 :Watchdog 的超时判定是渐进式的------先等 30 秒检查一次,如果未完成再等 30 秒(共 60 秒),超过 60 秒才确认阻塞并自杀。中间的 WAITED_HALF 状态会先 dump 堆栈用于诊断。


四、Watchdog 触发后的日志产物

4.1 DropBox 条目

通过 dumpsys dropbox --print 可以查看:

复制代码
Tag: system_server_watchdog
    Subject: Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in monitor ...
    --- pid 1234 at 2024-01-01 12:00:00 ---
    Cmd line: system_server
    "main" prio=5 tid=1 Blocked
      | group="main" sCount=1 dsCount=0 obj=0x12c0e0a0 self=0x7f8c3a4000
      | sysTid=1234 nice=-2 cgrp=default sched=0/0 handle=0x7f8c3a4b50
      at com.android.server.am.ActivityManagerService.monitor(AMS.java:12345)
      - waiting to lock <0x12345678> (a com.android.server.am.ActivityManagerService)
        held by thread 15
    ...

4.2 /data/anr/traces.txt

Watchdog 超时也会 dump 所有线程的堆栈到 /data/anr/traces.txt,格式与 ANR 的 traces 一致。


五、典型 Watchdog 场景与定位

场景 1:系统服务死锁

现象:手机使用中突然卡死,几十秒后自动重启。

日志特征

复制代码
"main" prio=5 tid=1 Blocked
  waiting to lock <0x12345678> held by "Binder:1234_5" tid=15
  ...

"Binder:1234_5" tid=15 Blocked
  waiting to lock <0x87654321> held by "main" tid=1

分析:经典死锁------main 线程持有锁 A 等锁 B,Binder 线程持有锁 B 等锁 A。

定位方法

  1. dumpsys dropbox system_server_watchdog --print 提取堆栈
  2. 找出两个线程各自持有的锁和等待的锁
  3. 画出锁依赖图,找到循环依赖
  4. 修改代码,统一锁的获取顺序或使用 tryLock 超时机制

场景 2:IO 阻塞

现象:存储空间不足或 eMMC 异常时频繁 Watchdog 重启。

日志特征

复制代码
"main" prio=5 tid=1 Runnable
  at android.os.FileUtils.readTextFile(...)
  at com.android.server.pm.PackageManagerService.writeLp(...)

分析:主线程在等待 IO 写入完成,但 eMMC 响应异常缓慢。

定位方法 :结合内核日志(dmesg)查看是否有 mmc0: timeout 等 eMMC 错误。

场景 3:Binder 线程池耗尽

现象:系统逐渐变慢,最终 Watchdog 触发。

日志特征:所有 Binder 线程都在等待某个远程服务响应。

分析:Binder 线程池所有线程都被占用,新的请求无法被处理,形成级联阻塞。

定位方法:查看所有 Binder 线程的堆栈,找到它们都在等待哪个进程/服务。


六、Watchdog 的局限性

局限 说明
检测粒度粗 60 秒超时阈值,无法发现短时卡顿
被动检测 只能发现"已经卡死"的状态,无法预警
无法定位根因 只能告诉你"哪里卡住了",不能告诉你"为什么卡住"
Monitor 可能误报 如果锁本身设计就是长时间持有,会触发误报

七、总结

  1. Watchdog 是 System Server 的"心跳监护仪":每 30 秒检测一次关键线程和锁的健康状态,超时阈值为 60 秒。

  2. 5 个 HandlerChecker 覆盖关键线程:foreground、main、ui、i/o、display,Monitor 在 foreground 线程上执行。

  3. 渐进式超时处理:30 秒检查一次,超过 60 秒确认阻塞后自杀重启,中间会先 dump 堆栈用于诊断。

  4. 日志产物 :DropBox (system_server_watchdog) + /data/anr/traces.txt

  5. 常见根因:服务间死锁、IO 阻塞、Binder 线程池耗尽。

下一篇将介绍 System Server 崩溃的另一种形态------进程直接崩溃而非 Watchdog 超时触发。


本文基于 AOSP 7(Android Nougat)源码编写

相关推荐
天空之城--19 小时前
Android全链路性能优化:从原理到实践的终极指南
android·性能优化
Meteors.19 小时前
Android 性能优化:05.CPU优化
android·性能优化
石头猫灯20 小时前
一次 CMS 被黑实录:完整事件思路梳理
android·学习
zhonyu鱼20 小时前
Escrcpy:用电脑键盘鼠标操作安卓手机的跨平台投屏工具
android·计算机外设·电脑
千里马学框架21 小时前
安卓开机性能优化:如何安全高效地裁剪 SystemService
android·智能手机·framework·wms·手机·性能·车载
Android打工仔21 小时前
一次 Android 拍照后卡顿的 Perfetto 定位与优化实践
android·性能优化
OpenFDE开源桌面21 小时前
实操:如何将安卓 App与Linux系统应用级融合(附代码)
android·linux
xiangxiongfly91521 小时前
Android VideoView总结
android·videoview
杉氧21 小时前
从 Modifier 到 Flexbox:React Native 布局与样式设计哲学
android·前端·react native
Kapaseker21 小时前
我常用的 5 个 Kotlin 优化小技巧
android·kotlin