天天看點

Watchdog-最後的看門狗

Watchdog-最後的看門狗

Watchdog是什麼

Watchdog是android framework中一個java類(線上源碼), 也可以認為是一種系統檢查和處理的機制。比如在Android系統中,SystemServer程序會對應用程式進行卡頓檢測和處理(即ANR檢測等),那麼誰來檢測和處理SystemServer程序的服務呢?如果AMS、PMS等核心服務出現了卡死誰來處理呢?答案是Watchdog,在Android中,設計了Watchdog機制來檢測和處理系統核心服務是否能正常工作。

Watchdog怎麼工作

Watchdog作為系統服務的檢查者,在系統啟動過程的較早階段就已經啟動了。

啟動Watchdog
#SystemServer.java
private void startBootstrapServices() {
        // 目前方法在android系統啟動後首先調用,主要作用是啟動系統基礎服務,而Watchdog是在啟動基礎服務之前就啟動了
        final Watchdog watchdog = Watchdog.getInstance();
        watchdog.start();
       //.....
    }
           
#Watchdog.java
public class Watchdog extends Thread {
    static Watchdog sWatchdog;
    static final long DEFAULT_TIMEOUT = DB ? 10*1000 : 60*1000;
    final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<>();
    final HandlerChecker mMonitorChecker;
    
    public static Watchdog getInstance() {
        if (sWatchdog == null) {
            //單例模式,說明Watchdog在SystemServer程序内隻有一個執行個體 
            sWatchdog = new Watchdog();
        }
        return sWatchdog;
     }

    private Watchdog() {
        super("watchdog");
        //為每一個我們想檢測的線程初始化HandlerChecker對象,該對象是Runnable的子類
        // 注意,此處并不會檢測背景線程,因為背景線程可能會運作很長時間,無法保證及時性
        
        // 建立螢幕檢查者,這裡加入的是前台線程的Handler, DEFAULT_TIMEOUT為60s
        mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
                "foreground thread", DEFAULT_TIMEOUT);
         //将螢幕檢查者加入隊列       
        mHandlerCheckers.add(mMonitorChecker);
        // 添加主線程的檢查者
        mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
                "main thread", DEFAULT_TIMEOUT));
        // 添加UI線程檢查者
        mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
                "ui thread", DEFAULT_TIMEOUT));
        // 添加IO線程檢查者
        mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
                "i/o thread", DEFAULT_TIMEOUT));
        // 添加Display線程檢查者
        mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
                "display thread", DEFAULT_TIMEOUT));
        // 添加動畫線程檢查者
        mHandlerCheckers.add(new HandlerChecker(AnimationThread.getHandler(),
                "animation thread", DEFAULT_TIMEOUT));
        // 添加視窗動畫線程檢查者
        mHandlerCheckers.add(new HandlerChecker(SurfaceAnimationThread.getHandler(),
                "surface animation thread", DEFAULT_TIMEOUT));

        // 建立并添加Binder線程檢查者
        addMonitor(new BinderThreadMonitor());
        //檔案相關
        mOpenFdMonitor = OpenFdMonitor.create();
    }
    
    public void addMonitor(Monitor monitor) {
        synchronized (this) {
            mMonitorChecker.addMonitorLocked(monitor);
        }
    }
}
           

整理下Watchdog的建立源碼,其重點如下:

  1. Watchdog繼承自Thread,本質上是線程
  2. 在Watchdog是單例,該對象建立時添加了7個HandlerChecker
  3. 将BinderThreadMonitor加入了mMonitorChecker對象

那麼HandlerChecker和Monitor到底是個什麼東西呢 ?

#Watchdog.java
public final class HandlerChecker implements Runnable {
        private final Handler mHandler; //目前檢查者工作使用的Handler
        private final String mName; //目前檢查者名字
        private final long mWaitMax; //最大等待時間
        private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
        private final ArrayList<Monitor> mMonitorQueue = new ArrayList<Monitor>();
        private boolean mCompleted; //目前檢查是否已完成
        private Monitor mCurrentMonitor; //目前正在執行的監控器
        private long mStartTime; //檢測的開始時間
        private int mPauseCount; //暫停次數

