《Linux內核設計與實現》 第五週 讀書筆記(第十八章)

第18章 調試

20135307張嘉琪git


18.1 準備開始

18.2 內核中的bug

  • 內核中的bug多種多樣,它們的產生能夠有無數的緣由,同時它們的表象也變化無窮,從明白無誤的錯誤代碼(好比,沒有把正確的值存放在恰當的位置)到同步時發生的錯誤(好比共享變量鎖定不當)再到錯誤地管理硬件(好比,給錯誤的控制寄存器發送錯誤的指令)。從下降全部程序的運行性能到毀壞數據再到使得系統處於死鎖狀態,均可能是bug發做時的症狀。

18.3 經過打印來調試

18.3.1 健壯性

  • 健壯性是printk()函數最容易讓人們接受的一個特質。任什麼時候候,任何地方都能調用它,內核中的printk()比比皆是。它是一個彈性極佳的函數,這一點至關重要。printk()之因此這麼有用,就在於它隨時都能被調用。printk()函數的健壯軀殼下也不免會有漏洞,在系統啓動過程當中.終端尚未初始化以前在某些地方不能使用它。不過說實在的,若是終端沒有初始化,你又能輸出到什麼地方去呢?這―般不是一個什麼問題。除非你要調試的是啓動過程最開始的那些步驟(好比說在負責執行硬件體系結構相關的初始化動做的函數中)下進行這樣的調試挑戰性很強沒有任何打印函數能用,確實讓問題更加棘手。

18.3.2 日誌等級

  • printk()和printf()在使用上最主要的區別就是前者能夠指定一個日誌級別。算法

  • 內核根據這個級別來判斷是否在終端上打印消息。安全

  • 內核把級別比某個特定值低的全部消息顯示在終端上。架構

18.3.3 記錄緩衝區

  • 內核消息都被保存在一個LOGBUFLEN大小的環形隊列中。該緩衝區大小能夠在編譯時經過設置CONFIG_LOGBUFSHIFT進行調整。在單處理器的系統上其默認值是16KB。換句話說,就是內核在同一時間只能保存16KB的內核消息。若是消息隊列已經達到最大值,那麼若是再有printk()調用時,新消息將覆蓋隊列中的老消息。這個記錄緩衝區之因此稱爲環形是由於它的讀寫都是按照環形隊列方式進行操做的。編輯器

  • 使用環形隊列有許多好處。因爲同時讀寫環形緩衝區時,其同步問題很容易解決,因此即便在中斷上下文中也能夠方便地使用printfk()。此外,它使記錄維護起來也更容易。若是有大量的消息同時產生,新消息只需覆蓋掉舊消息便可。在某個問題引起大量消息的時候。記錄只會覆蓋掉它自己,而不會由於失控而消耗掉大量內存。而環形緩衝區的惟一缺點——可能會丟失消息可是與簡單性和健壯性的好處相比這點代價是值得的。函數

18.3.4 syslogd和klogd

  • 在標準的Linux系統上,用戶空間的守護進程klogd從記錄緩衝區中獲取內核消息,再經過syslogd守護進程將它們保存在系統日誌文件中,klogd程序既能夠從/proc/kmsg文件中,也能夠經過syslog()系統調用讀取這些消息,默認狀況下,它選擇讀取/proc方式實現,無論是哪一種方法,klogd都會阻塞,直到有新的內核消息可供讀出。在被喚醒以後,它會讀取出新的內核消息並進行處理,默認狀況下,它就是把消息傳給syslogd守護進程。

18.3.5 從printf()到printk()的轉換

18.4 oops

  • oops是內核告知用戶有不行法神最經常使用的方式。工具

  • oops中包含的重要信息對於全部體系結構都是徹底相同的:寄存器上下文和回溯線索。oop

  • 回溯線索顯示了致使錯誤發生的函數調用鏈。這樣咱們就能夠觀察究竟發生了什麼:機器處於空閒狀態,正在執行idle循環,由cpuidle()循環調用defaultidle()。此時定時器中斷產生了,它引發了對定時器的處理,tulip_timer()這個定時器處理函數被調用,而就是它引用了空指針。甚至能夠經過偏移量找出致使問題的語句。性能

18.4.1 ksymoops

  • 回溯線索中的地址須要轉化成有意義的符號名稱才方便使用,這須要調用ksymoops命令。而且還必須提供編譯內核時產生的System.map。若是使用的是模塊,還須要一些模塊信息。

18.4.2 kallsyms

  • 配置選項CONFIG_KALLSYMS_ALL 表示不只存放函數名稱,還存放全部的符號名稱。

18.5 內核調試配置選項

  • 在編譯的時候,爲了方便調試和測試內核代碼,內核提供了許多配置選項。在內核配置編輯器的內核開發菜單。這些選項中,它們都依賴於CONFIGDEBUGKERNEL。

