萬字解析Hungtask原理及分析(上)
一、簡介
Linux系統(tǒng)在運(yùn)行過程中,可能發(fā)生各種各樣的卡死情況。有的表現(xiàn)為某個(gè)或某些CPU無法調(diào)度其他進(jìn)程或無法響應(yīng)中斷,如正在CPU上運(yùn)行的進(jìn)程禁止了搶占或禁止了本地中斷后,但其需要的資源一直無法獲得(如發(fā)生了死鎖等情況),而一直占據(jù)著CPU;有的表現(xiàn)為某些重要進(jìn)程一直不能運(yùn)行,雖然不至于使某個(gè)或某些CPU上無法調(diào)度其他進(jìn)程,但由于重要進(jìn)程運(yùn)行異常,系統(tǒng)已無法正常進(jìn)行業(yè)務(wù)處理,例如重要進(jìn)程長期處于uninterruptible sleep狀態(tài)(也就是常說的D狀態(tài))或android systemserver的watchdog超時(shí)等情況。
本文主要討論進(jìn)程長期處于D狀態(tài)或重要進(jìn)程異??ㄗ〉臋z測方法,即hungtask detect機(jī)制。而恢復(fù)機(jī)制,一般就是在檢測到異常時(shí),直接觸發(fā)整機(jī)重啟。
二、hungtask detect原理及流程
hungtask detect方法有多種,原理都很簡單。比如:
A、可以定時(shí)輪詢系統(tǒng)中的所有task,然后判斷處于D狀態(tài)的task的上下文切換次數(shù)是否和之前輪詢時(shí)的相等,如果相等則表明該task兩個(gè)輪詢間隔期間一直處于D狀態(tài),可以認(rèn)為該task有hang的情況。當(dāng)然,task hang住的情況,對(duì)于有些task來說沒有關(guān)系,可能其本身的邏輯就是如此,不會(huì)對(duì)系統(tǒng)中其它task產(chǎn)生影響;但對(duì)于一些我們認(rèn)為重要的進(jìn)程,如android中的systemserver、surfaceflinger等進(jìn)程,如果發(fā)生hang的情況,則一定會(huì)對(duì)用戶使用產(chǎn)生影響;還有task長時(shí)間處于io wait狀態(tài),同樣是一種異常狀態(tài),因?yàn)橐话銇碚fio應(yīng)盡快結(jié)束,而時(shí)間過長則表明io子系統(tǒng)很可能已經(jīng)異常。
B、如果只是判斷系統(tǒng)中的重要進(jìn)程是否卡住,也可以不檢查系統(tǒng)中所有task的狀態(tài),只需要關(guān)注重要進(jìn)程的運(yùn)行情況??梢宰屵@個(gè)重要進(jìn)程在規(guī)定時(shí)間內(nèi)模擬喂狗操作,若發(fā)現(xiàn)沒有及時(shí)喂狗,則認(rèn)為該重要進(jìn)程已經(jīng)卡住。
以下分別討論上面所述的兩種hungtask detect實(shí)現(xiàn)方式,所列代碼均為開源代碼,代碼鏈接見附錄參考文檔。
1、輪詢系統(tǒng)中的所有任務(wù)
這里對(duì)輪詢系統(tǒng)中的所有任務(wù)的hungtask detect方式進(jìn)行分析,代碼見參考文檔1,主要涉及代碼:
kernel\hung_task.c (linux系統(tǒng)默認(rèn)實(shí)現(xiàn))
drivers\soc\qcom\hung_task_enh.c (在默認(rèn)實(shí)現(xiàn)上進(jìn)行vendor hook)
KCONFIG
lib\Kconfig.debug (對(duì)應(yīng)hung_task.c )

drivers\soc\qcom\Kconfig (對(duì)應(yīng)hung_task_enh.c)

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ?


kernel\hung_task.c

A. 將panic_block(notifier_block結(jié)構(gòu)體)掛到panic_notifier_list通知鏈,當(dāng)系統(tǒng)發(fā)生panic時(shí),會(huì)通過該通知鏈通知注冊到該鏈的所有notifier_block,調(diào)用每個(gè)notifier_block的notifier_call成員函數(shù)。
對(duì)于這里的hungtask,就是在panic時(shí)調(diào)用hung_task_panic函數(shù)置did_panic為1,在hungtask檢測流程中發(fā)現(xiàn)did_panic為1,則直接退出。

