天天看點

【原創】Android 系統穩定性 - Watchdog第3章 系統程序的Watchdog

文章都為原創,轉載請注明出處,未經允許而盜用者追究法律責任。 

很久之前寫的了,留着有點浪費,共享之(文章沒有完全寫完)。 

編寫者:李文棟 

第3章 系統程序的Watchdog

3.1 Watchdog簡介

        對于像筆者這樣沒玩過硬體的純軟程式員來說,第一次看到這個家夥的時候真心一頭霧水,隻是覺得這個名字很有意思。一番調查後發現,Watchdog機制最早來源于硬體,在計算機系統中,單片機的工作容易受到來自外界電磁場的幹擾,而陷入死循環,系統無法繼續工作,為了解決這個問題,便産生了一種專門用于監測單片機程式運作狀态的晶片,俗稱"看門狗"(Watchdog)。

        “看門狗”本身是一個定時器電路,内部會不斷的進行計時(或計數)操作。計算機系統和“看門狗”有兩個引腳相連接配接,正常運作時每隔一段時間就會通過其中一個引腳向“看門狗”發送信号,“看門狗”接收到信号後會将計時器清零并重新開始計時。而一旦系統出現問題,進入死循環或任何阻塞狀态,不能及時發送信号讓“看門狗”的計時器清零,當計時結束時,“看門狗”就會通過另一個引腳向系統發送“複位信号”,讓系統重新開機。

       這樣看來,向“看門狗”發送信号就像是“喂狗”,計時器就是“看門狗”的胃,當計時結束,狗餓了,就一口把系統咬死,讓它重生。

        軟體上的看門狗技術的思想和影響類似,例如Linux自帶的Watchdog。下面我們來看看Android系統程序的這條小狗吧。

3.2 系統程序的Watchdog

        Android系統程序中的Watchdog(以下簡稱WD)自然是用來監測系統程序的,它和硬體上的WD有什麼差別呢?系統程序中維護着大量的服務對象,其中有一些非常重要的對象,例如ActivityManagerService, WindowManagerService等,這些服務對象能夠被正常通路對系統的運作來說至關重要,暫且稱它們為關鍵對象。這些關鍵對象可能同時會被多個線程使用,是以需要在操作這些對象的地方使用同步鎖将它們保護起來,確定對象狀态的一緻性。但是如果某個線程鎖住關鍵對象後長時間沒有釋放鎖,其他線程無法使用對象完成後續的任務,那麼系統就會處于停滞狀态無法運作,此時就需要讓系統重新開機以恢複到一個正常的運作狀态。檢測這些關鍵服務對象是否被鎖住,和重新開機系統的操作就是由WD完成的。

        WD是如何完成這項神聖的使命的呢?我們先來了解一下WD的建立和啟動,再來剖析它的結構和流程。

3.2.1 Watchdog初始化和啟動

        WD對象是一個單例,是在系統啟動過程中的ServerThread線程中時建立的。

ServerThread.java → run()

Slog.i(TAG, "Init Watchdog");

Watchdog.getInstance().init(context, battery, power, alarm,

ActivityManagerService.self());

        需要注意的是WD的構造方法,其内部建立了一個HeartbeatHandler類型的對象,後面會詳細介紹它,不過可以肯定的是,這個Handler執行個體綁定了ServerThread線程的Looper。

Watchdog.java → Watchdog()

private Watchdog() {

        super("watchdog");

        mHandler = new HeartbeatHandler();

}

        再看一下init方法具體做了什麼。

Watchdog.java → init()

publicvoid init(Context context, BatteryService battery,

        PowerManagerService power, AlarmManagerService alarm,

        ActivityManagerService activity) {

        //儲存了幾個要用到的服務對象

        mResolver = context.getContentResolver();

        mBattery = battery;

        mPower = power;

        mAlarm = alarm;

        mActivity = activity;

        //注冊兩個BroadcastReceiver,來接收重新開機的消息

        context.registerReceiver(new RebootReceiver(),

        new IntentFilter(REBOOT_ACTION));

        mRebootIntent = PendingIntent.getBroadcast(context,

                0, new Intent(REBOOT_ACTION), 0);

        context.registerReceiver(new RebootRequestReceiver(),

        new IntentFilter(Intent.ACTION_REBOOT),

                android.Manifest.permission.REBOOT, null);

        mBootTime = System.currentTimeMillis(); //記錄啟動時間

}

        init方法很簡單,涉及的内容後面會做介紹。

        完成初始化後,WD還沒有啟動運作。看一下WD的類聲明可以知道,它是Thread類的子類,可以想到WD是在自己的線程中實作“定時器”的功能的,這很合理,要實作類似于獨立硬體完成的計時工作,用獨立線程完成對所在程序的監控是再好不過的。啟動WD線程是在ServerThread線程的最後階段完成的。

