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)源码编写

相关推荐
宝杰X73 小时前
Android Room3 多平台数据库
android·数据库
huibin1478523695 小时前
热缓解学习记录
android·学习
小田的博客8 小时前
SAP MM 供应商银行主数据更新报错!message R1228!
android·java·服务器
一笑的小酒馆9 小时前
Android智能猫砂盆视频加载慢卡顿问题分析
android
只会cv的小前端11 小时前
七巧低代码服务端脚本使用方法
android·低代码·rxjava
11 小时前
Android 自定义 View 实战:从零打造工业级全向摇杆控件(OmniJoystickView)
android·kotlin·自定义view·摇杆控件
雨白11 小时前
深入理解 Kotlin 协程 (十一):以逸待劳,探秘 select 多路复用与并发安全策略
android·kotlin
hunterandroid14 小时前
[Android 从零到一] Android 深度链接与 App Links:从 URI Scheme 到可验证的应用跳转
android
我命由我1234516 小时前
Jetpack Compose - Material Design 断点范围、WindowSizeClass、针对不同屏幕尺寸创建预览、四类导航栏
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime