1.概览

  看门狗可以是软件上的实现也可以是硬件上的实现,不过后者的可靠性要比前者高的多。至于看门狗的作用/功能可以用一句话概括:
    在一段时间内没有对看门狗进行喂狗操作,那么就触发系统重启。
  在system_server虚拟机中,Framwork则是引入了软件看门狗,来监控系统中的线程是否被卡死。如果被 Watchdog 所监控的线程在一段时间内没有进行喂狗操作的话,那么就会触发 system_server 的 crash,从而导致 Android 系统的重启。
  在开始正文前,先明确两个概念,
    1) 接口 Monitor 中的 monitor 本章中成为喂狗操作,喂狗的一个具体操作。
    2) HandlerChecker 类为执行喂狗操作的 Handler 也对应这一个 Looper,如果不理解 Looper可以认为它就是一个线程。

2.如何使用

  Watchdog 在初始化完后,用户就可以使用它提供的接口来设置各种喂狗操作(monitor)了,注意了,此处的喂狗操作是并的关系,即任何一个喂狗操作(monitor)的失败都会造成system_server进程被 kill,它支持两种喂狗操作的的添加。
    a)使用接口 addMonitor,将喂狗操作加到 foreground thread 线程中,这种喂狗操作添加是不需要自己提供一个线程的,在该喂狗操作失败后会卡住 foreground thread 线程,从而触发系统重启。

//frameworks/base/services/java/com/android/server/SystemServer.java
Watchdog()
        addMonitor(new BinderThreadMonitor()/*monitor*/);
            mMonitorChecker.addMonitorLocked(monitor);
                //private final ArrayList<Monitor> mMonitorQueue = new ArrayList<Monitor>();
                mMonitorQueue.add(monitor);

    b)使用接口 addThread ,添加新的喂狗线程,其内部的任何一个喂狗操作(monitor)失败后也触发系统的重启。

//frameworks/base/services/core/java/com/android/server/Watchdog.java
addThread(Handler thread)
    //private final ArrayList<HandlerCheckerAndTimeout> mHandlerCheckers = new ArrayList<>();
    mHandlerCheckers.add(withDefaultTimeout(new HandlerChecker(thread, name)));

3.实例化及启动

  system_server进程中的看门狗,当然在 system_server 中启动

//frameworks/base/services/java/com/android/server/SystemServer.java
startBootstrapServices
    final Watchdog watchdog = Watchdog.getInstance();
        //code 1
        mThread = new Thread(this::run, "watchdog");
        //code 2
        mMonitorChecker = new HandlerChecker(FgThread.getHandler(),"foreground thread");
        mHandlerCheckers.add(withDefaultTimeout(mMonitorChecker));
                ...
        //code 3
        addMonitor(new BinderThreadMonitor()/*monitor*/);
            mMonitorChecker.addMonitorLocked(monitor);
                //from HandlerChecker class
                mMonitorQueue.add(monitor);
    //code 4
    watchdog.start();

3.1 code 1 – watchdog 线程但不执行

3.2 code 2 – 监听 FgThread 线程

  虽然监听的是 FgThread 的Handler,但是 Handler 实际上包括一个Message 和 一个 Looper。
  foreground thread 线程的则是 HandlerChecker 类的一个实例,它用于执行具体的喂狗操作。下面来看看 HandlerChecker 的类结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i0rBue2Q-1686451608872)(./pictures/HandlerChecker.PNG)]
  从类图可知 HandlerChecker 实现了接口 Runnable,可见最终的执行体就是 run 了,下面就看看 “foreground thread” HandlerChecker 的实现

//frameworks/base/services/core/java/com/android/server/Watchdog.java
public void run() {
    final int size = mMonitors.size();
    for (int i = 0 ; i < size ; i++) {
        mCurrentMonitor = mMonitors.get(i);
        mCurrentMonitor.monitor();
    }
    mCompleted = true;
    mCurrentMonitor = null;
}

  mMonitors 中储存着该线程的所有喂狗操作,在run方法中对这些喂狗操作进行了遍历执行,只只要有任何一个喂狗操作没有正常返回都会卡住 foreground thread 线程,从而触发系统重启。
  当然如果没有任何喂狗操作卡住,那么就代表当前线程状态是喂狗成功的,然后置位状态 mCompleted 为 true,代表喂狗成功。

3.3 code 3 – 添加可用线程状态的喂狗操作

  这是添加一个喂狗操作的实例,它的作用则是监控当前进程的可用binder线程是否大于进程的最大支持的binder 线程数,即如果在看门狗的超时时间内依然没有可用的binder线程,那么也会触发系统重启。
  它的实现也很简单,只是创建以一个喂狗操作并添加到上foreground thread 线程中去。

3.4 code 4 – 启动看门狗

  Watchdog::run会在新线程中被执行,这部分实现分步骤来讲解
a.遍历执行每一个 HandlerChecker

    for (int i=0; i<mHandlerCheckers.size(); i++) {
        hc.checker().scheduleCheckLocked(hc.customTimeoutMillis()
                .orElse(watchdogTimeoutMillis * Build.HW_TIMEOUT_MULTIPLIER));
    }

  最终会调用到注册 HandlerChecker 时传入的 Hanlder,并将 HandlerChecker post 到传入 Handler 的 Message 中去,等待Looper/线程来执行。
scheduleCheckLocked的实现如下

scheduleCheckLocked
    mWaitMax = handlerCheckerTimeoutMillis;
    mCompleted = false;
    mCurrentMonitor = null;
    mStartTime = SystemClock.uptimeMillis();
    mHandler.postAtFrontOfQueue(this);

  handlerCheckerTimeoutMillis为当前喂狗线程执行完所有喂狗操作的超时时间,即在超时时间内喂狗线程没有做完它所包含的所有喂狗操作,那么Watchdog会判定喂狗失败,并重启系统。
b.消耗看门狗的超时时间

while (timeout > 0) {
    mLock.wait(timeout);
    timeout = checkIntervalMillis - (SystemClock.uptimeMillis() - start);
}

c.喂狗操作的状态检查
  mWaitMax 会用于判断当前喂狗操作的状态,如COMPLETED、WAITING、WAITED_HALF以及OVERDUE,如果时返回OVERDUE那么也就代表喂狗失败了,即在该次喂狗操作中消耗完了用户预设的最大等待时间了。

final int waitState = evaluateCheckerCompletionLocked();
    int state = COMPLETED;
    for (int i=0; i<mHandlerCheckers.size(); i++) {
        HandlerChecker hc = mHandlerCheckers.get(i).checker();
        state = Math.max(state, hc.getCompletionStateLocked());
    }
    return state; 

  返回所有喂狗操作结果指的或结果,其中 COMPLETED、WAITING、WAITED_HALF 以及 OVERDUE 的值由小变大,所以相当于指返回最差的喂狗结果。下面是对于每个值的处理
    1)COMPLETED
      本次喂狗结束,所有监听线程都在正常工作没有被blocked住。
    2)WAITING
      还在每个喂狗操作的超时时间一半内,继续等待。
    3)WAITED_HALF
      每个喂狗操作的消耗时间已经超过超时时间的一半内了,先坐下预先crash的工作,并继续等待。
    4)OVERDUE
      出现喂狗操作超时的了,需要进行debugging信息抓取并重启系统了。
  实际上也就是默认超时时间一半的时候来检查下各个喂狗线程中喂狗操作的状态,对于状态的判断见上面 HandlerChecker 中喂狗操作的讲解。
d.如果出现超时,准备重启系统

Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
//打印blocked 的线程
WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);
Slog.w(TAG, "*** GOODBYE!");
//发出杀掉自己的信号
Process.killProcess(Process.myPid());
//系统10ms后退出
System.exit(10);

4.添加一个喂狗操作

  见 3.2 code 2 – 监听 FgThread 线程

5.添加喂狗线程

  见 3.3 code 3 – 添加可用线程状态的喂狗操作

6.对默认超时时间配置

  Watchdog是支持在运行时对默认超时时间进行更改的,方便调试。

//frameworks/base/services/java/com/android/server/SystemServer.java
startOtherServices
    Watchdog.getInstance().registerSettingsObserver(context);
        //frameworks/base/services/core/java/com/android/server/Watchdog.java
        context.getContentResolver().registerContentObserver(
            Settings.Global.getUriFor(Settings.Global.WATCHDOG_TIMEOUT_MILLIS),
            false,
            new SettingsObserver(context, this),
            UserHandle.USER_SYSTEM);

  使用 ContentResolver 服务功能,所以这也就是为什么不和实例一样放在 startBootstrapServices 中而单独放在 startOtherServices 的原因。因为 ContentResolver 服务并不是 Bootstrap 系统。
  机制也非常简单,通过监听配置 Settings.Global.WATCHDOG_TIMEOUT_MILLIS 的改变来更新看门狗的默认时间。实现则位于 SettingsObserver 中,现在看看它的实现

private static class SettingsObserver extends ContentObserver {
    public void onChange() {
        mWatchdog.updateWatchdogTimeout(Settings.Global.getLong( mContext.getContentResolver(), Settings.Global.WATCHDOG_TIMEOUT_MILLIS, DEFAULT_TIMEOUT)/*timeoutMillis*/);
            mWatchdogTimeoutMillis = timeoutMillis;
    }
}

  所以开发者可以通过如下命令来调整默认的喂狗超时时间,最终 mWatchdogTimeoutMillis 的值也会被修改为用户传入的合法值。

board:/ # settings put global system_server_watchdog_timeout_ms 61
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