溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何理解Linux故障定位技術(shù)

發(fā)布時(shí)間:2021-11-01 09:59:23 來(lái)源:億速云 閱讀:122 作者:柒染 欄目:系統(tǒng)運(yùn)維

本篇文章為大家展示了如何理解Linux故障定位技術(shù),內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。

主要是來(lái)了解并學(xué)習(xí)linux故障定位技術(shù)的學(xué)習(xí),故障定位技術(shù)分為在線故障定位和離線故障定位。

1、故障定位(Debugging)場(chǎng)景分類

為便于描述問(wèn)題,將Linux上各種軟件故障定位的情形分成兩類

(1)在線故障故障定位

在線故障定位(online-debugging)就是在故障發(fā)生時(shí), 故障所處的操作系統(tǒng)環(huán)境仍然可以訪問(wèn),故障處理人員可通過(guò)console, ssh等方式登錄到操作系統(tǒng)上,在shell上執(zhí)行各種操作命令或測(cè)試程序的方式對(duì)故障環(huán)境進(jìn)行觀察,分析,測(cè)試,以定位出故障發(fā)生的原因

(2)離線故障定位

離線故障定位(offline-debugging)就是在故障發(fā)生時(shí),故障所處的操作系統(tǒng)環(huán)境已經(jīng)無(wú)法正常訪問(wèn),但故障發(fā)生時(shí)系統(tǒng)的全部或部分狀態(tài)已經(jīng)被系統(tǒng)本身所固有或事先設(shè)定的方式收集起來(lái),故障處理人員可通過(guò)對(duì)收集到的故障定位狀態(tài)信息進(jìn)行分析,定位出故障發(fā)生的原因

2、應(yīng)用進(jìn)程故障情形及處理

應(yīng)用進(jìn)程的故障一般不會(huì)影響操作系統(tǒng)運(yùn)行環(huán)境的正常使用(如果應(yīng)用代碼的bug導(dǎo)致了內(nèi)核的crash或hang,則屬于內(nèi)核存在漏洞),所以可采用在線故障定位的方法,靈活的進(jìn)行分析. 應(yīng)用代碼故障的情形有如下幾種:

(1)進(jìn)程異常終止

很多用戶認(rèn)為進(jìn)程異常終止情況無(wú)從分析,但實(shí)際上進(jìn)程異常終止情況都是有跡可尋的. 所有的進(jìn)程異常終止行為,都是通過(guò)內(nèi)核發(fā)信號(hào)給特定進(jìn)程或進(jìn)程組實(shí)現(xiàn)的. 可分成幾個(gè)類型進(jìn)行描述:

- SIGKILL. SIGKILL最特殊,因?yàn)樵撔盘?hào)不可被捕獲,同時(shí)SIGKILL不會(huì)導(dǎo)致被終止的進(jìn)程產(chǎn)生core文件, 但如果真正的是由內(nèi)核中發(fā)出的SIGKILL,則內(nèi)核一定會(huì)在dmesg中記錄下信息. 另外在內(nèi)核中使用SIGKILL的地方***,如oom_kill_process()中, 所以通過(guò)dmesg記錄并且分析內(nèi)核中使用SIGKILL的代碼,并不難分析原因

- SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV. 這幾個(gè)信號(hào)在保留情況下會(huì)終止進(jìn)程并會(huì)產(chǎn)生core文件, 用戶根據(jù)core中的stack trace信息,能直接定位出導(dǎo)致終止信號(hào)的代碼位置. 另外, SIGQUIT,SIGABRT一般是由用戶代碼自己使用的,好的代碼一般會(huì)記錄日志. SIGILL, SIGBUS, SIGFPE, SIGSEGV, 都是由內(nèi)核中產(chǎn)生的,搜索內(nèi)核源碼,不難列出內(nèi)核中使用這幾個(gè)信號(hào)的地方, 如SIGILL 是非法指令,可能是浮點(diǎn)運(yùn)算產(chǎn)生的代碼被corrupted或文本區(qū)域的物理內(nèi)存corruption; SIGBUS多由MCE故障定位導(dǎo)致; SIGSEGV多由應(yīng)用代碼的指針變量被corrupted導(dǎo)致. 對(duì)于應(yīng)用的heap或stack的內(nèi)存被corrupted, 可用valgrind工具對(duì)應(yīng)用進(jìn)行profile, 通常能直接發(fā)現(xiàn)導(dǎo)致corruption的代碼