        HandlerChecker(Handler handler, String name, long waitMaxMillis) {
            mHandler = handler;
            mName = name;
            mWaitMax = waitMaxMillis;
            mCompleted = true;
        }

        void addMonitorLocked(Monitor monitor) {
            //這裡先将監控器添加到mMonitorQueue集合而不是mMonitors集合,主要是為了安全,不想在mMonitors集合中的Monitor正在執行的時候更新集合,
            mMonitorQueue.add(monitor);
        }
    }


  public interface Monitor {
        void monitor();
    }
           

現在我們大緻知道Watchdog、HandleChecker和Monitor分别是什麼了,接下來看下watchdog.start(),watchdog是一個線程,是以直接到其run方法

#Watchdog.java
    public void run() {
        boolean waitedHalf = false;
        //無限循環
        while (true) {
            final List<HandlerChecker> blockedCheckers;
            final String subject;
            final boolean allowRestart;//是否允許重新啟動系統
            int debuggerWasConnected = 0;//debug連結數量
            synchronized (this) {
                long timeout = CHECK_INTERVAL; //檢測周期:debug下為5s,正式版本為30s
                for (int i=0; i<mHandlerCheckers.size(); i++) {
                    HandlerChecker hc = mHandlerCheckers.get(i);
                    //調用對應HandlerChecker的scheduleCheckLocked方法,該方法會觸發真正的檢查,稍後分析
                    hc.scheduleCheckLocked();
                }
                //...
                long start = SystemClock.uptimeMillis();
                //此處timeout > 0表明後續代碼一定會在檢測周期過後才會觸發
                while (timeout > 0) {
                    try {
                        wait(timeout);
                    } catch (InterruptedException e) {
                        Log.wtf(TAG, e);
                    }
                    timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
                }

                boolean fdLimitTriggered = false;
                if (mOpenFdMonitor != null) {
                    fdLimitTriggered = mOpenFdMonitor.monitor();
                }

                if (!fdLimitTriggered) {
                    final int waitState = evaluateCheckerCompletionLocked();//評估HandlerChecker檢查者的狀态
                    //處理檢測結果,稍後分析
        }
    }
           

Watchdog的run方法大緻做了如下事情:

  1. 周遊檢查者集合,觸發每一個HandlerChecker的scheduleCheckLocked來開啟檢查
  2. 等待一個檢查周期後,開始擷取檢查結果
  3. 根據檢查結果進行處理

接下來我們詳細看下watchdog的run方法的三個流程:

1.發起檢測

public void scheduleCheckLocked() {
            //mCompleted預設是true
            if (mCompleted) {
                // 這裡把mMonitorQueue集合中的資料添加搭配mMonitor中,因為此時HandlerChecker不在工作隊列中,這樣不會因為多線程操作觸發快速失效機制
                mMonitors.addAll(mMonitorQueue);
                mMonitorQueue.clear();
            }
            if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
                    || (mPauseCount > 0)) {
                //沒有需要監視的服務,或者目前MessageQueue正在poll時,不用檢查
                mCompleted = true;
                return;
            }
            if (!mCompleted) {
                // 不用重複檢查
                return;
            }
            mCompleted = false;
            mCurrentMonitor = null;
            mStartTime = SystemClock.uptimeMillis();
            mHandler.postAtFrontOfQueue(this);//将目前HandlerChecker添加到mHandler所屬MessageQueue隊列中頭
        }
           

scheduleCheckLocked方法做了兩件事:

  1. 将新添加的Monitor轉移到mMonitor集合中
  2. 重置狀态,并向持有的mHandler中post一個Runnable(HandlerChecker自身)。由消息機制可以了解其會在mHandler所屬線程執行目前HandlerChecker的run方法,如下
public void run() {
            //周遊mMonitors集合中每一個元素
            final int size = mMonitors.size();
            for (int i = 0 ; i < size ; i++) {
                synchronized (Watchdog.this) {
                    mCurrentMonitor = mMonitors.get(i);
                }
                //執行其monitor方法來檢測,由于其是在HandlerCheck的run方法中調用的,是以是運作在Handler對應的線程中的。
                mCurrentMonitor.monitor();
            }
            synchronized (Watchdog.this) {
                mCompleted = true;
                mCurrentMonitor = null;
            }
        }
           

由源碼分析可得

  • HandlerChecker的run方法在Handler所屬線程執行,其執行内容為周遊每個Monitor,執行monitor方法
  • 所有Monitor檢查完了之後,設定mCompleted為true,表示完成檢查
    Watchdog-最後的看門狗

2 擷取檢查結果

由Watchdog的run方法得知,檢查結果主要是擷取evaluateCheckerCompletionLocked方法的傳回值,如下

#HandlerChecker
    static final int COMPLETED = 0;
    static final int WAITING = 1;
    static final int WAITED_HALF = 2;
    static final int OVERDUE = 3;

private int evaluateCheckerCompletionLocked() {
        int state = COMPLETED;
        for (int i=0; i<mHandlerCheckers.size(); i++) {
            HandlerChecker hc = mHandlerCheckers.get(i);
            //取得目前HandlerChecker集合中state最大值
            state = Math.max(state, hc.getCompletionStateLocked());
        }
        return state;
    }

public int getCompletionStateLocked() {
            //在檢測開始時mCompleted為false,檢查結束時為true
            if (mCompleted) {
                return COMPLETED;
            } else {
                long latency = SystemClock.uptimeMillis() - mStartTime;
                if (latency < mWaitMax/2) {
                     //如果目前耗費時間小于最大等待時長的一半,傳回WAITING
                    return WAITING;
                } else if (latency < mWaitMax) {
                     //如果目前耗費時間大于等于最大等待時長的一半,且小于最大等待時長,傳回WAITED_HALF
                    return WAITED_HALF;
                }
            }
            return OVERDUE;//表示已經逾時了,即阻塞或者挂起了
        }
           

由源碼可知,檢查結果一共有4種:

  • COMPLETED:表示HandlerChecker檢測完成,本次沒有阻塞
  • WAITING:表示本次檢測還未完成,目前耗時小于最大耗時的一半
  • WAITED_HALF:表示本次檢測還未完成,目前耗時大于最大耗時的一半
  • OVERDUE: 表示本次檢測逾時了

    那麼Watchdog針對上述4種檢測結果,會如何處理呢?

3 處理檢測結果

處理檢測結果的代碼在Watchdog的run方法中,如下:

#Watchdog.java
    public void run() {
        boolean waitedHalf = false;
        //無限循環
        while (true) {
            final List<HandlerChecker> blockedCheckers;
            final String subject;
            final boolean allowRestart;//是否允許重新啟動系統
            int debuggerWasConnected = 0;//debug連結數量
            synchronized (this) {
               //... 檢測階段,前面分析過了
                if (!fdLimitTriggered) {
                    final int waitState = evaluateCheckerCompletionLocked();//評估HandlerChecker檢查者的狀态
                    if (waitState == COMPLETED) {
                        // 全部完成,則認為一切工作正常,本次檢查結束 重置waitedHalf為false
                        waitedHalf = false;
                        continue;//開始下一輪檢測
                    } else if (waitState == WAITING) {
                        // 在等待狀态,目前耗時小于最大限制的一半,
                        continue;//再檢查一次
                    } else if (waitState == WAITED_HALF) {
                        if (!waitedHalf) {
                           //在等待狀态,目前耗時大于最大限制的一半,但還未逾時
                            ArrayList<Integer> pids = new ArrayList<Integer>();
                            pids.add(Process.myPid());
                            //通過AMS儲存目前程序的堆棧資訊
                            ActivityManagerService.dumpStackTraces(pids, null, null,
                                getInterestingNativePids());
                            waitedHalf = true;
                        }
                        continue;//在檢查一次
                    }

                    // 走到這裡表明一些檢查器已經檢查出問題了
                    blockedCheckers = getBlockedCheckersLocked();//擷取已阻塞的那些HandlerChecker
                    subject = describeCheckersLocked(blockedCheckers);//建構提示字元串
                } else {
                    
                }
                allowRestart = mAllowRestart;
            }
            //代碼走到這裡,表示系統大機率已挂起了
            ArrayList<Integer> pids = new ArrayList<>();
            pids.add(Process.myPid());
            if (mPhonePid > 0) pids.add(mPhonePid);
            通過AMS儲存再次目前程序的堆棧資訊
            final File stack = ActivityManagerService.dumpStackTraces(
                    pids, null, null, getInterestingNativePids());
            //等待5s,為了确認堆棧資訊都被寫入
            SystemClock.sleep(5000);

            // 觸發核心以轉儲所有阻塞的線程,并将所有CPU上的回溯到核心日志
            doSysRq('w');
            doSysRq('l');

            // ...dropbox相關
            IActivityController controller;
        synchronized (this) {
            controller = mController;
        }
        if (controller != null) {
            //将阻塞狀态報告給activity controller,
            try {
                Binder.setDumpDisabled("Service dumps disabled due to hung system process.");
                //傳回值為1表示繼續等待,-1表示殺死系統
                int res = controller.systemNotResponding(subject);
                if (res >= 0) {
                    waitedHalf = false;
                    continue; //設定ActivityController的某些情況下,可以讓發生Watchdog時繼續等待
                }
            } catch (RemoteException e) {
            }
        }

        //當debugger沒有attach時,才殺死程序
        if (Debug.isDebuggerConnected()) {
            debuggerWasConnected = 2;
        }
        if (debuggerWasConnected >= 2) {
            Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
        } else if (debuggerWasConnected > 0) {
            Slog.w(TAG, "Debugger was connected: Watchdog is *not* killing the system process");
        } else if (!allowRestart) {
            Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
        } else {
            Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
            //周遊輸出阻塞線程的棧資訊
            for (int i=0; i<blockedCheckers.size(); i++) {
                Slog.w(TAG, blockedCheckers.get(i).getName() + " stack trace:");
                StackTraceElement[] stackTrace
                        = blockedCheckers.get(i).getThread().getStackTrace();
                for (StackTraceElement element: stackTrace) {
                    Slog.w(TAG, " at " + element);
                }
            }
            Slog.w(TAG, "*** GOODBYE!");
            //殺死程序system_server
            Process.killProcess(Process.myPid());
            System.exit(10);
        }
        waitedHalf = false;
        }
        }
    }
    
   private ArrayList<HandlerChecker> getBlockedCheckersLocked() {
        ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>();
        for (int i=0; i<mHandlerCheckers.size(); i++) {
            HandlerChecker hc = mHandlerCheckers.get(i);
            if (hc.isOverdueLocked()) {
                //統計逾時那些checker
                checkers.add(hc);
            }
        }
        return checkers;
    }
           

處理檢測結果也是針對4種不同檢測結果分開進行的:

  • COMPLETED:繼續下一輪檢測
  • WAITING:繼續下一輪檢測
  • WAITED_HALF:dump堆棧,并繼續下一輪檢測
  • OVERDUE: 逾時,dump堆棧和核心線程,如果沒有特例,則殺掉程序并重新開機

    有細心的小夥伴

總結與反思

Watchdog是系統檢測的最後保障,其設計采用了單線程+多個HanlderChecker的結構,流程上主要包含啟動監控檢測、添加監控、擷取結果并處理。其巧妙的地方在于把檢測過程的耗時分攤到了各Handler對應的工作線程,保證了Watchdog檢測階段的性能。該方案也可稍作變通,以解決其他問題,比如檢測卡頓