B. hungtask_pm_notify_nb(notifier_block結(jié)構(gòu)體)掛到pm_chain_head通知鏈,當(dāng)系統(tǒng)發(fā)生pm狀態(tài)變化時(shí)調(diào)用hungtask_pm_notify,設(shè)置hung_detector_suspended變量。

C. 起內(nèi)核線程,運(yùn)行D狀態(tài)檢測函數(shù)watchdog(),下面分析。

A. 取sysctl_hung_task_timeout_secs和sysctl_hung_task_check_interval_secs最小值作為檢測時(shí)的interval,加上次檢測時(shí)間hung_last_checked,如達(dá)到或超過當(dāng)前時(shí)間jiffies則進(jìn)行hungtask check。
B. hungtask check函數(shù),下面詳細(xì)分析。
C. 進(jìn)入可中斷休眠,如有信號(hào)提前中斷喚起該線程,會(huì)在A處的時(shí)間判斷中確定是否進(jìn)行hungtask check。

A. 限制進(jìn)行hung check的task數(shù)量,本輪檢測的task數(shù)量達(dá)到該值后退出。
B. 如已經(jīng)運(yùn)行了HUNG_TASK_LOCK_BREAK時(shí)間,調(diào)用rcu_lock_break() 短暫退出rcu臨界區(qū)并調(diào)度出去,避免一次rcu grace period的時(shí)間過長,之后再調(diào)度回來時(shí)再次進(jìn)入rcu臨界區(qū)。由于調(diào)度出去再回來時(shí),正在檢測的task可能已經(jīng)釋放,所以在調(diào)度出去之前,需要使用get_task_struct增加task的task_struct結(jié)構(gòu)體的引用計(jì)數(shù),防止其被釋放,在通過pid_alive判斷task是否dead后,再調(diào)用put_task_struct減小引用計(jì)數(shù)。如果調(diào)度回來時(shí)發(fā)現(xiàn)task已經(jīng)dead,則退出本輪hung check。

C. 為符合GKI規(guī)范,此處通過vendor hook函數(shù),調(diào)用vendor實(shí)現(xiàn)的hook函數(shù),這里的實(shí)現(xiàn)是調(diào)用register函數(shù)注冊對(duì)應(yīng)hook函數(shù),qcom_before_check_tasks() 和qcom_check_tasks_done(),后面會(huì)有分析,主要就是判斷該task是否需要hungtask檢查,并獲得當(dāng)前iowait task的數(shù)量。
vendor hook函數(shù)注冊如下所示:
drivers\soc\qcom\hung_task_enh.c

D. 根據(jù)C處返回的need_check,如判斷需要進(jìn)行hungtask檢查,則調(diào)用check_hung_task(),后面會(huì)有分析。
E. 此處調(diào)用qcom_check_tasks_done,判斷在對(duì)所有task進(jìn)行hung_task_enh.max_iowait_timeout_cnt輪的檢測,如果連續(xù)地每輪都有大于等于hung_task_enh.max_iowait_task_cnt數(shù)量的task處于iowait狀態(tài),則直接觸發(fā)panic。
F. 之后的流程就是在本輪hungtask檢測結(jié)束后,跟蹤標(biāo)志狀態(tài)顯示task的鎖狀態(tài)及當(dāng)前各CPU上的棧。
接下來看下hook函數(shù)的具體內(nèi)容。
drivers\soc\qcom\hung_task_enh.c

A. 一個(gè)task根據(jù)其task_struct中的walt_task_struct的hung_detect_status成員判斷,如果在白名單(白名單模式)或不在黑名單(黑名單模式),則置need_check標(biāo)志,然后繼續(xù)判斷是否要增加iowait task數(shù)量的統(tǒng)計(jì)值。
B. 如果task處于iowait狀態(tài),且為D狀態(tài)、暫停狀態(tài)、跟蹤狀態(tài)之一,且到了檢查hungtask的時(shí)間,且為用戶空間進(jìn)程主線程,則增加iowait task數(shù)量的統(tǒng)計(jì)值。
接下來看check_hung_task()的具體內(nèi)容。