- SIGINT, SIGPIPE, SIGALRM, SIGTERM. 這幾個(gè)信號(hào)在保留情況下終止進(jìn)程但不會(huì)產(chǎn)生core文件. 對(duì)這幾個(gè)信號(hào),建議用戶一定要定義一個(gè)handler,以記錄產(chǎn)生問(wèn)題的上下文. 比較容易忽略的是SIGPIPE, 很多用戶程序在使用select()或poll()時(shí)只監(jiān)聽(tīng)read/write描述符,不監(jiān)聽(tīng)exception描述符,在對(duì)方TCP已經(jīng)關(guān)閉的情況下,仍然向socket中寫(xiě)入,導(dǎo)致SIGPIPE.

- 對(duì)于惡意的代嗎產(chǎn)生的進(jìn)程終止行為,如合作的一些進(jìn)程中,A向B發(fā)SIGKILL, 而沒(méi)做日志記錄,或者B直接判斷某條件而調(diào)用exit(), 也沒(méi)有做日志記錄.在應(yīng)用代碼量很大的情況下,通過(guò)分析代碼故障定位這種情形也許很難. SystemTap提供了解決這個(gè)問(wèn)題的一個(gè)比較好的方法,就是寫(xiě)用戶層的probes, 追蹤進(jìn)程對(duì)signal(), exit() 等系統(tǒng)調(diào)用的使用

(2)進(jìn)程阻塞,應(yīng)用無(wú)法正常推進(jìn)

這種情況,對(duì)于單個(gè)被阻塞的進(jìn)程而言,屬于正常狀態(tài), 但對(duì)于包含多個(gè)進(jìn)程的應(yīng)用整體而言,屬于異常. 應(yīng)用無(wú)法推進(jìn),說(shuō)明其中某一個(gè)進(jìn)程推進(jìn)的因素出現(xiàn)了問(wèn)題,導(dǎo)致其他依賴于它的進(jìn)程也要等待. 分析這種情形需要分析清楚進(jìn)程或事件之間的依賴關(guān)系,及數(shù)據(jù)的處理流. 首先要用gdb -p 的back trace功能查出各進(jìn)程阻塞的執(zhí)行路徑, 以確定每個(gè)進(jìn)程所處在的狀態(tài)機(jī)的位置.

通常而言,如果只考慮各個(gè)進(jìn)程的狀態(tài),則進(jìn)程之間可能形成了一種互相依賴的環(huán)形關(guān)系,如(P1發(fā)請(qǐng)求=>P2處理=>P2發(fā)反應(yīng)=>P1再請(qǐng)求=>P2處理=>P2再發(fā)反應(yīng)), 但應(yīng)用對(duì)workload, 一般是按一個(gè)個(gè)的transaction 或 session的方式進(jìn)行處理的,每個(gè)transaction都有起點(diǎn)和終點(diǎn), 我們需要用strace, tcpdump 等工具以及應(yīng)用的執(zhí)行日志進(jìn)行觀察,分析出當(dāng)前正被處理的transaction所被阻滯的位置,從而找出全部狀態(tài)機(jī)被阻塞的原因. 導(dǎo)致這種狀態(tài)機(jī)停止運(yùn)轉(zhuǎn)的原因有多個(gè):如和應(yīng)用通信的遠(yuǎn)端出現(xiàn)了問(wèn)題,后端數(shù)據(jù)庫(kù)/目錄等出現(xiàn)了問(wèn)題,應(yīng)用的某個(gè)進(jìn)程或線程處于非正常的blocking位置或直接終止,不再正常工作.

(3)用戶進(jìn)程形成死鎖

用戶進(jìn)程形成死鎖,如果沒(méi)有內(nèi)存上的故障定位,則完全是應(yīng)用自身的邏輯問(wèn)題. 死鎖的進(jìn)程或線程之間由于鎖的互相占有形成了環(huán)路。 這種情況發(fā)生時(shí),用gdb -p 的back trace的功能能直接確定死鎖的進(jìn)程全部阻塞在futex()等和鎖相關(guān)的系統(tǒng)調(diào)用上, 這些調(diào)用futex()的路徑可能是mutex, semaphore, conditional variable 等鎖函數(shù). 通過(guò)分析call trace 的代碼,能直接確定各進(jìn)程在執(zhí)行到該位置時(shí),可能已經(jīng)持有的全部鎖, 根據(jù)這個(gè)修改程序的代碼,消除死鎖環(huán)路,就可解決問(wèn)題.

注意,內(nèi)存故障也可導(dǎo)致假的死鎖的,如物理內(nèi)存故障可直接導(dǎo)致鎖變量的值為-1, 所以使用該鎖的進(jìn)程都會(huì)阻塞. 如果是代碼的bug導(dǎo)致的內(nèi)存corruption,可用valgrind工具檢查程序來(lái)發(fā)現(xiàn). 但如果是物理內(nèi)存的故障定位導(dǎo)致的corruption, 則需要硬件的支持,對(duì)于高端的PC, 如MCE功能的機(jī)器,當(dāng)物理內(nèi)存故障定位時(shí)能直接產(chǎn)生異?;驁?bào)告, 但對(duì)于低端PC服務(wù)器,除了運(yùn)行memtest工具進(jìn)行檢測(cè)外,沒(méi)有其他方法

