系列目录 :第一篇:异常机制全景图 | [第二篇: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。
定位方法:
- 从
dumpsys dropbox system_server_watchdog --print提取堆栈 - 找出两个线程各自持有的锁和等待的锁
- 画出锁依赖图,找到循环依赖
- 修改代码,统一锁的获取顺序或使用
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 可能误报 | 如果锁本身设计就是长时间持有,会触发误报 |
七、总结
-
Watchdog 是 System Server 的"心跳监护仪":每 30 秒检测一次关键线程和锁的健康状态,超时阈值为 60 秒。
-
5 个 HandlerChecker 覆盖关键线程:foreground、main、ui、i/o、display,Monitor 在 foreground 线程上执行。
-
渐进式超时处理:30 秒检查一次,超过 60 秒确认阻塞后自杀重启,中间会先 dump 堆栈用于诊断。
-
日志产物 :DropBox (
system_server_watchdog) +/data/anr/traces.txt。 -
常见根因:服务间死锁、IO 阻塞、Binder 线程池耗尽。
下一篇将介绍 System Server 崩溃的另一种形态------进程直接崩溃而非 Watchdog 超时触发。
本文基于 AOSP 7(Android Nougat)源码编写。