A. 如果task已凍結(jié)或?yàn)檎{(diào)用vfork的進(jìn)程(會(huì)處于D狀態(tài)直到等子進(jìn)程調(diào)用exit或exec)則跳過hungtask檢查。
B. task的自愿(nvcsw )和非自愿(nivcsw)上下文切換次數(shù)的和如果在檢測interval之間變動(dòng)過,則說明該task沒有hung住,即使task當(dāng)前為D狀態(tài)。直接返回,跳過該task。
C. 打印sysctl_hung_task_warnings次task block信息后就不再打印,也就是說,更多的hungtask信息有可能不會(huì)再被看到。打印task block信息時(shí),會(huì)置hung_task_show_lock和hung_task_show_all_bt標(biāo)志,在退出本輪所有task的hungtask檢查后,會(huì)根據(jù)這些標(biāo)志打印task的鎖情況以及各CPU的backtrace。之后就退出了本輪的所有task的hungtask檢查。
2.只關(guān)注重要進(jìn)程
這里對(duì)第二種hungtask detect實(shí)現(xiàn)方式進(jìn)行分析,只判斷系統(tǒng)中的重要進(jìn)程是否卡住,代碼見參考文檔2,主要涉及驅(qū)動(dòng)代碼:
drivers\misc\mediatek\monitor_hang\hang_detect.c
KCONFIG定義
drivers\misc\mediatek\monitor_hang\Kconfig (對(duì)應(yīng)hung_task.c )

代碼分析
drivers\misc\mediatek\monitor_hang\hang_detect.c

A. 注冊hang monitor的misc device,名稱為RT_Monitor,通過其write接口控制hang monitor的使能,通過ioctl設(shè)置(類似watchdog kick操作)hang_detect_counter(后面分析的hang_detect線程會(huì)定時(shí)遞減這個(gè)counter,也就是說,如果不重置,一定時(shí)間之后就會(huì)認(rèn)為重要任務(wù) hang住了)和hang monitor的使能(hd_detect_enabled)。

B. 啟動(dòng)hang_detect和hang_detect1線程。hang_detect線程為檢測線程,下面分析。hang_detect1用來在檢測到hang時(shí)dump系統(tǒng)狀態(tài)。

繼續(xù)看下hang_detect線程的工作。

A處啟動(dòng)循環(huán),當(dāng)hang detect使能,且白名單中的task均在系統(tǒng)中時(shí),每HD_INTER秒(默認(rèn)為30秒)會(huì)對(duì)hang_detect_counter減一,減一前會(huì)檢查hang_detect_counter,當(dāng)小于等于0時(shí),會(huì)dump系統(tǒng)狀態(tài)或觸發(fā)BUG死機(jī)。
當(dāng)系統(tǒng)持續(xù)hang住時(shí),hang_detect_counter會(huì)先減到0,這時(shí)Hang_first_done是false(表示hang后的第一次處理還沒完成),所以會(huì)運(yùn)行wake_up_dump()(hang_detect線程)喚醒hang_detect1線程,hang_detect1線程dump系統(tǒng)狀態(tài)之后,置dump_bt_done為1,表示已經(jīng)完成dump backtrace,再喚醒wake_up_dump()(hang_detect線程),之后E處設(shè)置Hang_first_done為true,表示hang后的第一次處理已經(jīng)完成,之后hang_detect_counter會(huì)減到-1。
若系統(tǒng)還持續(xù)hang住,會(huì)走到B處,此處判斷條件時(shí)的意思是,如果之前hang_detect1線程dump系統(tǒng)狀態(tài)沒有正常執(zhí)行完成,則這里會(huì)再啟動(dòng)hang_detect2;否則,還會(huì)再喚醒hang_detect1線程,再次dump系統(tǒng)狀態(tài),方便和之前的進(jìn)行對(duì)比。之后走到D處,調(diào)用BUG觸發(fā)死機(jī)重啟以復(fù)位系統(tǒng),否則關(guān)鍵進(jìn)程可能一直卡住而不能自動(dòng)恢復(fù)。
wake_up_dump()

hang_detect1線程

hang_detect2線程

篇幅有限 下文繼續(xù)講解
原文作者:內(nèi)核工匠