(4)進(jìn)程長(zhǎng)期處于 'D' (UnInterruptible)狀態(tài)沒(méi)法退出

這種多是由內(nèi)核中的故障引起的. 內(nèi)核在很多執(zhí)行路徑中會(huì)將進(jìn)程至于'D'的狀態(tài),以確保關(guān)鍵的執(zhí)行路徑不被外部的信號(hào)中斷, 導(dǎo)致不必要的內(nèi)核中數(shù)據(jù)結(jié)構(gòu)狀態(tài)的不一致性. 但一般而言,進(jìn)程處于 'D' 狀態(tài)的時(shí)間不會(huì)太久, 因?yàn)闋顟B(tài)結(jié)束的條件(如timer觸發(fā),

IO操作完成等)很快會(huì)將進(jìn)程喚醒. 當(dāng)進(jìn)程長(zhǎng)期處于 'D',關(guān)鍵是要找出其阻塞的代碼位置, 用 sysrq 的t鍵功能可直接打印出系統(tǒng)中全部睡眠進(jìn)程的內(nèi)核執(zhí)行堆棧,如 echo 't' > /proc/sysrq-trigger, 其中包括出現(xiàn) 'D'狀態(tài)的進(jìn)程的內(nèi)核態(tài)堆棧. 找出代碼位置后,一般可直接分析出 'D' 狀態(tài)不能退出的原因, 如IO read操作因硬件或nfs故障而不能完成.

有可能導(dǎo)致 'D' 狀態(tài)的原因比較復(fù)雜,如‘D’的退出依賴于某變量的值,而該變量的值因某種原因被***corrupted掉了.

3、內(nèi)核故障情形及處理

(1)內(nèi)核panic

panic是內(nèi)核最直接的故障定位報(bào)告,發(fā)生panic時(shí),內(nèi)核已經(jīng)認(rèn)為故障定位已經(jīng)導(dǎo)致操作系統(tǒng)不再具備正常運(yùn)行的條件了. 當(dāng)發(fā)生panic時(shí),Linux會(huì)將所有CPU的中斷和進(jìn)程調(diào)度功能都關(guān)掉,所以這時(shí)系統(tǒng)是沒(méi)有任何反應(yīng)的,如果用戶啟動(dòng)的是圖形界面,則在屏幕上也看不到任何關(guān)于panic的信息.

我們通常遇到的,機(jī)器沒(méi)反應(yīng),ping不通的情況,絕大部分都是panic. Panic發(fā)生時(shí),內(nèi)核直接在console上打印導(dǎo)致panic的代碼位置的調(diào)用堆棧, 傳統(tǒng)的用戶用串口連接到機(jī)器上來(lái)收集console上的打印信息, 但串口這種方式,顯然用起來(lái)不方便, 現(xiàn)在的Linux, 如RHEL5,RHEL6, 都采用kdump的方法來(lái)收集panic時(shí)的信息. 在配置好kdump的情況下,panic時(shí)系統(tǒng)會(huì)用kexec加載并切換到一個(gè)新的內(nèi)核上(放置在預(yù)先分配的內(nèi)存位置),并用磁盤(pán)或網(wǎng)絡(luò)等將系統(tǒng)的全部或部分內(nèi)存數(shù)據(jù)保存起來(lái).

用kdump收集到panic的數(shù)據(jù)后,用戶用crash工具就能直接查看導(dǎo)致panic的代碼路徑.

panic一般是很直觀的,panic的堆棧信息能直接反映出導(dǎo)致bug的原因,如MCE故障,NMI故障, 數(shù)據(jù)結(jié)構(gòu)分配失敗等. 但有時(shí)panic是因?yàn)閮?nèi)核主動(dòng)發(fā)現(xiàn)了關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)不一致性,這種不一致性是什么時(shí)候,什么代碼導(dǎo)致的,并不清楚,可能還需要多次測(cè)試用SystemTap這樣的工具進(jìn)行捕捉

(2)多處理機(jī)環(huán)境內(nèi)核執(zhí)行路徑產(chǎn)生的死鎖

內(nèi)核死鎖和panic不一樣,產(chǎn)生死鎖時(shí),內(nèi)核并不主動(dòng)的使自己處于掛起狀態(tài). 但內(nèi)核死鎖發(fā)生時(shí),兩個(gè)以上的CPU的執(zhí)行路徑在內(nèi)核態(tài)不能推進(jìn)了,處于互相阻塞狀態(tài), 而且是100%的占用CPU(用的spin-lock),直接或間接的導(dǎo)致全部CPU上的進(jìn)程無(wú)法調(diào)度. 內(nèi)核死鎖又分兩種情況:

