Android中的軟件Watchdog

由於Android的SystemServer內有一票重要Service,所以在進程內有一個軟件實現的Watchdog機制,用於監視SystemServer中各Service是否正常工作。如果超過一定時間(默認30秒),就dump現場便於分析,再超時(默認60秒)就重啓SystemServer保證系統可用性。同時logcat中會打印類似下面信息:

W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in monitor com.android.server.am.ActivityManagerService on foreground thread (android.fg), Blocked in handler on ActivityManager (ActivityManager), Blocked in handler on WindowManager thread (WindowManager)


主要實現代碼位於/frameworks/base/services/core/java/com/android/server/Watchdog.java和/frameworks/base/core/jni/android_server_Watchdog.cpp。大體的框架很簡單。Watchdog是SystemServer中的獨立線程,它隔一定時間間隔會向各監視線程調度一次檢查操作。這個檢查操作當中會調用已註冊的Monitor對象。如果Monitor對象上產生死鎖,或是關鍵線程hang住,那麼該檢查必定不能按時結束,這樣就被Watchdog檢查到。


先來看看總體類圖。因爲是唯一的,Watchdog實現爲Singleton。其中維護HandlerChecker數組,對應要檢查的線程。HandlerChecker數組中有Monitor數組,對應要檢查的Monitor對象。要接受檢查的對象需要實現Monitor接口。



初始化是從SystemServer的startOtherServices()中開始的,其大體流程如下:


首先,在SystemServer.java中,會創建Watchdog並啓動。

472            Slog.i(TAG, "Init Watchdog");
473            final Watchdog watchdog = Watchdog.getInstance();
474            watchdog.init(context, mActivityManagerService);
…
1120                Watchdog.getInstance().start();
在Watchdog的構造函數中,會爲每個要檢查的線程創建HandlerChecker,並加到mHandlerCheckers這個隊列中。首先是FgThread。它繼承自ServiceThread,是一個單例,負責那些常規的前臺操作,它不應該被後臺的操作所阻塞。在Watchdog.java中:
215        mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
216                "foreground thread", DEFAULT_TIMEOUT);
217        mHandlerCheckers.add(mMonitorChecker);

接下來,對於System Server的主線程,UI線程,IO線程和Display線程,分別做相同操作,這坨線程和FgThread一樣都繼承自ServiceThread。在init()函數中,接下來會調用registerReceiver()來註冊系統重啓的BroadcastReceiver。在收到系統重啓廣播時會執行RebootRequestReceiver的onReceive()函數,繼而調用rebootSystem()重啓系統。它允許其它模塊(如CTS)通過發廣播來讓系統重啓。


然後,各個需要被Watchdog監視的Service需要將自己進行註冊。它們都實現了Watchdog.Monitor接口,其中主要是monitor()函數。例如ActivityManagerService:

2150        Watchdog.getInstance().addMonitor(this);
2151        Watchdog.getInstance().addThread(mHandler);
其中addMonitor()將自身放到foreground thread的HandlerChecker的monitor隊列中,addThread()根據當前線程的Handler創建HandlerChecker並放到mHandlerCheckers隊列中。monitor()的實現一般很簡單,只是嘗試去獲得鎖再釋放鎖。如果有deadlock,就會卡住無法返回。
18767    public void monitor() {
18768        synchronized (this) { }
18769    }

回到SystemServer中,通過Watchdog的start()方法啓動Watchdog線程,Watchdog.run()被執行。


Watchdog的主體是一個循環。在每一次循環中,所有HandlerChecker的scheduleCheckLocked()函數被調用。其中主要是往被監視線程的Looper中放入HandlerChecker對象,HandlerChecker本身作爲Runnable,是線程的可執行體。因此當被監視線程把它從Looper中拿出來後,它的run()函數被調用。然後,Watchdog.run()等待最長30秒後,調用evaluateCheckerCompletionLocked()檢查各HandlerChecker結果狀態。一個HandlerChecker結果狀態有四種,COMPLETED(0),WAITING(1), WAITED_HALF(2)和OVERDUE(3)。分別代表目標Looper已處理monitor,延時小於30秒,延時大於30秒小於60秒,延時大於60秒。最終的總狀態是它們的最大值(也就是意味着最壞情況的那種情況)。如果總狀態是COMPLETED,WAITING或WAITED_HALF,則進入循環下一輪。注意如果是WAITED_HALF,也就是說等了大於30秒,需要調用AMS.dumpStackTraces()來dump棧。如果狀態爲WAITED_HALF,進入下一輪循環後,又會等待最長30秒。

假設有線程阻塞,對於阻塞線程的HandlerChecker,它的延遲超過60秒,導致總狀態爲OVERDUE。這裏會調用getBlockedCheckersLocked()和describeCheckersLocked()打印出是哪個Handler阻塞了。在Eventlog中打出信息後,把當前pid和相關進程pid放入待殺列表。然後和上面一樣調用AMS.dumpStackTraces()打印棧。之後等待2秒等stacktrace寫完。如有需要還會調用dumpKernelStackTraces()將kernel部分的stacktrace打出來。本質上是讀取/proc/[pid]/task下的線程的和相應的/proc/[tid]/stack文件。然後調用doSyncRq()通知kernel把阻塞線程信息和backtrace打印出來(通過寫/proc/sysrq-trigger)。之後會創建專門的線程來將信息寫入到Dropbox,該線程會執行AMS.addErrorToDropBox()。Dropbox是Android中的一套日誌記錄系統,作用是將系統出錯信息記錄下來並且保留一段時間,避免被覆蓋。當出現crash, wtf, lowmem或者Watchdog被觸發都會通過Dropbox來記錄。