18.6 引起bug並打印信息

  • 一些內核調用能夠用來方便標記bug方便標記bug提供斷言並輸出信息。最經常使用的兩個是BUG()和些聲明BUG_ON()。當被調用的時候,它們會引起oops,致使棧的回溯和錯誤信息的打印。大部分體系結構把BUG()和BUG_ON()定義成某種會致使oops跟硬件的體系結構是相關的非法操做,這樣天然會產生須要的oops。能夠把這些調用當作斷言使用,想要斷言某種狀況不應發生。測試

  • 18.7 神奇的系統請求鍵

  • 系統請求鍵功能能夠經過定義CONFIGMAGICSYSRQ配置選項來啓用。

  • 當該功能被啓用的時候,不管內核處於什麼狀態,均可以經過特殊的組合鍵跟內核進行通訊。這種功能可讓你在面對一臺奄奄一息的系統時能完成一些有用的工做。除了配置選項之外,還要經過一個sysctl用來標記該特性的開或關。

  • 須要啓用它時使用以下命令:echo 1> /proc/sys/kernel/sysrq

18.8 內核調試器的傳奇

  • 不少內核開發者一直以來都但願能擁有一個用於內核的調試器.不幸的是,Linus不肯意在它的內核源代碼樹中加入一個調試器。他認爲調試器會誤導開發者,從而致引入不良的修正,沒有人能對他的邏輯提出異議從真正理解代碼出發,確實更能保證修正的正確性。然而,許多內核開發者們仍是但願有一個官方發佈的、用於內核的調試器。由於這個要求看起來不會立刻被知足,因此許多補丁應運而生了,它們爲標準內核附加上了內核調試的支持,雖然這都是―些不被官方承認的附加補丁,但它們確實功能完善,十分強大。在咱們深刻這些解決方案以前,先看看標準的調試器gdb可以給咱們一些什麼幫助是―個不錯的選擇。

18.8.1 gdb

18.8.2 kgdb

  • kgdb是一個補丁,它可讓咱們在遠端主機上經過串口利用gdb的全部功能對內核進行調試。這須要兩臺計算機:第一臺運行帶有kgdb補丁的內核,第二臺經過串行線使用gdb對第一臺進行調試。經過kgdb的全部功能都能使用:讀取或修改變量值,設置斷點,設置關注變量,單步執行等。某些版本的gdb甚至容許執行函數。設置kgdb和鏈接串行線比較麻煩,可是一旦作完了,調試就變得很簡單了。

18.9 探測系統

18.9.1 用UID做爲選擇條件

  • 假設爲了加入一個激動人心的新特性,你重寫了fork()系統調用。除非第一次的嘗試就天衣無縫,不然系統調試就是―場噩夢。如fork()系統調用不正常的話,壓根就不用期望整個系統還能正常工做。固然,和任什麼時候候同樣,但願老是存在的,通常狀況下,只要保留原有的算法而把你的新算法加入到其餘位置上,基本就能保證安全:能夠利用把用戶id做爲選擇條件來實現這種功能,經過這種選擇條件,能夠安排到底執行哪一種算法。

18.9.2 使用條件變量

  • 若是代碼與進程無關,或者但願有一個針對全部狀況都能使用的機制來控制某個特性,可使用條件變量。這比使用UID還來得簡單,只須要建立一個全局變量做爲一個條件選擇開關。若是該變量爲零,就使用一個分支上的代碼。若是它不爲零,就選擇另一個分支。能夠經過某種接口提供對這個變量的操控,也能夠直接經過調試器進行操控。

18.9.3 使用統計量

  • 有些時候你須要掌握某個特定事件的發生規律。有些時候須要比較多個事件並從中得出規律。經過建立統計量並提供某種機制訪問其統計結果,很容易就能知足這種需求。舉個例子,假設咱們但願獲得foo和bar的發生頻率,那麼在某個文件中,固然最好是在定義該事件的那個文件裏定義兩個全局變量。

18.9.4 重複頻率限制

18.10 用二分查找法找出引起罪惡的變動

18.11 使用Git進行二分搜索

  • Git源碼管理工具提供了一個有用的二分搜索機制。若是你使用Git來控制Linux源碼樹的副本,那麼Git將自動運行二分搜索進程。此外,Git會在修訂版本中進行二分搜索,這樣能夠找到具體哪次提交的代碼引起了bug。不少Git相關的任務比較繁雜,但使用Git進行二分搜索並不那麼的困難。

18.12 當全部的努力都失敗時:社區

  • 或許你已經作完了全部你能想到的嘗試,你在鍵盤上嘔心瀝血了幾個小時。實際上,多是無很多天子,答案依舊沒有眷顧你。此時,若是bug是在Linux內核的主流部分中,你能夠在內核開發社區中尋求其餘開發者的幫助你應該向內核郵件列表發送一份電子郵件,對bug進行完整而又簡潔地描述,你的發現可能會對找到最終的答案起到幫助。

18.13 小結

  • 本章討論了內核的調試。調試過程實際上是一種尋求實現與目標誤差的行爲,咱們考察了幾種技術:從內核內置的調試架構到調試程序,從記錄日誌到用git二分法查找,由於調試Linux內核困難重重,非調試用戶程序能比,所以,本章的資料對於試圖在內核代碼中牛刀小試的任何人都相當重要。
相關文章
相關標籤/搜索