- 涉及到中斷上下文的死鎖. 這種情況的死鎖,最少一個(gè)CPU上的中斷被屏蔽了.系統(tǒng)可能沒(méi)法響應(yīng)ping請(qǐng)求. 由于有一個(gè)CPU已經(jīng)沒(méi)法響應(yīng)中斷,其上的local APIC定時(shí)中斷沒(méi)法工作,可以用NMI Watchdog的方法來(lái)檢測(cè)出來(lái)(檢查local APIC handler維護(hù)的計(jì)數(shù)器變量),NMI Watchdog可以在其處理程序中調(diào)用panic(), 用戶就可以用kdump收集內(nèi)存信息,從而分析各死鎖CPU上的調(diào)用堆棧,查處導(dǎo)致死鎖的邏輯原因.

- 不涉及中斷上下文的死鎖. 這種情況的死鎖,各CPU上的中斷都是正常的,系統(tǒng)能對(duì)ping請(qǐng)求作出反應(yīng),這時(shí)NMI Watchdog無(wú)法被觸發(fā). 在 2.6.16之前的內(nèi)核中,并沒(méi)有一種很好的方法來(lái)處理這種情形. 在RHEL5, RHEL6 內(nèi)核中, 每個(gè)CPU上提供了一個(gè)watchdog內(nèi)核線程,在死鎖出現(xiàn)的情況下,死鎖CPU上的watchdog內(nèi)核線程沒(méi)法被調(diào)度(即使它是***優(yōu)先級(jí)的實(shí)時(shí)進(jìn)程),它就沒(méi)法update相應(yīng)的counter變量,各CPU的NMI Watchdog中斷會(huì)周期性的檢查其CPU對(duì)應(yīng)的counter, 發(fā)現(xiàn)沒(méi)有updated, 會(huì)調(diào)用panic(),用戶就可用kdump收集內(nèi)存信息,分析各死鎖CPU上的調(diào)用堆棧,查處導(dǎo)致死鎖的邏輯原因.

(3)內(nèi)核的oops或warning

oops 和warning和panic類似的地方是,他們都是因內(nèi)核發(fā)現(xiàn)了不一致而主動(dòng)報(bào)告的異常. 但oops和warning導(dǎo)致的問(wèn)題嚴(yán)重程度要比panic輕很多,以致于內(nèi)核處理該問(wèn)題時(shí)不需要使系統(tǒng)掛起. 產(chǎn)生oops和warning, 內(nèi)核通常已經(jīng)在dmesg中記錄了相當(dāng)?shù)男畔?,特別是oops, 至少會(huì)打印出現(xiàn)故障的地方的call trace. Oops也可轉(zhuǎn)換成panic/kdump來(lái)進(jìn)行offline-debugging, 只要將/proc/sys/kernel下的panic_on_oops變量設(shè)置為1就行了.

產(chǎn)生oops和warning的直接原因有很多,如內(nèi)核中的segment fault, 或內(nèi)核發(fā)現(xiàn)的某數(shù)據(jù)結(jié)構(gòu)的counter值不對(duì), 而segment fault 和counter值的變化還有更深層次的原因,通常并不能從內(nèi)核dmesg的信息中看出來(lái),解決這種問(wèn)題的是要用SystemTap進(jìn)行probe, 如發(fā)現(xiàn)某counter的值不對(duì),就用SystemTap做一個(gè)probe來(lái)記錄所有代碼對(duì)該counter的訪問(wèn), 然后再進(jìn)行分析.

定位oops和warning會(huì)比定位應(yīng)用程序的內(nèi)存訪問(wèn)故障定位困難很多,因?yàn)樵趦?nèi)核并不能象用valgrind去trace應(yīng)用程序一樣跟蹤數(shù)據(jù)結(jié)構(gòu)的分配和使用情況.

2、其他(硬件相關(guān))故障

機(jī)器自動(dòng)重啟是一種常見(jiàn)的故障情形,一般是由硬件如物理內(nèi)存故障引起的,軟件的故障只會(huì)導(dǎo)致死鎖或panic, 內(nèi)核中幾乎沒(méi)有代碼在發(fā)現(xiàn)問(wèn)題的情況下去reboot機(jī)器. 在/proc/sys/kernel目錄下有個(gè)參數(shù)“panic”, 其值如果設(shè)置為非0,則在panic發(fā)生若干秒后,內(nèi)核會(huì)重啟機(jī)器. 現(xiàn)在高端的PC服務(wù)器,都在努力用軟件來(lái)處理物理內(nèi)存故障,如MCA的 “HWPoison”方法會(huì)將故障的物理頁(yè)隔離起來(lái),Kill掉故障頁(yè)所在的進(jìn)程就可以了,RHEL6現(xiàn)在已經(jīng)支持 “HWPoison”. 那些不具備MCA能力的機(jī)器,物理內(nèi)存故障時(shí),不會(huì)產(chǎn)生MCE異常,直接由硬件機(jī)制reboot機(jī)器

