文章都為原創,轉載請注明出處,未經允許而盜用者追究法律責任。
很久之前寫的了,留着有點浪費,共享之(文章沒有完全寫完)。
編寫者:李文棟
第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的類圖。
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的流程圖