425            Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
426                    public void run() {
427                        mActivity.addErrorToDropBox(
428                                "watchdog", null, "system_server", null, null,
429                                subject, null, stack, null);
430                    }
431                };
432            dropboxThread.start();
433            try {
434                dropboxThread.join(2000);  // wait up to 2 seconds for it to return.
435            } catch (InterruptedException ignored) {}
DropBoxManagerService在SystemServer的startOtherServices()中添加,信息默認存放路徑爲/data/system/dropbox。DropBoxManagerService實現了IDropBoxManagerService服務接口,Client通過DropBoxManager來訪問服務。在Watchdog中調用AMS.addErrorToDropBox()後,該函數會起工作線程(因爲涉及I/O),將前面得到的stack信息dump到Dropbox,並且獲取最近的logcat,最後通過DBMS的addText()接口進行存儲。 

接下來,如果設置了ActivityController就調用其systemNotResponding()接口(IActivityController是給測試開發時用的接口,用於監視AMS裏的行爲)。然後判斷Debugger是否連着和是否允許restart。如果沒有連着debugger且允許restart,就開始大開殺戒了。
467                Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
...
476                Slog.w(TAG, "*** GOODBYE!");
477                Process.killProcess(Process.myPid());
478                System.exit(10);
因爲Watchdog和SystemServer是同一進程,這裏Watchdog kill掉了自己,也就是kill了SystemServer。因它是主要進程,殺掉後會被init重啓。

這就是Watchdog的大體流程,回過頭看下AMS中dumpStackTraces()的一些細節。參數中的pids包含了本進程,阻塞線程以及phone進程等。NATIVE_STACKS_OF_INTEREST包含了下面三個關鍵進程。

67    public static final String[] NATIVE_STACKS_OF_INTEREST = new String[] {
68        "/system/bin/mediaserver",
69        "/system/bin/sdcard",
70        "/system/bin/surfaceflinger"
71    };
注意雖然它們不在SystemServer中,但因爲SystemServer中的Service會用Binder同步調用它們的方法。如果這些進程中阻塞,也可能導致SystemServer中發生阻塞。

dumpStackTraces()實現中先從dalvik.vm.stack-trace-file這個system property中取出trace路徑,默認爲/data/anr/traces.txt。接着它創建該文件(需要的話),設置屬性,最後調用同名函數dumpStackTraces()完成真正的dump工作。dump工作首先會用FileObserver(利用inotify機制)監視trace文件什麼時候寫完。它會創建一個獨立的線程ObserverThread並運行。然後對於前面加入到要dump線程列表中的進程,發送SIGQUIT信號。如果是虛擬機進程,ART中的SignalCatcher::HandleSigQuit()(在/art/runtime/signal_catcher.cc)會被調用來dump信息(DVM也是類似的)。對於前面的核心Service,調用Debug.dumpNativeBacktraceToFile()來輸出它們的backtrace。

總結下來,dumpStackTraces()大體流程如下:

可以看到,其中主要收集三類信息:一是關鍵進程(也就是上面收集的pid)的stacktrace;二是幾個關鍵native服務的stacktrace;三是CPU的使用率。其中一是通過往目標進程發SIGQUIT來獲取,因爲Java虛擬機的Runtime會捕獲SIGQUIT信號打印棧信息。二的原理是向後臺debuggerd這個daemon發起申請,讓其用ptrace打印目標進程的stacktrace然後用本地socket傳回來。部分實現位於android_os_Debug.cpp和/system/core/libcutils/debugger.c。發起申請和接收數據的代碼在以下函數:

131int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
132  int sock_fd = make_dump_request(DEBUGGER_ACTION_DUMP_BACKTRACE, tid, timeout_secs);
...
137  /* Write the data read from the socket to the fd. */
...
141  while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) {
142    if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
143      result = -1;
144      break;
145    }
146  }
...
最後使用ProcessCpuTracker類測量CPU使用率。它主要是通過讀系統的/proc/[pid]/stat文件。裏邊可以讀到進程所佔用的時間(user mode和kernel mode)。統計半秒後,排序後輸出最佔CPU的前幾名的stacktrace以便分析誰可能是罪魁禍首。

總得來說,Watchdog是一個軟件實現的檢測SystemServer進程內死鎖或掛起問題,並能夠從中恢復的機制。除了Watchdog外,Android中還有一些自檢容錯及出錯信息收集機制,前者有ANR,OOM Killer,init中的重啓機制等,後者有Dropbox,Debuggerd,Eventlog,Bugreport等。除此之外,其它的信息查看和調試命令就不計其數了,如dumpsys, dumpstate, showslab, procrank, procmem, latencytop, librank, schedtop, svc, am ,wm, atrace, proc, pm, service, getprop/setprop, logwrapper, input, getevent/sendevent等。充分利用這些工具能夠有效提高分析問題的效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章