4、RHEL6 上的Debugging技術(shù)介紹

(1)Kdump故障定位收集和crash分析

kdump就是用來(lái)在內(nèi)核panic的情況下收集系統(tǒng)內(nèi)存信息的, 用戶也可在online情況下用sysrq的'c'鍵觸發(fā). Kdump 采用沒(méi)有污染的內(nèi)核來(lái)執(zhí)行dump工作,所以其比以前的diskdump, lkcd方法更可靠. 使用kdump,用戶可選擇將數(shù)據(jù)dump到本地盤(pán)或網(wǎng)絡(luò)上,也可通過(guò)定義makedumpfile的參數(shù)過(guò)濾要收集的內(nèi)存信息,已減少kdump所需要的停機(jī)時(shí)間

Crash就是對(duì)kdump的信息進(jìn)行分析的工具.其實(shí)際就是gdb的一個(gè)wrapper. 使用crash時(shí),***安裝kernel-debuginfo包,這樣能解析kdump收集的內(nèi)核數(shù)據(jù)的符號(hào)信息. 用crash來(lái)定位問(wèn)題的能力,完全取決于用戶對(duì)內(nèi)核代碼的理解和分析能力

參考 “#>man kdump.conf”, “#>man crash”, “#>man makedumpfile”學(xué)習(xí)怎樣使用kdump和crash. 訪問(wèn) http://ftp.redhat.com可下載debuginfo文件

(2)用systemTap定位bug

systemtap 屬于probe類的定位工具,它能對(duì)內(nèi)核或用戶代碼的指定位置進(jìn)行probe, 當(dāng)執(zhí)行到指定位置或訪問(wèn)指定位置的數(shù)據(jù)時(shí),用戶定義的probe函數(shù)自動(dòng)執(zhí)行,可打印出該位置的調(diào)用堆棧,參數(shù)值,變量值等信息. systemtap選擇進(jìn)行probe的位置很靈活,這是systemtap的強(qiáng)大功能所在. Systemtap的probe點(diǎn)可包括如下幾個(gè)方面:

- 內(nèi)核中全部系統(tǒng)調(diào)用,內(nèi)核及模塊中全部函數(shù)的入口或出口點(diǎn)

- 自定義的定時(shí)器probe點(diǎn)

- 內(nèi)核中任意指定的代碼或數(shù)據(jù)訪問(wèn)位置

- 特定用戶進(jìn)程中任意制定的代碼或數(shù)據(jù)訪問(wèn)位置

- 各個(gè)功能子系統(tǒng)預(yù)先設(shè)置的若干probe點(diǎn),如tcp,udp,nfs,signal各子系統(tǒng)都預(yù)先設(shè)置了很多探測(cè)點(diǎn)

systemTap的腳本用stap腳本語(yǔ)言來(lái)編寫(xiě),腳本代碼中調(diào)用stap提供的API進(jìn)行統(tǒng)計(jì),打印數(shù)據(jù)等工作,關(guān)于stap語(yǔ)言提供的API函數(shù),參考 “#> man stapfuncs”. 關(guān)于systemTap的功能和使用可參考 “#> man stap”, “#> man stapprobes”

(3)ftrace

ftrace 是linux內(nèi)核中利用tracepoints基礎(chǔ)設(shè)施實(shí)現(xiàn)的事件追蹤機(jī)制,它的作用在于能比較清楚的給出在一定時(shí)間內(nèi)系統(tǒng)或進(jìn)程所執(zhí)行的活動(dòng),如函數(shù)調(diào)用路徑,進(jìn)程切換流等. Ftrace可用于觀察系統(tǒng)各部分的latency,以便進(jìn)行實(shí)時(shí)應(yīng)用的優(yōu)化; 它也可以通過(guò)記錄一段時(shí)間內(nèi)的內(nèi)核活動(dòng)來(lái)幫助故障定位. 如用以下方法可trace某個(gè)進(jìn)程在一端時(shí)間的函數(shù)調(diào)用情況

#> echo “function” > /sys/kernel/debug/tracing/current_tracer  #> echo “xxx” > /sys/kernel/debug/tracing/set_ftrace_pid  #> echo 1 > /sys/kernel/debug/tracing/tracing_enabled

