【Linux內核debug】二分法與printk()

人生就是一個茶几,上面擺滿了杯具。內核也是一個大茶几,不過它上面的杯具是一個個的bug。確定bug什麼時候被引入是一個很關鍵的步驟,在這個定位bug的過程中,不論有意或無意,都會很自然地用到二分查找的方法。

二分查找法的基本原理
對於二分查找法,我們不會也不應該會感到陌生。作爲一種高效的查找算法,它曾出現在我們的數據結構課堂裏,出現在一次又一次的面試裏,更是會頻繁地應用在我們的代碼裏。在我們所接觸到的各種算法裏,它可以說是最爲大衆化、最充滿生活智慧的一個,很多人並不知道二分查找法的概念,卻能夠在生活中熟練的去應用。

比如,一個工人要維修一條10km長的電話線,首先他需要定位出故障所在,如果沿着線路一小段一小段地查找,顯然非常得困難,每查一個點都要爬一次電線杆,10km長的距離會有大約200多根電線杆。假設電線兩端分別爲A、B,這時他會很自然地首先從中間的C開始查起,用話機向兩端測試時,發現AC段正常,故而斷定故障在BC段,再到BC段的中點D,如果發現BD段正常,則故障在CD段,然後再到CD的中間點E查找,這樣每查一次,就可以把待查線路的長度縮減一半,因而經過7次查找,就可以將故障發生的範圍縮小到50~100m左右,即在一兩根電線杆附近。如此一來要節省很多的精力與時間。

這是二分查找法在生活中的一個典型應用,實際上,查找內核的bug與查找電話線的故障相比,本質上都是相同的,並沒有高深到哪裏去,都是首先要定位出故障的位置,然後去解決它。

比如你在使用某個版本的內核時,發現了一個內核bug,這時你需要知道它究竟是在應用哪個補丁時被引入的,如果一個一個的去還原那些補丁,每還原一個補丁就要測試一次內核,那麼必然會浪費過多的時間,而應用二分查找法,首先確定一個肯定沒有出現該bug的內核版本,然後去測試位於這兩個版本中間的那個版本,這樣重複篩選,就能夠很容易的定位出是從哪個版本開始出現了這個bug。

printk()
printk()應該是每一個驅動開發者最爲親密的夥伴了,我們常常將它與二分查找法結合在一起尋找代碼中發生問題的位置。

通常情況下,對於代碼中的兩個printk()語句,如果一個正常執行,而另一個沒有被執行,就說明問題發生在這兩個printk()之間,接下來就可以在這個範圍內應用二分查找法定位有問題的代碼。

1. printk()與printf()

用戶空間有printf(),內核空間有printk(),它們就如代表善與惡的命運雙生子,即使長相功能如何的接近,都不能在代碼中共存。

對於我們來說,最容易犯的錯誤是,在需要printk()的地方誤用了printf(),而在需要printf()的地方卻又誤用了printk(),通常這都不會是因爲不知道它們的區別,而只是習慣使然。民間流傳有這樣的說法:當你在編寫用戶空間應用程序的時候,下意識寫出的都是printk(),那麼就說明你是個標準的內核開發者了。

2. printk()的消息級別

printk()與printf()的一個重要區別就是前者可以指定消息的打印級別,內核根據這個指定的級別來決定是否將消息打印到終端上。如下表所示,printk()共有8個級別。

級別
 描述
 
KERN_EMERG
 緊急情況,系統可能會崩潰
 
KERN_ALERT
 必須立即響應
 
KERN_CRIT
 臨界情況
 
KERN_ERR
 錯誤信息
 
KERN_WARNING
 警告信息
 
KERN_NOTICE
 普通的但可能需要注意的信息
 
KERN_INFO
 提示性信息
 
KERN_DEBUG
 調試信息
 

如果沒有指定消息的級別,printk()會使用默認的DEFAULT_MESSAGE_LOGLEVEL(通常是KERN_WARNING)。

3. 控制檯的日誌級別(console_loglevel)

當printk指定的消息級別小於指定的控制檯日誌級別時,消息的內容就會顯示在該控制檯上。控制檯的日誌級別定義在include/linux/kernel.h文件中,默認爲DEFAULT_CONSOLE_LOGLEVEL(值等於7),也就是說默認情況下,比KERN_DEBUG級別高的printk()消息內容都可以在控制檯上顯示。

我們可以執行下面的命令使任何級別的printk()消息都被打印在終端上

$ echo 8 > /proc/sys/kernel/printk

4. printk()的變體

內核在include/linux/kernel.h文件中提供了兩個printk()的變體pr_debug和pr_info,它們的定義爲:

235 #define pr_debug(fmt,arg...) /

236 printk(KERN_DEBUG fmt,##arg)

244 #define pr_info(fmt,arg...) /

245 printk(KERN_INFO fmt,##arg)

5. printk()不是萬能的

printk()雖然很好用,但它並不是萬能的,在系統啓動時,終端還沒有初始化之前,它並不能被使用,不過如果不是在調試系統的啓動過程的話,這並不能算是個問題。

其實內核提供了一個printk()的變體early_printk(),專門用於在系統啓動的初期在終端上打印消息,它與printk()的區別僅僅在於名字的不同以及它能夠更早地工作。

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/fudan_abc/archive/2010/04/29/5543647.aspx

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