紅帽Linux故障定位技術詳解與實例(1)

在線故障定位就是在故障發生時, 故障所處的操作系統環境仍然可以訪問,故障處理人員可通過console, ssh等方式登錄到操作系統上,在shell上執行各種操作命令或測試程序的方式對故障環境進行觀察,分析,測試,以定位出故障發生的原因。
AD:2014WOT全球軟件技術峰會北京站 課程視頻發布
?
紅帽Linux故障定位技術詳解與實例是本文要介紹的內容,主要是來了解并學習紅帽linux中故障定位技術的學習,故障定位技術分為在線故障定位和離線故障定位,一起來看詳解。
1、故障定位(Debugging)場景分類
為便于描述問題,將Linux上各種軟件故障定位的情形分成兩類
(1)在線故障故障定位
在線故障定位(online-debugging)就是在故障發生時, 故障所處的操作系統環境仍然可以訪問,故障處理人員可通過console, ssh等方式登錄到操作系統上,在shell上執行各種操作命令或測試程序的方式對故障環境進行觀察,分析,測試,以定位出故障發生的原因
(2)離線故障定位
離線故障定位(offline-debugging)就是在故障發生時,故障所處的操作系統環境已經無法正常訪問,但故障發生時系統的全部或部分狀態已經被系統本身所固有或事先設定的方式收集起來,故障處理人員可通過對收集到的故障定位狀態信息進行分析,定位出故障發生的原因
2、應用進程故障情形及處理
應用進程的故障一般不會影響操作系統運行環境的正常使用(如果應用代碼的bug導致了內核的crash或hang,則屬于內核存在漏洞),所以可采用在線故障定位的方法,靈活的進行分析. 應用代碼故障的情形有如下幾種:
(1)進程異常終止
很多用戶認為進程異常終止情況無從分析,但實際上進程異常終止情況都是有跡可尋的. 所有的進程異常終止行為,都是通過內核發信號給特定進程或進程組實現的. 可分成幾個類型進行描述:
- SIGKILL. SIGKILL最特殊,因為該信號不可被捕獲,同時SIGKILL不會導致被終止的進程產生core文件, 但如果真正的是由內核中發出的SIGKILL,則內核一定會在dmesg中記錄下信息. 另外在內核中使用SIGKILL的地方屈指可數,如oom_kill_process()中, 所以通過dmesg記錄并且分析內核中使用SIGKILL的代碼,并不難分析原因
- SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV. 這幾個信號在保留情況下會終止進程并會產生core文件, 用戶根據core中的stack trace信息,能直接定位出導致終止信號的代碼位置. 另外, SIGQUIT,SIGABRT一般是由用戶代碼自己使用的,好的代碼一般會記錄日志. SIGILL, SIGBUS, SIGFPE, SIGSEGV, 都是由內核中產生的,搜索內核源碼,不難列出內核中使用這幾個信號的地方, 如SIGILL 是非法指令,可能是浮點運算產生的代碼被corrupted或文本區域的物理內存corruption; SIGBUS多由MCE故障定位導致; SIGSEGV多由應用代碼的指針變量被corrupted導致. 對于應用的heap或stack的內存被corrupted, 可用valgrind工具對應用進行profile, 通常能直接發現導致corruption的代碼
- SIGINT, SIGPIPE, SIGALRM, SIGTERM. 這幾個信號在保留情況下終止進程但不會產生core文件. 對這幾個信號,建議用戶一定要定義一個handler,以記錄產生問題的上下文. 比較容易忽略的是SIGPIPE, 很多用戶程序在使用select()或poll()時只監聽read/write描述符,不監聽exception描述符,在對方TCP已經關閉的情況下,仍然向socket中寫入,導致SIGPIPE.
- 對于惡意的代嗎產生的進程終止行為,如合作的一些進程中,A向B發SIGKILL, 而沒做日志記錄,或者B直接判斷某條件而調用exit(), 也沒有做日志記錄.在應用代碼量很大的情況下,通過分析代碼故障定位這種情形也許很難. SystemTap提供了解決這個問題的一個比較好的方法,就是寫用戶層的probes, 追蹤進程對signal(), exit() 等系統調用的使用
(2)進程阻塞,應用無法正常推進
這種情況,對于單個被阻塞的進程而言,屬于正常狀態, 但對于包含多個進程的應用整體而言,屬于異常. 應用無法推進,說明其中某一個進程推進的因素出現了問題,導致其他依賴于它的進程也要等待. 分析這種情形需要分析清楚進程或事件之間的依賴關系,及數據的處理流. 首先要用gdb -p 的back trace功能查出各進程阻塞的執行路徑, 以確定每個進程所處在的狀態機的位置.
通常而言,如果只考慮各個進程的狀態,則進程之間可能形成了一種互相依賴的環形關系,如(P1發請求=>P2處理=>P2發反應=>P1再請求=>P2處理=>P2再發反應), 但應用對workload, 一般是按一個個的transaction 或 session的方式進行處理的,每個transaction都有起點和終點, 我們需要用strace, tcpdump 等工具以及應用的執行日志進行觀察,分析出當前正被處理的transaction所被阻滯的位置,從而找出全部狀態機被阻塞的原因. 導致這種狀態機停止運轉的原因有多個:如和應用通信的遠端出現了問題,后端數據庫/目錄等出現了問題,應用的某個進程或線程處于非正常的blocking位置或直接終止,不再正常工作.
(3)用戶進程形成死鎖
用戶進程形成死鎖,如果沒有內存上的故障定位,則完全是應用自身的邏輯問題. 死鎖的進程或線程之間由于鎖的互相占有形成了環路。 這種情況發生時,用gdb -p 的back trace的功能能直接確定死鎖的進程全部阻塞在futex()等和鎖相關的系統調用上, 這些調用futex()的路徑可能是mutex, semaphore, conditional variable 等鎖函數. 通過分析call trace 的代碼,能直接確定各進程在執行到該位置時,可能已經持有的全部鎖, 根據這個修改程序的代碼,消除死鎖環路,就可解決問題.
注意,內存故障也可導致假的死鎖的,如物理內存故障可直接導致鎖變量的值為-1, 所以使用該鎖的進程都會阻塞. 如果是代碼的bug導致的內存corruption,可用valgrind工具檢查程序來發現. 但如果是物理內存的故障定位導致的corruption, 則需要硬件的支持,對于高端的PC, 如MCE功能的機器,當物理內存故障定位時能直接產生異常或報告, 但對于低端PC服務器,除了運行memtest工具進行檢測外,沒有其他方法
(4)進程長期處于 'D' (UnInterruptible)狀態沒法退出
這種多是由內核中的故障引起的. 內核在很多執行路徑中會將進程至于'D'的狀態,以確保關鍵的執行路徑不被外部的信號中斷, 導致不必要的內核中數據結構狀態的不一致性. 但一般而言,進程處于 'D' 狀態的時間不會太久, 因為狀態結束的條件(如timer觸發,
IO操作完成等)很快會將進程喚醒. 當進程長期處于 'D',關鍵是要找出其阻塞的代碼位置, 用 sysrq 的t鍵功能可直接打印出系統中全部睡眠進程的內核執行堆棧,如 echo 't' > /proc/sysrq-trigger, 其中包括出現 'D'狀態的進程的內核態堆棧. 找出代碼位置后,一般可直接分析出 'D' 狀態不能退出的原因, 如IO read操作因硬件或nfs故障而不能完成.
有可能導致 'D' 狀態的原因比較復雜,如‘D’的退出依賴于某變量的值,而該變量的值因某種原因被永久corrupted掉了.