除tracing函數(shù)調(diào)用外,ftrace還可tracing系統(tǒng)的進(jìn)程切換,喚醒,塊設(shè)備訪問(wèn),內(nèi)核數(shù)據(jù)結(jié)構(gòu)分配等活動(dòng). 注意,tracing和profile是不同的,tracing記錄的是一段時(shí)間內(nèi)的全部活動(dòng),而不是統(tǒng)計(jì)信息,用戶可以通過(guò)/sys/kernel/debug/tracing下的buffer_size_kb設(shè)置緩沖區(qū)的大小, 以記錄更長(zhǎng)時(shí)間的數(shù)據(jù).

關(guān)于ftrace的具體使用可參考內(nèi)核源碼Documenation/trace下的內(nèi)容

(4)oprofile 和 perf

oprofile和perf都是對(duì)系統(tǒng)進(jìn)行profile(抽樣,統(tǒng)計(jì))的工具,它們主要用來(lái)解決系統(tǒng)和應(yīng)用的性能問(wèn)題. perf功能更強(qiáng)大,更全面,同時(shí)perf的用戶空間工具和內(nèi)核源碼一起維護(hù)和發(fā)布,讓用戶能及時(shí)的享受perf內(nèi)核新增加的特征. Perf 是在RHEL6中才有,RHEL5中沒(méi)有Perf. Oprofile和perf 都使用現(xiàn)代CPU中具有的硬件計(jì)數(shù)器進(jìn)行統(tǒng)計(jì)工作,但perf還可以使用內(nèi)核中定義的 “software counter”及 “trace points”, 所以能做更多的工作. Oprofile的抽樣工作利用 CPU的NMI中斷來(lái)進(jìn)行,而perf既可以利用NMI中斷也可利用硬件計(jì)數(shù)器提供的周期性中斷. 用戶能很容易用perf來(lái)oprofile一個(gè)進(jìn)程或系統(tǒng)的執(zhí)行時(shí)間分布,如

#> perf top -f 1000 -p

還可以利用系統(tǒng)定義的 “software counter”和各子系統(tǒng)的 “trace points” 對(duì)子系統(tǒng)進(jìn)行分析, 如

#>perf stat -a -e kmem:mm_page_alloc -e kmem:mm_page_free_direct -e kmem:mm_pagevec_free sleep 6

能統(tǒng)計(jì)6秒內(nèi)kmem子系統(tǒng)的活動(dòng) (這一點(diǎn)實(shí)際是利用ftrace提供的tracepoints來(lái)實(shí)現(xiàn))

我認(rèn)為有了perf, 用戶就沒(méi)必要使用oprofile了

5、用kdump工具內(nèi)核故障定位實(shí)例

A) 部署Kdump

部署 kdump 收集故障信息的步驟如下:

(1)設(shè)置好相關(guān)的內(nèi)核啟動(dòng)參數(shù)

在 /boot/grub/menu.lst 中加入如下內(nèi)容

crashkernel=128M@16M nmi_watchdog=1

其中crashkernel參數(shù)是用來(lái)為kdump的內(nèi)核預(yù)留內(nèi)存的; nmi_watchdog=1 是用來(lái)激活NMI中斷的, 我們?cè)谖创_定故障是否關(guān)閉了中斷的情況下, 需要部署NMI watchdog才能確保觸發(fā)panic. 重啟系統(tǒng)確保設(shè)置生效

(2)設(shè)置好相關(guān)的sysctl內(nèi)核參數(shù)

在/etc/sysctl.conf 中***加入一行

kernel.softlookup_panic = 1

該設(shè)置確保softlock發(fā)生時(shí)會(huì)調(diào)用panic, 從而觸發(fā)kdump行為執(zhí)行 #>sysctl -p 確保設(shè)置生效

(3)配置 /etc/kdump.conf

在 /etc/kdump.conf 中加入如下幾行內(nèi)容

ext3 /dev/sdb1  core-collector makedumpfile -c –message-level 7 -d 31 -i /mnt/vmcoreinfo  path /var/crash  default reboot

其中 /dev/sdb1 是用于放置dumpfile 的文件系統(tǒng), dumpfile 文件放置在/var/crash下, 要事先在/dev/sdb1分區(qū)下創(chuàng)建/var/crash 目錄. “-d 31”指定對(duì)dump內(nèi)容的過(guò)濾級(jí)別,這參數(shù)對(duì)于dump分區(qū)放不下全部?jī)?nèi)存內(nèi)容或用戶不想讓dumping中斷業(yè)務(wù)太長(zhǎng)時(shí)間時(shí)很重要. vmcoreinfo 文件放置在 /dev/sdb1 分區(qū)的 / 目錄下, 需要使用如下命令產(chǎn)生:

#>makedumpfile -g //vmcoreinfo -x /usr/lib/debug/lib/modules/2.6.18-128.el5.x86_64/vmlinux