ActivityManagerService.self().systemReady(new Runnable() {

        publicvoid run() {

        ... ...

        Watchdog.getInstance().start();

        ... ...

        }

);

3.2.2 Watchdog結構剖析

        為了描述友善,先給出WD的類圖。

【原創】Android 系統穩定性 - Watchdog第3章 系統程式的Watchdog

        WD的結構還是比較簡單的,下面我們分幾個部分來分析。

1. Watchdog

        WD的“定時器”的功能是在單獨的線程中完成的,是以WD本身繼承了Thread,如前面所說,它是在ActivityManagerService的systemReady方法中啟動的。

2. HeartbeatHandler

        Android系統程序的WD和硬體的“看門狗”的思想是一緻的,但是實作方式上不同。WD線程在計時的過程中并不是被動的等待系統的“喂狗”信号,而是在每輪計時的開始向ServerThread(以下簡稱ST)線程發一個檢測消息,ST接收到消息後開始周遊Monitor對象集合,嘗試擷取每個對象的鎖,這個消息檢測過程就是在HeartbeatHandler(以下簡稱“HH”)中實作的。需要注意的是,HH綁定的是ST線程,ST作為系統程序的主線程執行檢測操作。

3. Monitor和被監控的服務對象

        WD監控的是系統程序中幾個關鍵的服務對象,對這類對象進行抽象定義,便有了Monitor接口,它隻有一個monitor方法。WD對實作了此接口的對象進行監控,其内部有一個存放Monitor對象的集合,任何對象隻要實作了Monitor接口,并且通過WD的addMonitor方法注冊進集合即可被監控。

        在GingerBread之前,被監控服務對象隻<!-- 确認一下是否是從2.3開始的 -->有ActivityManagerService、WindowManagerService和PowerManagerService,在此之後又增加了4個,分别是NetworkManagementService、MountService、NativeDaemonConnector和InputManager。

        Monitor接口的實作方法很簡單,例如AMS的實作:

ActivityManagerService.java → monitor()

publicvoid monitor() {

        synchronized (this) { }

}

        可以看到所謂的“監控服務對象”,說白了就是對這些對象進行死鎖檢測,如果能夠順利的獲得被監控對象的鎖則認為系統運作正常,如果長時間沒有獲得則認為系統處于停滞狀态,需要采取措施了。

4. RebootReceiver和RebootRequestReceiver

3.2.3 Watchdog工作流程

        WD的工作流程主要就是WD線程和HH線程之間的互動,先從WD的run方法看起。

Watchdog.java → run()

publicvoid run() {

        boolean waitedHalf = false;//➀記錄是否已經等待了一半

        while (true) {

        mCompleted = false;//用一個布爾變量标記是否完成死鎖檢測

        //➁向HH發送檢測信号,它是在ST線程中執行的

        mHandler.sendEmptyMessage(MONITOR);

        synchronized (this) {

        //發送檢測信号後會等待檢測操作完成,等待時間在正常運作的情況下是30秒

        long timeout = TIME_TO_WAIT;

        long start = SystemClock.uptimeMillis();

        while (timeout > 0 && !mForceKillSystem) {

        try {

                wait(timeout);

                ... ...

        }

.        .. ...

        通過以上代碼可以知道,監控過程就是一個死循環,每次循環都會做一輪死鎖檢測。有兩個需要注意的點,說明如下:

➀ WD的這個“定時器”每輪檢測的逾時時間是30秒,但是30秒逾時後WD并不會馬上重新開機系統,而是将waitedHalf設定為true,認為隻是等待了一半的時間,也就是說WD想多給那個被鎖住的對象一次機會,做兩輪檢測,如果仍然逾時再殺也不晚。WD還是很有人情味的,後面會看到waitedHalf何時被設定為true的。

➁ 每輪的檢測操作不是由WD線程自己完成的,而是發送一個消息給HH,由HH所綁定的ST線程完成。它是怎麼做的呢?接下來轉到HH一探究竟。

finalclass HeartbeatHandler extends Handler {

        ... ...

        caseMONITOR: {

        ... ...

        finalint size = mMonitors.size();

        for (int i = 0 ; i < size ; i++) {

                //記錄下目前正在被檢測的Monitor對象,這很重要

                mCurrentMonitor = mMonitors.get(i);

                mCurrentMonitor.monitor();//檢測死鎖

        }

        synchronized (Watchdog.this) {

                mCompleted = true;//标記檢測完成,WD線程會用此判斷是否完成

                mCurrentMonitor = null;

        ... ...

        邏輯很簡單,不多解釋,隻是請留意HH做完死鎖檢測後沒有用notifyAll喚醒WD線程,是以正常情況下WD線程會在逾時後再繼續下一輪檢測。HH比較會偷懶。

接下來又回到WD線程。

        ... ... //WD線程結束wait等待

        if (mCompleted && !mForceKillSystem) {

                //如果檢測成功,則重置waitedHalf标記,繼續下一輪檢測

                waitedHalf = false;

                continue;

        }

        if (!waitedHalf) {

                //執行到這裡,說明檢測過程阻塞了,沒有完成,并且waitedHalf為false,說明

                //這是死鎖檢測失敗的第一輪檢測。通過AMS将系統程序中各個線程的函數調用棧

                //輸出到/data/anr/traces.txt檔案中,同時也會輸出幾個重要的native程序的

                //backtrace,以便提供更多資訊來定位問題,因為Java層的阻塞很有可能是native

                //層的阻塞造成的。

                ArrayList<Integer> pids = new ArrayList<Integer>();

                pids.add(Process.myPid());

                ActivityManagerService.dumpStackTraces(true, pids, null, null,

                        NATIVE_STACKS_OF_INTEREST);

                waitedHalf = true;//設定為true,說明WD線程等了一輪了

                continue;//再來一輪,給個機會

        }

        如果接下來的第二輪死鎖檢測仍然失敗,則上述的代碼就不會執行,繼續往下走。

        //此後便是為了能夠友善分析死鎖原因,而輸出的各種類型的日志資訊

        final String name = (mCurrentMonitor != null) ?

        mCurrentMonitor.getClass().getName() : "null";

        //➀記錄正在執行死鎖檢測的對象

        EventLog.writeEvent(EventLogTags.WATCHDOG, name);

        //➁再次輸出系統程序的函數棧資訊

        ArrayList<Integer> pids = new ArrayList<Integer>();

        pids.add(Process.myPid());

        //同時輸出com.android.phone程序的函數棧,因為電話系統對于手機來說是最重要的

        //子產品,自然要重點對待

        if (mPhonePid > 0) pids.add(mPhonePid);

        final File stack = ActivityManagerService.dumpStackTraces(

                !waitedHalf, pids, null, null, NATIVE_STACKS_OF_INTEREST);

        ... ...

        if (RECORD_KERNEL_THREADS) {

                dumpKernelStackTraces();//輸出一部分Kernel的資訊幫助定位問題

        }

        ... ...

        //➂在一個子線程中輸出到DropBox中

        mActivity.addErrorToDropBox(

                "watchdog", null, "system_server", null, null,

                name, null, stack, null);

        ... ...

        //如果調試器沒有連結則直接退出程序

        if (!Debug.isDebuggerConnected()) {

                Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + name);

                Process.killProcess(Process.myPid());

                System.exit(10);

        } else {//如果正在Debug,那你就可以斷點調試了

        ... ...

有三個關鍵點需要注意:

➀ 記錄正在執行死鎖檢測的對象,如果name為”null”,其實就相當于ServerThread線程還沒有執行HH的handleMessage方法,就在其他地方阻塞了。是以要特别注意,如果在分析trace資訊時發現沒有因為被檢測的關鍵服務對象而發生阻塞,那麼就需要看看ServerThread線程的函數調用棧,确定真正的阻塞原因。

➁ 在進行系統重新開機前會做兩輪死鎖檢測,第一輪會建立traces.txt檔案,但第二輪會在原有檔案的基礎上續寫,是以你會在trace資訊中看到兩次系統程序各個線程的函數調用棧資訊。

➂ 在一個子線程中輸出到DropBox中,是以如果這次儲存在traces.txt中的死鎖資訊沒有來得及檢視就被覆寫了,那麼可以到/data/system/dropbox目錄下找到這次日志的備份。

Watchdog的實作說白了其實就是在一個線程中建立消息循環,通過Message和成員變量線上程間進行通訊,這和Handler機制的本質是一樣的。

至此,WD的工作流程介紹完了,還算比較簡單,在本章的最後附上了WD的流程圖。接下來會介紹一些WD檢測到死鎖後導緻重新開機的問題的分析方法,對于Android系統工程師來說,處理這類問題肯定是家常便飯了。

3.3 Watchdog引起的重新開機問題分析方法

3.3.1 被監測對象死鎖

(待續)

3.3.2 ServerThread線程阻塞

(待續)

Watchdog的流程圖

【原創】Android 系統穩定性 - Watchdog第3章 系統程式的Watchdog