1. Watchdog初始

Watchdog的中文的“看门狗”,有保护的意思。最早引入Watchdog是在单片机系统中,由于单片机的工作环境容易受到外界磁场的干扰,导致程序“跑飞”,造成整个系统无法正常工作,因此,引入了一个“看门狗”,对单片机的运行状态进行实时监测,针对运行故障做一些保护处理,譬如让系统重启。这种Watchdog属于硬件层面,必须有硬件电路的支持。

Linux也引入了Watchdog,在Linux内核下,当Watchdog启动后,便设定了一个定时器,如果在超时时间内没有对/dev/Watchdog进行写操作,则会导致系统重启。通过定时器实现的Watchdog属于软件层面。

Android设计了一个软件层面Watchdog,用于保护一些重要的系统服务,当出现故障时,通常会让Android系统重启。由于这种机制的存在,就经常会出现一些system_server进程被Watchdog杀掉而发生手机重启的问题。

本文期望回答以下问题:

  1. Watchdog是怎么工作的?这涉及到Watchdog的工作机制。
  2. 遇到Watchdog的问题该怎么办?这涉及到分析Watchdog问题的惯用方法。

2. Watchdog机制剖析

我们以frameworks/base/services/core/java/com/android/server/Watchdog.java为蓝本,分析Watchdog的实现逻辑。为了描述方便,ActivityManagerService, PackageManagerService, WindowManagerService会分别简称为AMS, PKMS, WMS。

2.1 Watchdog的初始化

Android的Watchdog是一个单例线程,在System Server时就会初始化Watchdog。Watchdog在初始化时,会构建很多HandlerChecker,大致可以分为两类:

  • Monitor Checker,用于检查是Monitor对象可能发生的死锁, AMS, PKMS, WMS等核心的系统服务都是Monitor对象。

  • Looper Checker,用于检查线程的消息队列是否长时间处于工作状态。Watchdog自身的消息队列,Ui, Io, Display这些全局的消息队列都是被检查的对象。此外,一些重要的线程的消息队列,也会加入到Looper Checker中,譬如AMS, PKMS,这些是在对应的对象初始化时加入的。

private Watchdog() {
....
mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
"foreground thread", DEFAULT_TIMEOUT);
mHandlerCheckers.add(mMonitorChecker);
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));
...
}

两类HandlerChecker的侧重点不同,Monitor Checker预警我们不能长时间持有核心系统服务的对象锁,否则会阻塞很多函数的运行; Looper Checker预警我们不能长时间的霸占消息队列,否则其他消息将得不到处理。这两类都会导致系统卡住(System Not Responding)。

2.2 添加Watchdog监测对象

Watchdog初始化以后,就可以作为system_server进程中的一个单独的线程运行了。但这个时候,还不能触发Watchdog的运行,因为AMS, PKMS等系统服务还没有加入到Watchdog的监测集。 所谓监测集,就是需要Watchdog关注的对象,Android中有成千上万的消息队列在同时运行,然而,Watchdog毕竟是系统层面的东西,它只会关注一些核心的系统服务。

Watchdog提供两个方法,分别用于添加Monitor Checker对象和Looper Checker对象:

public void addMonitor(Monitor monitor) {
// 将monitor对象添加到Monitor Checker中,
// 在Watchdog初始化时,可以看到Monitor Checker本身也是一个HandlerChecker对象
mMonitors.add(monitor);
} public void addThread(Handler thread, long timeoutMillis) {
synchronized (this) {
if (isAlive()) {
throw new RuntimeException("Threads can't be added once the Watchdog is running");
}
final String name = thread.getLooper().getThread().getName();
// 为Handler构建一个HandlerChecker对象,其实就是**Looper Checker**
mHandlerCheckers.add(new HandlerChecker(thread, name, timeoutMillis));
}
}

被Watchdog监测的对象,都需要将自己添加到Watchdog的监测集中。以下是AMS的类定义和构造器的代码片段:

public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { public ActivityManagerService(Context systemContext) {
...
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
} public void monitor() {
synchronized (this) { }
}
}

AMS实现了Watchdog.Monitor接口,这个接口只有一个方法,就是monitor(),它的作用后文会再解释。这里可以看到在AMS的构造器中,将自己添加到Monitor Checker对象中,然后将自己的handler添加到Looper Checker对象中。 其他重要的系统服务添加到Watchdog的代码逻辑都与AMS差不多。

整个Android系统中,被monitor的对象并不多,十个手指头就能数出来Watchdog.Monitor的实现类的个数。

2.3 Watchdog的监测机制

Watchdog本身是一个线程,它的run()方法实现如下:

@Override
public void run() {
boolean waitedHalf = false;
while (true) {
...
synchronized (this) {
...
// 1. 调度所有的HandlerChecker
for (int i=0; i<mHandlerCheckers.size(); i++) {
HandlerChecker hc = mHandlerCheckers.get(i);
hc.scheduleCheckLocked();
}
...
// 2. 开始定期检查
long start = SystemClock.uptimeMillis();
while (timeout > 0) {
...
try {
wait(timeout);
} catch (InterruptedException e) {
Log.wtf(TAG, e);
}
...
timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
} // 3. 检查HandlerChecker的完成状态
final int waitState = evaluateCheckerCompletionLocked();
if (waitState == COMPLETED) {
...
continue;
} else if (waitState == WAITING) {
...
continue;
} else if (waitState == WAITED_HALF) {
...
continue;
} // 4. 存在超时的HandlerChecker
blockedCheckers = getBlockedCheckersLocked();
subject = describeCheckersLocked(blockedCheckers);
allowRestart = mAllowRestart;
}
...
// 5. 保存日志,判断是否需要杀掉系统进程
Slog.w(TAG, "*** GOODBYE!");
Process.killProcess(Process.myPid());
System.exit(10);
} // end of while (true) }

以上代码片段主要的运行逻辑如下:

  1. Watchdog运行后,便开始无限循环,依次调用每一个HandlerChecker的scheduleCheckLocked()方法
  2. 调度完HandlerChecker之后,便开始定期检查是否超时,每一次检查的间隔时间由CHECK_INTERVAL常量设定,为30秒
  3. 每一次检查都会调用evaluateCheckerCompletionLocked()方法来评估一下HandlerChecker的完成状态:
    • COMPLETED表示已经完成
    • WAITING和WAITED_HALF表示还在等待,但未超时
    • OVERDUE表示已经超时。默认情况下,timeout是1分钟,但监测对象可以通过传参自行设定,譬如PKMS的Handler Checker的超时是10分钟
  4. 如果超时时间到了,还有HandlerChecker处于未完成的状态(OVERDUE),则通过getBlockedCheckersLocked()方法,获取阻塞的HandlerChecker,生成一些描述信息
  5. 保存日志,包括一些运行时的堆栈信息,这些日志是我们解决Watchdog问题的重要依据。如果判断需要杀掉system_server进程,则给当前进程(system_server)发送signal 9

只要Watchdog没有发现超时的任务,HandlerChecker就会被不停的调度,那HandlerChecker具体做一些什么检查呢? 直接上代码:

public final class HandlerChecker implements Runnable {

    public void scheduleCheckLocked() {
// Looper Checker中是不包含monitor对象的,判断消息队列是否处于空闲
if (mMonitors.size() == 0 && mHandler.getLooper().isIdling()) {
mCompleted = true;
return;
}
...
// 将Monitor Checker的对象置于消息队列之前,优先运行
mHandler.postAtFrontOfQueue(this);
} @Override
public void run() {
// 依次调用Monitor对象的monitor()方法
for (int i = 0 ; i < size ; i++) {
synchronized (Watchdog.this) {
mCurrentMonitor = mMonitors.get(i);
}
mCurrentMonitor.monitor();
}
...
}
}
  • 对于Looper Checker而言,会判断线程的消息队列是否处于空闲状态。 如果被监测的消息队列一直闲不下来,则说明可能已经阻塞等待了很长时间

  • 对于Monitor Checker而言,会调用实现类的monitor方法,譬如上文中提到的AMS.monitor()方法, 方法实现一般很简单,就是获取当前类的对象锁,如果当前对象锁已经被持有,则monitor()会一直处于wait状态,直到超时,这种情况下,很可能是线程发生了死锁

至此,我们已经分析了Watchdog的工作机制,回答了我们提出的第一个问题:

Watchdog定时检查一些重要的系统服务,举报长时间阻塞的事件,甚至杀掉system_server进程,让Android系统重启。

Watchdog机制概述的更多相关文章

  1. SQL Server 内存中OLTP内部机制概述(四)

    ----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...

  2. SQL Server 内存中OLTP内部机制概述(三)

    ----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...

  3. SQL Server 内存中OLTP内部机制概述(二)

    ----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...

  4. SQL Server 内存中OLTP内部机制概述(一)

    ----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...

  5. Android广播机制概述

    1.Android广播机制概述 Android广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者(广播接收器).广播作为Android组件间的通 ...

  6. VS2010/MFC编程入门之五(MFC消息映射机制概述)

    VS2010/MFC编程入门之五(MFC消息映射机制概述)-软件开发-鸡啄米 http://www.jizhuomi.com/software/147.html 上一讲鸡啄米为大家简单分析了MFC应用 ...

  7. JVM垃圾回收机制概述

    JVM垃圾回收机制概述 1.定义 是指JVM用于释放那些不再使用的对象所占用的内存. 2.方式 2.1引用计数(早期) 当引用程序创建引用以及引用超出范围时,JVM必须适当增减引用数.当某个对象的引用 ...

  8. SSL/TLS 协议运行机制概述(二)

    SSL/TLS 协议运行机制概述(二) 在SSL/TLS 协议运行机制概述(一)中介绍了TLS 1.2 的运行机制,现在我们来看年 TLS 1.3 的运行机制.会涉及到SSL/TLS 协议运行机制概述 ...

  9. SSL/TLS 协议运行机制概述(一)

    SSL/TLS 协议运行机制概述(一) SSL/TLS 发展史 1994年,NetScape 设计了SSL协议(Secure Sockets Layer) 1.0,未正式发布 1995年,NetSca ...

随机推荐

  1. mybatis 嵌套查询子查询column传多个参数描述

    https://my.oschina.net/softwarechina/blog/375762

  2. [D3] Debug D3 v4 with Dev Tools

    Since D3 outputs standard markup, you can use familiar dev tools and inspectors to debug your visual ...

  3. POJ 2536 Gopher II (ZOJ 2536) 二分图匹配

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1882 http://poj.org/problem?id=2536 题目大 ...

  4. 低成本开始互联网创业:探讨域名、服务器、CDN、邮箱等节流之道

    互联网创业一直是个热门话题,对这个问题我也有不断的思考. 今天,探讨下如何低成本开始互联网创业. 背景 愿意冒险去创业的同志,大多是"屌丝"而非"高富帅",大多 ...

  5. 在Java中,return null 是否安全, 为什么?

    Java代码中return value 为null 是不是在任何情况下都可以,为什么不会throw NullPointerException? Java语言层面:null值自身是不会引起任何问题的.它 ...

  6. HDU 5372 线段树

    给出两种操作: 第i个0:在x位置插入一个长度为i的线段,并输出该线段共覆盖了多少之前增加的线段 1:删除第i次插入的线段 官方题解:对于新插入的线段,查询有多少个线段左端点大于等于该线段的左端点. ...

  7. javascript面对对象编程 之继承

    上一篇博客中为大家介绍了javascript面向对象编程原则的封装,今天为大家介绍继承.在javascript中没有类的概念,全部不能像c#.java语言那样.直接的用类去继承类.比方如今有比方.如今 ...

  8. js闭包中的this(匿名函数中的this指向的是windows)

    js闭包中的this(匿名函数中的this指向的是windows) 一.总结 1.普通函数中的this指向的是对象,匿名函数中的this指向的是windows,和全局变量一样 2.让匿名函数中的thi ...

  9. 关于Linux启动时挂载rootfs的几种方式

    一直对Linux启动时挂载根文件系统的过程存在着很多疑问,今天在水木精华区找到了有用的资料,摘录如下: 1.Linux启动时,经过一系列初始化之后,需要mount 根文件系统,为最后运行init进程等 ...

  10. Sql Server 删除所有表 脚本

    如果由于外键约束删除table失败,则先删除所有约束: --/第1步**********删除所有表的外键约束*************************/ DECLARE c1 cursor f ...