“vmlinux” 文件是由kernel-debuginfo 包提供的,在運(yùn)行makedumpfile 之前需要安裝相應(yīng)內(nèi)核的 kernel-debuginfo 和 kernel-debuginfo-common 兩個(gè)包,該兩個(gè)包需從 http://ftp.redhat.com 下載. “default reboot” 用來(lái)告訴kdump, 收集完dump信息后重啟系統(tǒng)

(4)激活kdump

運(yùn)行 #>service kdump start 命令,你會(huì)看到,在成功完成的情況下會(huì)在/boot/目錄下生成一個(gè)initrd-2.6.18-128.el5.x86_64kdump.img 文件,該文件就是kdump加載的內(nèi)核的 initrd文件,收集dump信息的工作就是在該initrd的啟動(dòng)環(huán)境下進(jìn)行的. 查看/etc/init.d/kdump腳本的代碼,你可看到其中會(huì)調(diào)用mkdumprd命令創(chuàng)建用于dump的initrd文件

1、測(cè)試Kdump部署的有效性

為了測(cè)試kdump部署的有效性,本人寫(xiě)了如下一個(gè)內(nèi)核模塊,通過(guò)insmod 加載該內(nèi)核模塊, 就能產(chǎn)生一個(gè)內(nèi)核線程,在10秒左右后,占據(jù)100%的CPU,在20秒左右后觸發(fā)kdump. 系統(tǒng)重啟后,檢查/oracle分區(qū)/var/crash 目錄下的內(nèi)容,就能確認(rèn)vmcore文件是否生成.

Zqfthread.c #include   #include   #include   #include   #include   #include    MODULE_AUTHOR("frzhang@redhat.com");   MODULE_DESCRIPTION("A module to test ....");   MODULE_LICENSE("GPL");    static struct task_struct *zqf_thread;   static int zqfd_thread(void *data);    static int zqfd_thread(void *data)   {   int i=0;    while (!kthread_should_stop()) {   i++;   if ( i < 10 ) {   msleep_interruptible(1000);   printk("%d seconds\n", i);   }   if ( i == 1000 ) // Running in the kernel   i = 11 ;   }   return 0;   }   static int __init zqfinit(void)   {   struct task_struct *p;    p = kthread_create(zqfd_thread, NULL,"%s","zqfd");    if ( p ) {   zqf_thread = p;   wake_up_process(zqf_thread); // actually start it up   return(0);   }    return(-1);   }    static void __exit zqffini(void)   {   kthread_stop(zqf_thread);   }    module_init(zqfinit);   module_exit(zqffini)   Makefile obj-m += zqfthread.o  Making #> make -C /usr/src/kernels/2.6.32-71.el6.x86_64/ M=`pwd` modules

2、用crash 工具分析vmcore 文件

用crash 命令分析vmcore 的命令行格式如下所示. 用crash打開(kāi)vmcore后,主要是用dmesg及 bt 命令打印出問(wèn)題的執(zhí)行路徑的call trace, 用dis 反匯編出代碼,最終確認(rèn)call trace對(duì)應(yīng)的C源碼中的位置,再進(jìn)行邏輯分析.

#>crash /usr/lib/debug/lib/modules/2.6.18-128.el5.x86_64/vmlinux /boot/System.map-2.6.18-128.el5.x86_64 ./vmcore

6、使用kprobe來(lái)觀察內(nèi)核函數(shù)的執(zhí)行實(shí)例

kprobe是SystemTap對(duì)內(nèi)核函數(shù)進(jìn)行probing的功能在內(nèi)核中的實(shí)現(xiàn),由于內(nèi)核中提供了正式的API來(lái)使用kprobe,所以對(duì)很多內(nèi)核程序員來(lái)說(shuō),也許直接使用kprobe比使用SystemTap更方便. 內(nèi)核中提供了三種類型的kprobe處理函數(shù),分別是jprobe, kprobe, kretprobe, 下面的代碼用這三個(gè)probe觀察在TCP/IP的arp_process函數(shù)執(zhí)行中對(duì)ip_route_input()調(diào)用的返回結(jié)果.這個(gè)代碼還展示了在同一個(gè)函數(shù)probe的Entry handler和Ret handler之間共享參數(shù)的方法. 代碼如下:

