戳蓝字“牛晓伟”关注我哦!
用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章,技术文章也可以有温度。
本文摘要
本文同样采用自述的方式来介绍systemserver进程的监控者watchdog,通过本文您将了解watchdog的作用,它是如何工作的。(文中代码基于Android13)
本文大纲
1. 我是谁
2. 我的启动
3. 我的工作原理
4. 总结
1. 我是谁
我的名字叫watchdog,翻译为中文是看门狗,说实话我不喜欢这个名字,大家叫我进程监控者吧,这名字才符合我做的事情,我运行在一个单独的线程同时我也是单例,也就是在systemserver进程只有我一个实例,谁要是想使用我就调用我的 getInstance 方法。
既然是进程监控者,那我监控的是systemserver进程,说的更具体点我会定时监控systemserver进程内具有Handler的线程是否存在耗时方法、是否存在死锁、是否存在长时间持有锁 的情况。这里可要明确一点并不是说我会检测systemserver进程中所有的具有 Handler 的线程和所有的锁,而是哪些线程和锁需要我监控的话“告知”我,我才会去监控它们。我监控的可并不是只有一个线程或者锁,我可是监控着很多的线程和锁。
如果检测到有耗时方法/死锁/长时间持有锁的情况,那我就会把systemserver进程杀掉,zygote进程检测到systemserver进程死掉的话也会“自杀”,init进程会重新启动zygote进程,zygote进程会启动systemserver进程。
或许你们会觉得我做的事情微不足道,但是我做的事情对于systemserver进程的稳定性来说那是至关重要的。如果没有我会发生什么情况呢?比如ActivityManagerService内的代码出现了死锁,那有可能从用户感官上来看就是整个界面会出现卡死不动的现象,因为是死锁导致的,死锁是无解的,那如果没有我整个界面就一直处于卡死不动的状态,用户只能强制重新启动设备。
接下来看下我是如何启动的吧。
2. 我的启动
在介绍systemserver进程的时候提到过启动各种 service 分为启动 bootstrap services、启动 core services、启动 other services 这三个阶段,因为我确实非常的重要也确实会被各种 service 提前使用到,因此会在第一阶段第一个启动我。
我运行在一个单独的线程,启动我后我的 run 方法就开始执行,至此我就启动成功了。我的启动是不是非常简单,接下来给大家展示下我的工作原理这个重头戏吧。
下面是相关代码,有兴趣自行取阅:
//SystemServer类
private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
//Watchdog是单例,因此调用getInstance方法获取它的实例,该方法在下面
final Watchdog watchdog = Watchdog.getInstance();
//调用它的start方法,该方法在下面
watchdog.start();
省略代码······
watchdog.init(mSystemContext, mActivityManagerService);
省略代码······
}
//Watchdog类
public static Watchdog getInstance() {
if (sWatchdog == null) {
sWatchdog = new Watchdog();
}
return sWatchdog;
}
//私有构造方法
private Watchdog() {
//new 一个线程
mThread = new Thread(this::run, "watchdog");
省略代码······
}
//start方法
public void start() {
//调用线程的start方法
mThread.start();
}
3. 我的工作原理
老规矩还是先看一幅图,下图展示了我的工作原理:
结合上面的图文用文字来总结下我的工作原理:
-
遍历所有的 HandlerChecker,并依次执行每个 HandlerChecker 的 scheduleCheckLocked 方法。
-
等待 timeout 时间后
-
检查所有的 HandlerChecker 的状态 (它们的状态有 COMPLETED、WAITING、WAITED_HALF、OVERDUE四种)
3.1 若没有 OVERDUE 状态的 HandlerChecker 则会继续从第 1 步开始重新执行
3.2 若有 OVERDUE 状态的 HandlerChecker,则会进行超时处理,比如收集日志,杀掉systemserver进程
我的工作原理是不是特别的简单,就是定时的重复执行上面的操作,除非检查到了超时的 HandlerChecker 才会退出。大家是不是对 HandlerChecker 不熟悉啊,那我就先来介绍下它,再来细细梳理下上面流程。
3.1 HandlerChecker是啥?
HandlerChecker的作用是监控具有Handler的线程是否有耗时方法 或者 是否有死锁或长时间持锁的情况。HandlerChecker 会对应一个 Handler,它也有自己的 timeout (超时时间),下面是它的类图
HandlerChecker 它实现了 Runnable的主要原因是它可以被 Handler 通过 post 的方式被执行,接下来下看下 HandlerChecker 的关键属性。
3.1.1 HandlerChecker 的关键属性
mHander
该属性是 Handler 类型的,一个 HandlerChecker 对应一个 Handler,HandlerChecker 其实就是监控 mHander 对应的线程是否存在耗时方法
mWaitMax
该属性的作用相当于 timeout,也就是每个被监控的 Handler 都会对应自己的超时时间,若执行时间超过 mWaitMax 则认为当前的 Handler 对应的线程存在耗时方法或者有死锁或者长时间持有锁的情况
mCompleted
是一个 boolean 值,为 false 则代表当前的 Handler 对应的线程存在耗时方法或者有死锁或者长时间持有锁的情况
mStartTime
代表执行的开始时间
mPauseCount
大于0则代表当前的 HandlerChecker 暂停工作
mMonitors
它是一个 Monitor 的集合,Monitor 主要作用是用来监控锁,也就是一个 HandlerChecker 是可以监控很多锁的。
3.1.2 HandlerChecker 的关键方法
scheduleCheckLocked
该方法是 HandlerChecker 开始执行的入口方法,该方法会有一个参数 handlerCheckerTimeoutMillis,该参数的作用就是告诉 HandlerChecker 的超时时间是多少。
getCompletionStateLocked
该方法的作用是返回执行状态, 主要有 COMPLETED、WAITING、WAITED_HALF、OVERDUE 四个状态。COMPLETED 代表已经执行完毕,WAITING 代表执行时间还没超过 timeout 的一半,WAITED_HALF 代表执行时间超过 timeout 的一半,OVERDUE 代表执行时间已经超过 timeout。
看了 HandlerChecker 的类信息后,那来看下 HandlerChecker 是如何做到监控的。
3.1.3 HandlerChecker 如何监控
老规矩还是来看一张图
图解
-
先把 mComplete 置为 false,在调用 mHander 的 postAtFrontOfQueue 方法,参数为当前 HandlerChecker 对象 (postAtFrontOfQueue 方法会把 Message 放入 Handler 对应的 MessageQueue 的最前面,保证它会被优先执行)
-
如果 run 方法被顺利执行,会遍历 mMonitors,并且执行每个 Monitor 的 monitor 方法,若所有的 Monitor都被顺利执行,则会把 mComplete 置为 true 代表执行完毕,不存在耗时方法和死锁情况。
Watchdog会在等待一段时间后,获取当前 HandlerChecker 的状态,如果状态为 OVERDUE,则代表超时。
HandlerChecker 若出现超时,则大致有以下几个原因:
-
HandlerChecker 对应的 run 方法没有被执行,主要是因为 mHander 对应的 Looper 中正在被执行的 Message 确实出现了耗时情况
-
mMonitors 中有些 Monitor 的 monitor 方法出现了超时情况
3.1.4 添加被监控的 Handler
systemserver进程中如果有 Handler 被监控的话,可以调用Watchdog的 addThread 方法,在Watchdog的构造方法中已经有部分 Handler 被添加了,如下代码:
//Watchdog 类的构造方法
private Watchdog() {
省略代码······
//它主要用来监控各种锁
mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
"foreground thread");
mHandlerCheckers.add(withDefaultTimeout(mMonitorChecker));
//监控systemserver的主线程的Handler
mHandlerCheckers.add(withDefaultTimeout(
new HandlerChecker(new Handler(Looper.getMainLooper()), "main thread")));
//UiThread主要用来显示UI,监控它的 Handler
mHandlerCheckers.add(withDefaultTimeout(
new HandlerChecker(UiThread.getHandler(), "ui thread")));
//IoThread主要用来io操作,监控它的Handler
mHandlerCheckers.add(withDefaultTimeout(
new HandlerChecker(IoThread.getHandler(), "i/o thread")));
//DisplayThread主要与显示有关
mHandlerCheckers.add(withDefaultTimeout(
new HandlerChecker(DisplayThread.getHandler(), "display thread")));
// AnimationThread动画相关
mHandlerCheckers.add(withDefaultTimeout(
new HandlerChecker(AnimationThread.getHandler(), "animation thread")));
省略代码······
}
3.1.5 添加被监控的锁
systemserver进程中的锁如果想要被监控的话,可以调用Watchdog的 addMonitor 方法,添加非常的简单,下面用 ActivityManagerService 来演示如何添加的
//ActivityManagerService类,它实现了Watchdog.Monitor接口
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
public ActivityManagerService(Context systemContext, ActivityTaskManagerService atm) {
省略代码······
Watchdog.getInstance().addMonitor(this);
省略代码······
}
public void monitor() {
//获取到锁后,不执行任何代码,立即释放锁;若获取不到锁会一直阻塞
synchronized (this) { }
}
}
其实也已经有很多的锁被添加进来了,如 ActivityManagerService、PowerManagerService、binder线程的锁 (处理任务的binder线程数比binder启动的最大binder线程数大的话会处于阻塞状态,否则不阻塞)
3.1.6 小结
HandlerChecker 会对应一个 Handler 和 零个或多个 Monitor,Monitor 的作用是监控是否死锁或者长时间持有锁,可以调用Watchdog的 addTHread 方法和 addMonitor 方法把需要被监控的 Handler 和锁进行监控,systemserver中已经有很多的 Handler 和锁被监控了。关于 HandlerChecker 介绍到此,那在回过头来重新梳理下我的工作原理。
3.2 再来看 scheduleCheckLocked 方法
Watchdog会遍历所有的 HandlerChecker,并且执行它的 scheduleCheckLocked 方法,该方法的作用其实就是告诉每个 HandlerChecker 可以去执行监控逻辑了,在执行之前每个 HandlerChecker 的状态也有所区别,比如有的 HandlerChecker 已经在执行中了,有的已经执行完毕了,有的可以先暂时暂停等。
如下是相关代码,自行取阅:
//Watchdog类
public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) {
//超时时间
mWaitMax = handlerCheckerTimeoutMillis;
//为true,则把mMonitorQueue添加到mMonitors
if (mCompleted) {
mMonitors.addAll(mMonitorQueue);
mMonitorQueue.clear();
}
//mPauseCount 大于0 说明需要暂停
if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
|| (mPauseCount > 0)) {
//暂停则把 mCompleted 置为true
mCompleted = true;
return;
}
//还在运行,则直接返回
if (!mCompleted) {
// we already have a check in flight, so no need
return;
}
//重置各种信息
mCompleted = false;
mCurrentMonitor = null;
mStartTime = SystemClock.uptimeMillis();
//放入MessageQueue的最前面,保证被优先执行
mHandler.postAtFrontOfQueue(this);
}
3.3 检查所有的 HandlerChecker 状态
Watchdog在等待一段时间后 (wait(timeout)),会从所有的 HandlerChecker 中选取最大的状态值,如下图:
每个 HandlerChecker 都会根据自己的执行状态和执行时间返回自己的状态值,状态值有 COMPLETED、WAITING、WAITED_HALF、OVERDUE 四种,它们都是整数类型的,它们的值是从小到大,也就时候 COMPLETED < WAITING < WAITED_HALF < OVERDUE,为啥要设置这几个状态值呢?
其主要作用是每个 HandlerChecker 的 timeout 超时时间有可能不一样,HandlerChecker 对应的 Handler 的线程执行的方法需要的时间也不一样,因此我Watchdog需要知道每个 HandlerChecker 的执行状态,到底执行了多久,还有多久没执行完毕,这样我对于每个 HandlerChecker 心里有底了。
从所有的 HandlerChecker 中获取到最大的状态值有如下几种情况:
-
若是 WAITED_HALF,则代表该 HandlerChecker 已经执行了多余 timeout 一半的时间,该 HandlerChecker 有可能会出现超时,因此这时候会收集日志,不会跳出循环
-
若是 OVERDUE,则有 HandlerChecker 出现了超时,则会进行超时处理,跳出循环
-
若是 COMPLETED 和 WAITING,则会重新执行,不会跳出循环
3.4 超时处理
检测到有超时的 HandlerChecker 会走到这步,超时处理其实主要做了以下几个工作:
- 收集日志
- 把超时的 HandlerChecker 的堆栈信息收集起来
- 杀掉SystemServer进程
zygote进程检测到systemserver进程死掉的话也会“自杀”,init进程会重新启动zygote进程,zygote进程会启动systemserver进程,进而启动launcher。
4. 总结
我是Watchdog,为了systemserver进程的稳定性,我会定时监控具有Handler线程是否有耗时方法、是否出现了死锁或者长时间持有锁的情况,若监控到以上这些情况,我会把systemserver进程杀掉,导致整个系统重新启动;若没有发现异常情况,则会循环进行监控。