arp_probe.c /*  * arp_probe.c, by Qianfeng Zhang (frzhang@redhat.com)  */   #include   #include   #include   #include   #include   #include   #include   #include    MODULE_AUTHOR("frzhang@redhat.com");  MODULE_DESCRIPTION("A module to track the call results of ip_route_input() inside arp_process using jprobe and kretprobe");  MODULE_LICENSE("GPL");   static int j_arp_process(struct sk_buff *skb)  {  struct net_device *dev = skb->dev;  struct in_device *in_dev;  int no_addr, rpf;   in_dev = in_dev_get(dev);  no_addr = ( in_dev->ifa_list == NULL );  rpf = IN_DEV_RPFILTER(in_dev);  in_dev_put(in_dev);  printk("\narp_process() is called with interface device %s, in_dev(no_addr=%d,rpf=%d) \n", dev->name, no_addr, rpf);  jprobe_return();  return(0);  };   static int j_fib_validate_source(__be32 src, __be32 dst, u8 tos, int oif,  struct net_device *dev, __be32 *spec_dst, u32 *itag, u32 mark)   {  printk("fib_validate_source() is called with dst=0x%x, oif=%d \n", dst, oif);  jprobe_return();  return(0);  };   static struct jprobe my_jp1 = {  .entry = j_arp_process,  .kp.symbol_name = "arp_process" };   static struct jprobe my_jp2 = {  .entry = j_fib_validate_source,  .kp.symbol_name = "fib_validate_source" };   static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs)  {  printk("Calling: %s()\n", ri->rp->kp.symbol_name);  return(0);  };   static int return_handler(struct kretprobe_instance *ri, struct pt_regs *regs)  {  int eax;   eax = regs->ax & 0xffff ;  printk("Returning: %s() with a return value: 0x%lx(64bit) 0x%x(32bit)\n", ri->rp->kp.symbol_name, regs->ax, eax);   return(0);  };   static int fib_lookup_entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs)  {  struct fib_result *resp;   resp = (struct fib_result *) regs->dx;  printk("Calling: %s()\n", ri->rp->kp.symbol_name);  *((struct fib_result **)ri->data) = resp;   return(0);  };   static int fib_lookup_return_handler(struct kretprobe_instance *ri, struct pt_regs *regs)  {  struct fib_result *resp;  int eax;   eax = regs->ax & 0xffff ;  resp = *((struct fib_result **) ri->data);  printk("Returning: fib_lookup() with a return value: 0x%lx(64bit) 0x%x(32bit), result->type: %d\n", regs->ax, eax, resp->type);   return(0);  }   static struct kretprobe my_rp1 = {  .handler = return_handler,  .entry_handler = entry_handler,  .kp.symbol_name = "ip_route_input_slow" };   static struct kretprobe my_rp2 = {  .handler = return_handler,  .entry_handler = entry_handler,  .kp.symbol_name = "fib_validate_source" };   static struct kretprobe my_rp3 = {  .handler = fib_lookup_return_handler,  .entry_handler = fib_lookup_entry_handler,  .kp.symbol_name = "fib_lookup",  .data_size = sizeof(struct fib_result *)  };   static int __init init_myprobe(void)  {  int ret;   printk("RTN_UNICAST is %d\n", RTN_UNICAST);  if ( (ret = register_jprobe(&my_jp1)) < 0) {  printk("register_jprobe %s failed, returned %d\n", my_jp1.kp.symbol_name, ret);  return(-1);  }   if ( (ret = register_jprobe(&my_jp2)) < 0) {  printk("register_jprobe %s failed, returned %d\n", my_jp2.kp.symbol_name, ret);  return(-1);  }   if ( (ret = register_kretprobe(&my_rp1)) < 0 ) {  printk("register_kretprobe %s failed, returned %d\n", my_rp1.kp.symbol_name, ret);  unregister_jprobe(&my_jp1);  unregister_jprobe(&my_jp2);  return(-1);  }   if ( (ret = register_kretprobe(&my_rp2)) < 0 ) {  printk("register_kretprobe %s failed, returned %d\n", my_rp2.kp.symbol_name, ret);  unregister_jprobe(&my_jp1);  unregister_jprobe(&my_jp2);  unregister_kretprobe(&my_rp1);  return(-1);  }   if ( (ret = register_kretprobe(&my_rp3)) < 0 ) {  printk("register_kretprobe %s failed, returned %d\n", my_rp3.kp.symbol_name, ret);  unregister_jprobe(&my_jp1);  unregister_jprobe(&my_jp2);  unregister_kretprobe(&my_rp1);  unregister_kretprobe(&my_rp2);  return(-1);  }   return 0;  }    static void __exit rel_myprobe(void)  {  unregister_jprobe(&my_jp1);  unregister_jprobe(&my_jp2);  unregister_kretprobe(&my_rp1);  unregister_kretprobe(&my_rp2);  unregister_kretprobe(&my_rp3);  }   module_init(init_myprobe);  module_exit(rel_myprobe);   Makefile obj-m += arp_probe.o  Making #> make -C /usr/src/kernels/2.6.32-71.el6.x86_64/ M=`pwd` modules

上述內(nèi)容就是如何理解Linux故障定位技術(shù),你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI