linux設備驅動程序2004-驅動設計第四章_第1頁
linux設備驅動程序2004-驅動設計第四章_第2頁
linux設備驅動程序2004-驅動設計第四章_第3頁
linux設備驅動程序2004-驅動設計第四章_第4頁
免費預覽已結束,剩余9頁可下載查看

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、第 4 章 調試技術對于任何編寫內核代碼的人來說,最吸引他們注意之一就是如何完成調試。由于內核是一個不與某個進程相關的功能集,其代碼不能很輕松地放在調試器中執(zhí)行,而且也不能跟蹤。本章介紹你可以用來監(jiān)視內核代碼和錯誤的技術。用打印信息調試最一般的調試技術就是監(jiān)視,就是在應用的時候,你可以用 prk 完成這個任務。合適的點加上 prf 調用。當你調試內核代碼Prk些章中,簡單假設prk 工作起來和 prf 很類似?,F在是介紹一下它們之間不同的時候了。其中一個不同點就是,prk 允許你根據它們的嚴重程度,通過附加不同的“消息分類,或賦予消息優(yōu)先級。你可以用宏來指示級。例如,KERN_INFO,級”來

2、對前面已經看到它被加在打印語句的前面,它就是一種可能的消息級。級宏展開為一個字串,在編譯時和消息文本拼接在一起;這也就是為什么下面的例子中優(yōu)先級和格式字串間沒有逗號。這有兩個 prk 的例子,一個是調試信息,一個是關鍵信息:(代碼)在中定義了 8 種級別串。沒有指定優(yōu)先級的 prk 語句默認使用DEFAULT_MESSAGE_LOGLEVEL 優(yōu)先級,它是一個在 kernel/prk.c 中定義的整數。默認級的具體數值在Linux 的開發(fā)期間曾變化過若干次,所以我建議你最好總是指定一個合適的根據級。級,內核將消息打印到當前文本控制臺上:如果優(yōu)先級低于 console_loglevel 這個數值

3、的話,該消息就顯示在控制臺上。如果系統(tǒng)同時運行了 klogd 和 syslogd,無論 console_loglevel 為何值,內核都將消息追加到/var/log/messages 中。變量 console_loglevel 最初初始化為 DEFAULT_CONSOLE_LOGLEVEL , 但可以通過sys_syslog 系統(tǒng)調用修改。如 klogd冊所示,可以在啟動 klogd 時指定-c 開關來修改這個變量。此外,你還可以寫個程序來改變控制臺級。你可以在 OReilly 站點上的源文件中找到我寫的一個這種功能的程序,miscprogs/setlevel.c。新優(yōu)先級是通過一個 1 到

4、8 之間的整數值指定的。你也許需要在內核失效后降低級(見“調試系統(tǒng)故障”),這是因為失效處理代碼會將console_loglevel到 15,之后所有的消息都會出現在控制臺上。為看到你的調試信息,如果你運行的是內核 2.0.x 話, 你 需要級 。 內核 2.0降低了 MINIMUM_CONSOLE_LOGLEVEL,而舊版本的 klogd 默認情況下要打印很多控制消息。如果你碰巧使用了這個舊版本的守護進程,除非你級,內核 2.0 會比你預期的打印出更少的消息。這就是為什么o.c 中使用了標記,這樣可以保證消息顯示在控制臺上。從 1.3.43 一來的內核版本通過允許你向指定虛控制臺發(fā)送消息,藉

5、此提供一個靈活的策略。默認情況下,“控制臺”是當前虛終端。也可以選擇不同的虛終端接收消息,你只需向所選的虛終端調用 ioctl(TIOCLINUX)。如下程序,setconsole,可以用來選擇哪個虛終端接收內核消息;它必須以超級用戶運行。如果你對 ioctl 還不有把握,你可以跳過這至下一節(jié),等到讀完第 5 章“字符設備驅動程序的擴展操作”的“ioctl”一節(jié)后,再回到這里讀這段代碼。(代碼)setconsole 使用了用于 Linux功能的特殊的 ioctl 命令 TIOCLINUX 。為了使用TIOCLINUX,你要傳遞給它一個指向字節(jié)數組的指針。數組的第一個字節(jié)是所請求的子命令的編碼,

6、隨后的字節(jié)依命令而不同。在 setconsole 中使用了子命令 11,后一個字節(jié)(存放在 bytes1中)標別虛擬控制臺。TIOCLINUX 的完成介紹可以在內核源碼 drivers/char/tty_io.c中找到。消息是如何的prk 函數將消息寫到一個長度為 LOG_BUF_LEN 個字節(jié)的循環(huán)緩沖區(qū)中。然后喚醒任何等待消息的進程,即那些在調用 syslog 系統(tǒng)調用或/proc/kmesg 過程中睡眠的進程。這兩個引擎的接口是等價的。不過/proc/kmesg 文件更象一個 FIFO 文件,從中數據更容易些。一跳簡單的 cat 命令就可以消息。如果循環(huán)緩沖區(qū)填滿了,pr k 就繞到緩沖

7、區(qū)的開始處填寫新數據,覆蓋舊數據。于是進程就丟失了最舊的數據。這個問題與利用循環(huán)緩沖區(qū)所獲得的好處相比可以忽略不計。例如,循環(huán)緩沖區(qū)可以使系統(tǒng)在沒有 進程的情況下照樣運行,同時又不浪費內存。Linux處理消息的方法的另一個特點是,可以在任何地方調用 pr k,甚至在中斷處理函數里也可以調用,而且對數據量的大小沒有限制。這個方法的唯一缺點就是可能丟失某些數據。如果 klogd 正在運行,它內核消息并將它們分派到 syslogd,它隨后檢查/etc/syslog.conf找到處理這些數據的方式。syslogd 根據一個“設施”和“優(yōu)先級”切分消息;可以使用的值定義在中。內核消息根據相應 prk 中

8、指定的優(yōu)先級。如果 klogd 沒有運行,數據將保存在循環(huán)緩沖區(qū)中直到有進程來到 LOG_KERN 設數據或數據溢出。,你給 klogd 指定-f(文件)法是一種強硬方法:殺掉如果你不希望因監(jiān)視你的驅動程序的消息而的系統(tǒng)選項或修改/etc/syslog.conf 將寫到另一個文件中。另klogd,將消息打印到不用的虛終端上*,或者在一個不用的 xterm 上執(zhí)行 cat /proc/kmesg 顯示消息。使用預處理方便監(jiān)視處理在驅動程序開發(fā)早期,prk 可以對調試和測試新代碼都非常有幫助。然而當你正式驅動程序時,你應該去掉,或者至少關閉,這些打印語句。很不幸,你可能很快就發(fā)現,隨著你想不再需要

9、那些消息并去掉它們時,你可能又要加新功能,你又需要這些消息了。解決這些問題有幾種方法如何從全局打開和關閉消息以及如何打開和關閉個別消息。下面給出了我處理消息所用的大部分代碼,它有如下一些功能:可以通過在宏名字加一個字母或去掉一個字母打開或關閉每一條語句。*例如,使用命令setlevel 8;set console 10 設置終端 10 顯示消息。通過在編譯前修改 CFLAGS 變量,可以一次關閉所有消息。同樣的打印語句既可以用在內核態(tài)(驅動程序)也可以用在用戶態(tài)(演示或測試程序)。下面這些直接來自 scull.h 的代碼片斷實現了這些功能。(代碼)符合 PDEBUG 和 PDEBUGG 依賴于

10、是否定義了 SCULL_DEBUG,它們都和 prf 調用很類似。為了進一步方便這個過程,在你的 Makefile 加上如下幾行。(代碼)本節(jié)所給出的代碼依賴于 gcc 對 ANSI C 預編譯器的擴展,gcc 可以支持帶可變數目參數的宏。這種對 gcc 的依賴并不是什么問題,因為內核對 gcc 特性的依賴更強。此外,Makefile依賴于 GNU 的gmake;基于同樣的道理,這也不是什么問題。如果你很熟悉 C 預編譯器,你可以將上面的定義擴展為可以支持“調試級”概念的,可以為每級賦一個整數(或位圖),說明這一級打印多么瑣碎的消息。但是每一個驅動程序都有它自己的功能和監(jiān)視需求。好的編程技巧會

11、在靈活性和高效之間找到一個權衡點,這個我就不能說哪個對你最好了。記住,預編譯器條件(還有代碼中的常量法就是使用 C表達式)只到編譯時運行,你必須重新編譯程序來打開或關閉消息。另條件語句,它在運行時運行,因此可以讓你在程序執(zhí)行期間打開或關閉消息。這個功能很好,但每次代碼執(zhí)行系統(tǒng)都要進行額外的處理,甚至在消息關閉后仍然會影響性能。有時這種性能損失是無法接受的。個人觀點,盡管上面給出的宏迫使你每次要增加或去掉消息時都要重新編譯,重新加載模塊,但我覺得用這些宏已經很好了。通過查詢調試上一節(jié)談到了 prk 是如何工作的以及如何使用它。但沒有談及它的缺點。由于 syslogd 會一直保持刷新它的輸出文件,

12、每打印一行都會引起一次磁盤操作,因此過量使用 prk 會嚴重降低系統(tǒng)性能。至少從 syslogd 的角度看是這樣的。它會將所有的數據都一股腦地寫到磁盤上,以防在打印消息后系統(tǒng);然而,你不想因為調試信息的緣故而降低系統(tǒng)性能。這個問題可以通過在/etc/syslogd.conf 中文件的名字前加一個波折號解決,但有時你不想修改你的配置文件。如果不這樣,你還可以運行一個非 klogd 的程序(如前面介紹的 cat /proc/kmesg),但這樣并不能為正常操作提供一個合適的環(huán)境。與這相比,最好的方法就是在你需要信息的時候,通過查詢系統(tǒng)獲得相關信息,而不是持續(xù)不斷地產生數據。事實上,每一個 Unix

13、 系統(tǒng)都提供了很多工具用來獲得系統(tǒng)信息:ps,nets,vms等等。有許多技術適合與驅動程序開發(fā)ioctl 驅動程序方法。查詢系統(tǒng),簡而言之就是,在/proc 下創(chuàng)建文件和使用使用/proc 文件系統(tǒng)Linux 中的/proc 文件系統(tǒng)與任何設備都沒有關系/proc 中的文件都在被時有創(chuàng)建的。這些文件都是普通的文本文件,它們基本上可由普通人理解,也可被工具程序理解。例如,對于大多數 Linux 的 ps 實現而言,它都通過/proc 文件系統(tǒng)獲得進程表信息的。/proc 虛擬文件的創(chuàng)意已由若干現代操作系統(tǒng)使用,且非常成功。/proc 的當前實現可以動態(tài)創(chuàng)建 i 節(jié)點,允許用戶模塊為方便信息檢索

14、創(chuàng)建如何點。為了在/proc 中創(chuàng)建一個健全的文件節(jié)點(可以 read,write,seek 等等),你需要定義file_operations 結構和 inode_operations 結構,后者與前者有類似的作用和尺寸。創(chuàng)建這樣一個 i 節(jié)點比起創(chuàng)建整個字符設備并沒不同這里不這個問題,如果你感,你可以在源碼樹 fs/proc 中獲得進一步細節(jié)。與大多數/proc 文件一樣,如果文件節(jié)點僅僅用來讀,創(chuàng)建它們是比較容易的,我將這里介紹這一技術。很不幸,這一技術只能在Linux 2.0 及其后續(xù)版本中使用。這里是創(chuàng)建一個稱為/proc/scullmem 文件的 scull 代碼,這個文件用來獲取

15、scull 使用的內存信息。(代碼)填寫/proc 文件非常容易。你的函數獲取一個空閑頁面填寫數據;它將數據寫進緩沖區(qū)并返回所寫數據的長度。其他事情都由/proc 文件系統(tǒng)處理。唯一的限制就是所寫的數據過 PAGE_SIZE 個字節(jié)(宏 PAGE_SIZE 定義在頭文件中;它是與體系結構相關的,但你至少可以它有 4KB 大?。?。如果你需要寫多于一個頁面的數據,你必須實現功能健全的文件。注意,如果一個正在讀你的/proc 文件的進程發(fā)出了若干 read 調用,每一個都獲取新數據,盡管量數據被,你的驅動程序每次都要重寫整個緩沖區(qū)。這些額外的工作會使系統(tǒng)性能下降,而且如果文件產生的數據與下一次的不同

16、,以后的 read 調用要重新裝配不相關的部分,這一會造成數據錯位。事實上,由于每個使用 C 庫的應用程序都大塊地數據,性能并不是什么問題。然而,由于錯位時有發(fā)生,它倒是一個值得考慮。在獲取數據后,庫調用至少要調用 1 次 read只有當 read 返回 0 時才文件尾。如果驅動程序碰巧比前面產生了的數據,系統(tǒng)就返回到用戶空間額外的字節(jié)并且與前面的數據塊是在第 6 章“時間流”的“任務隊列”一節(jié)中涉及/proc/jiq*,那時錯位的。到錯位問題。還會遇cleanup_module 中應該使用下面的語句注銷/proc 節(jié)點:(代碼)傳遞給函數的參數是包含要撤銷文件的目錄名和文件的 i 節(jié)點號。由

17、于 i 節(jié)點號是自動分配的,在編譯時是無法知道的,必須從數據結構中。ioctl 方法ioctl,下一章將詳細,是一個系統(tǒng)調用,它可以操做在文件描述符上;它接收一個“命令”號和(可選的)一個參數,通常這是一個指針。做為替代/proc 文件系統(tǒng)的方法,你可以為調試實現若干 ioctl 命令。這些命令從驅動程序空間相關數據到進程空間,在進程空間里檢查這些數據。只有使用 ioctl 獲取信息比起/proc 來要一些,因為你一個程序調用 ioctl 并顯示結果。必須編寫這樣的程序,還要編譯,保持與你測試的模塊間的一致性等。不過有時候這是最好的獲取信息的方法,因為它比起讀/proc 來要快得多。如果在數據

18、寫到屏幕前必須完成某些處理工作,以二進制獲取數據要比 文本文件有效得多。此外,ioctl不限制返回數據的大小。ioctl 方法的一個優(yōu)點是,當調試關閉后調試命令仍然可以保留在驅動程序中。/proc 文件對任何查看這個目錄的人都是可見的,然而與/proc 文件不同,未公開的 ioctl 命令通常都不會*被注意到。此外,如果驅動程序會稍微大一些。異常,它們仍然可以用來調試。唯一的缺點就是模塊通過監(jiān)視調試有時你遇到并不特別糟,通過在用戶空間運行應用程序來查看驅動程序與系統(tǒng)之間的交互過程可以幫助你捕捉到一些小問題,并可以驗證驅動程序確實工作正常。例如,看到scull 的 read 實現如何處理不同數據

19、量的 read 請求后,我對 scull 更有信心。有許多方法監(jiān)視一個用戶態(tài)程序的工作情況。你可以用調試器一步步它的函數,打印語句,或者用 strace 運行程序。在實際目的是查看內核代碼時,最后一項技術非常有用。 strace 命令是一個功能非常強大的工具,它可以現實程序所調用的所有系統(tǒng)調用。它不僅可以顯示調用,而且還能顯示調用的參數,以符號方式顯示返回值。當系統(tǒng)調用失敗時,錯誤的符號值(如,ENOMEM)和對應的字串(Out of memory)同時顯示。strace 還有許多命令行選項;最常用的是-t,它用來顯示調用發(fā)生的時間,-T,顯示調用所花費的時間,以及-o,將輸出重定向到一個文件

20、中。默認情況下,strace 將所有信息打印到 stderr 上。strace 從內核接收信息。這意味著一個程序無論是否按調試方式編譯(用gcc 的-g 選項)或是被去掉了符號信息都可以被。與調試器可以連接到一個運行進程并控制它類似,你還可以一個已經運行的進程。信息通常用來生成錯誤給應用開發(fā),但是對內核編程來說也一樣非常檢查每一次調用有用??梢钥吹较到y(tǒng)調用是如何執(zhí)行驅動程序代碼的;strace 允許輸入輸出的一致性。例如,下面的屏幕輸出給出了命令 ls /dev /dev/scull0 的最后幾行:(代碼)很明顯,在 ls 完成目標目錄的檢索后首次對 write 的調用中,它試圖寫 4KB。很

21、奇怪,只寫了 4000 個字節(jié),接著重試這一操作。然而,知道 scull 的 write 實現每次只寫一個量子,我在這里看到了部分寫。經過若干步驟之后,所有的東西都清空了,程序正常退出。另一個例子,讓來讀 scull 設備:(代碼)正如所料,read 每次只能讀到 4000 個字節(jié),但是數據總量是不變的。注意本例中重試工作是如何組織的,注意它與上面寫標準庫,以便每次用一個系統(tǒng)調用要讀 16KB。的對比。wc 專門為快速讀數據進行了優(yōu)化,它繞過了的數據。你可以從的 read 行中看到 wc 每次Unix可以在 strace 的輸出中找到很多有用信息。如果你被這些符號搞得滿頭霧水,我可以只看文件方

22、法(open,read 等等)是如何工作的。個人認為,perror 調用有幫助。工具在查明系統(tǒng)調用的運行時錯誤過程中最有用。通常應用或演示程序中的以用來調試,而且對于查明到底是什么樣的參數觸發(fā)了系統(tǒng)調用的錯誤也很調試系統(tǒng)故障即便你用了所有監(jiān)視和調試技術,有時候驅動程序中依然有錯誤,當這樣的驅動程序執(zhí)行會會造成系統(tǒng)故障。當這種情況發(fā)生時,獲取足夠多的信息來解決問題是的。注意,“故障”不意味著“panic”。Linux 代碼非常魯棒,可以很好地響應大部分錯誤:故障通常會導致當前進程的終止,但系統(tǒng)繼續(xù)運行。如果在進程上下文之外發(fā)生故障,或是組成系統(tǒng)的重要發(fā)生故障時,系統(tǒng)可nic。但問題出在驅動程序時

23、,通常只會導致產生故障的進程終止即那個使用驅動程序的進程。唯一不可恢復的損失就是當進程被終止時,進程上下文分配的內存丟失了;例如,由驅動程序通過 kmalloc 分配的動態(tài)鏈表可能丟失。然而,由于內核會對尚是打開的設備調用close,你的驅動程序可以法分配的資源。任何有open 方已經,當內核行為異常時會在控制臺上顯示一些有用的信息。下一節(jié)將解釋如何解碼和使用這些消息。盡管它們對于初學者來說相當晦澀,處理器的給出數據都是些很有意思的信息,通常無需額外測試就可以查明程序錯誤。Oops 消息大部分錯誤都是 NULL 指針oops 消息?;蚴褂闷渌徽_的指針數值。這些錯誤通常會導致一個由處理器使用

24、的地址都是“虛”地址,而且通過一個復雜的稱為頁表(見第 13 章“Mmap和 DMA”中的“頁表”一節(jié))的結構為物理地址。當一個指針時,頁面機制就不能將地址到物理地址,并且處理器向操作系統(tǒng)發(fā)出一個“頁面失效”。如果地址確實是的,內核就無法從失效地址上“換頁”;如果此時處理在超級用戶太,系統(tǒng)于是就產生一個“oops”。值得注意的是,在版本 2.1 中內核處理失效的方式有所變化,它可以處理在超級用戶態(tài)的間失效”中介紹。地址了。新實現將在第 17 章“最近發(fā)展”的“處理內核空oops 顯示故障時的處理器狀態(tài),模塊 CPU 寄存器內容,頁描述符表的位置,以及其他似乎不能理解的信息。這些是由失效處理函數

25、(arch/*/kernel/traps.c)中的 prk 語句產生的,而且象前面“Prk”一節(jié)介紹的那樣進行分派。讓看看這樣一個消息。這里給出的是傳統(tǒng)個人電腦(x86),運行 Linux 2.0 或更新版本的 oops版本 1.2 的輸出稍有不同。(代碼)上面的消息是在一個有意加入錯誤的失效模塊上運行cat 所至。fault.c(代碼)如下代碼:由于 read 從它的小緩沖區(qū)(faulty_buf)工作。然而,每次讀出多于 1KB 的數據會數據到用戶空間,頁面邊界,如果希望讀一小塊文件能夠了頁面 read 就會失敗。事實上,前面給出的 oops 是在請求一個 4KB 大小的 read時發(fā)生的

26、,這條消息在/var/log/messages(syslogd 默認存放內核消息的文件)的 oops 消息前給出了:(代碼)同樣的 cat 命令卻不能在 Alpha 上產生oops,這是因為從 faulty_buf4KB 字節(jié)沒有超出頁邊界(Alpha 上的頁面大小是 8KB,緩沖區(qū)正好在頁面的起始位置附近)。如果在你的系統(tǒng)上faulty 沒有產生 oops,試試wc,或者給dd 顯式地指定塊大小。使用 ksymoopsoops 消息的最大問題就是十六進制數值對于程序員來說沒什么意義;需要將它們號。為符內核源碼通過其所包含的 ksymoops 工具幫助開發(fā)但是注意,版本 1.2 的源碼中沒有這

27、個程序。該工具將 oops 消息中的數值地址為內核符號,但只限于 PC 機產生的 oops消息。由于消息本身就是處理器相關的,每一體系結構都有其自身的消息格式。ksymoops 從標準輸入獲得 oops 消息,并從命令行內核符號表的名字。符號表通常就是/usr/src/linux/System.map。程序以更可讀的方式打印調用軌跡和程序代碼,而不是最原始的 oops 消息。下面的片斷就是用上一節(jié)的 oops 消息得出的結果:(代碼)由 ksymoops 反匯編出的代碼給出了失效的指令和其后的指令。很明顯對于那些知道一點匯編的人repz movsl 指令(REPeat till cx is Z

28、ero, MOVe a String of Longs)用源索引(esi,是 0 x202e000)了一個未頁面。用來獲得模塊信息的 ksymoops -m 命令給出,模塊到一個在 0 x0202d的頁面上,這也確認樂 esi 確實超出了范圍。由于 faulty 模塊所占用的內存不在系統(tǒng)表中,被的調用軌跡還給出了兩個數值地址。這些值可以手動補充,或是通過ksyms 命令的輸出,或是在/proc/ksyms 中查詢模塊的名字。然而對于這個失效,這兩個地址并不對應與代碼地址。如果你看了 arch/i386/kernel/traps.c,你就發(fā)現,調用軌跡是從整個堆棧并利用一些啟發(fā)式方法區(qū)分數據值(

29、本地變量和函數參數)和返回地址獲得的。調用軌跡中只給出了內核代碼的地址和模塊的地址。由于模塊所占頁面既有代碼也有數據,錯綜復雜的??赡軙┑魡l(fā)式信息,這就是上面兩個0 x202x 地址的情況。如果你不愿手動查看模塊地址,下面這組管道可以用來創(chuàng)建一個既有內核又有模塊符號的符號表。無論何時你加載模塊,你都必須重新創(chuàng)建這個符號表。(代碼)這個管道將完整的系統(tǒng)表與/proc/ksyms 中的公開內核符號混合在一起,后者除了內核符號外,還包括了當前內核里的模塊符號。這些地址在 insmod 重定位代碼后就出現在/proc/ksyms中。由于這兩個文件的格式不同,使用了 sed 和awk 將所有的文本行

30、轉換為一種合適的格式。然后對這排序,去除重復部分,這樣 ksymoops 就可以用了。如果重新運行 ksymoops,它從新的符號表中截取出如下信息:(代碼)正如你所見到的,當與模塊有關的 oops 消息時,創(chuàng)建一個修訂的系統(tǒng)表是很有助益的:現在 ksymoops 能夠對指令指針并完成整個調用軌跡了。還要注意,顯式反匯編碼的格式和 objdump 所使用的格式一樣。objdump 也是一個功能強大的工具;如果你需要查看失敗前的指令,你調用命令 objdump d faulty.o。在文件的匯編列表中,字串 faulty_read+45/60 標記為失效行。有關 objdump 的的信息和它令行

31、選項可以參見該命令冊。即便你構建了你自己的修訂版符號表,上面提到的有關調用軌跡仍然存在:雖然0 x202學會x 指針被了,但仍然是。oops 消息需要一定的經驗,但是確實值得一做。用來學習的時間很快就會有所回報。不過由于機器指令的 Unix 語法與el 語法不同,唯一在于從哪獲得有關匯編語el 語法的編程獲得的。在參考言的文檔;盡管你了解 PC 匯編語言,但你的經驗都是用書目中,我給一些有所補益的書籍。使用 oops使用 ksymoops 有些繁瑣。你需要 C+編譯器編譯它,你還要構建你自己的符號表來充分發(fā)揮程序的能力,你還要將原始消息和 ksymoops 輸出合在一起組成可用的信息。如果你不

32、想找這么多麻煩,你可以使用oops 程序。oops 在本書的OReilly FTP 站點給出的源碼中。它源自最初的 ksymoops 工具,現在它的作者已經不這個工具了。oops 是用 C語言寫成的,而且直接查看/proc/ksyms 而無需用戶每次加載模塊后構建新的符號表。該程序試圖所有的處理器寄存器并堆棧軌跡為符號值。它的缺點是,它要比ksymoops 羅嗦些,但通常你所有的信息越多,你發(fā)現錯誤也就越快。oops 的另一個優(yōu)點是,它可以x86,Alpha 和Sparc 的 oops 消息。與內核源碼相同,這個程序也按 GPL。oops 產生的輸出與 ksymoops 的類似,但是更完全。這

33、里給出前一個 oops 輸出的開始部分由于在這個 oops 消息中堆棧沒保存什么有用的東西,我不認為應該顯示整個堆棧軌跡:(代碼)當你調試“真正的”模塊(faulty 太短了,沒意義)時,將寄存器和堆棧是非常有益的,而且如果被調試的所有模塊符號都開放出來時更有幫助。在失效時,處理器寄存器一般不會指向模塊的符號,只有當符號表開放給/proc/ksyms 時,你才能輸出中標別它們??梢杂靡幌虏襟E制作一張更完整的符號表。首先,不應在模塊中靜態(tài)變量,否則就無法用 insmod 開放它們了。第二,如下面的截取自 scull 的 init_module 函數的代碼所示,(代碼)可以用#ifdef SCUL

34、L_DEBUG 或類似的宏register_symtab 調用。在第 2 章“編寫和運行模塊”的“符號表”一節(jié)中已經看到了類似內容,那里說,如果模塊不符號表,所有的全局符號就都開放。盡管這能僅在SCULL_DEBUG 被激活時才有效,為了避免內核中的名字空間污染,所有的全局符號有合適的前綴(參見第 2章的“模塊與應用程序”一節(jié))。使用 klogdklogd 守護進程的近期版本可以在 oops 存放到文件前對 oops 消息。過程只由版本 1.3 或更新版本的守護進程完成,而且只有將-k /usr/src/linux/System.map 做為參數傳遞給守護進程時才。(你可以用其他符號表文件代替

35、 System.map)有新的 klogd 給出的faulty 的oops 如下所示,它寫到了系統(tǒng)(代碼)中:能的 klogd 對于調試一般的 Linux 安裝的來說是很好的工具。由 klogd的消息包括大部分 ksymoops 的功能,而且也要求用戶編譯額外的工具,或是,當系統(tǒng)出現故障時,為了給出完整的錯誤而合并兩個輸出。當 oops 發(fā)生在內核時,守護進程還會正確地指令指針。它并不反匯編代碼,但這不是問題,當錯誤給出消息時,二進制數據仍然存在,可以離線反匯編代碼。守護進程的另一個功能就是,如果符號表版本與當前內核不匹配,它會符號。如果在系統(tǒng)中出了符號,你可以確信它是正確的。然而,盡管它對

36、Linux 用戶很有幫助,這個工具在調試模塊時沒幫助。我個人沒有在開放的電腦里使用選項。klogd是它不模塊中的符號;因為守護進程在程序員加載模塊前就已經運行了,即使讀了/proc/ksyms 也不會幫助。文件中存在后的符號會使oops 和 ksymoops,造成進一步的如果你需要使用 klogd 調試你的模塊,版本的守護進程需要加入一些新的特殊支持,我期待它的完成,只要給內核打一個小補丁就可以了。系統(tǒng)掛起盡管內核代碼中的大多數錯誤僅會導致一個 oops 消息,有時它們完全將系統(tǒng)掛起。如果系統(tǒng)掛起了,沒有消息能夠打印出來。例如,如果代碼遇到一個死循環(huán),內核停止了調度過程,系統(tǒng)不會再響應任何動作

37、,包括魔法鍵 Ctrl-Ael 組合。處理系統(tǒng)掛起有兩個選擇一個是防范與未然,另一個就是亡羊補牢,在發(fā)生掛起后調試代碼。通過在策略點上schedule 調用可以防止死循環(huán)。schedule 調用(正如你所猜想到的)調用調度器,因此允許其他進程偷取當然進程的 CPU 時間。如果進程因你的驅動程序中的錯誤而在內核空間循環(huán),你可以在到這種情況后殺掉這個進程。在驅動程序代碼中schedule 調用會給程序員帶來新”:函數,,以及調用軌跡中的所有函數,必須是可重入的。在正常環(huán)境下,由于不同的進程可能并發(fā)地設備,驅動程序做為整體是可重入的,但由于 Linux 內核是不可搶占的,不必每個函數都是可重入的。但

38、如果驅動程序函數允許調度器中斷當前進程,另一個不同的進程可能會進入同一個函數。如果 schedule 調用僅在調試期間打開,如果你不允許,你可以避免兩個并發(fā)進程驅動程。在介紹阻塞型操作時(第 5 章的“寫可重入代序,所以并發(fā)性倒不是什么非常重要碼”)再詳細介紹并發(fā)性問題。如果要調試死循環(huán),你可以利用 Linux 鍵盤的特殊鍵。默認情況下,如果和修飾鍵一起按了PrScr 鍵(鍵碼是 70),系統(tǒng)會向當前控制臺打印有關機器狀態(tài)的有用信息。這能在 x86和 Alpha 系統(tǒng)都有。Linux 的 Sparc 移植也有同樣的功能,但它使用了標記為“Break/Scroll Lock”的鍵(鍵碼是 30)

39、。每一個特殊函數都有一個名字,并如下面所示都有一個按鍵事件與之對應。組合鍵之后的括號里是函數名。Shift-PrScr(Show_Memory)打印若干行關于內存使用的信息,尤其是有關緩沖區(qū)高速緩存的使用情況。Control-PrScr(Show_Se)針對系統(tǒng)里的每一個處理器打印一行信息,同時還打印RightAlt-PrScr(Show_Registers)進程樹。對當前進程進行標記。由于它可以打印按鍵時的處理器寄存器內容,它是系統(tǒng)掛起時最重要的一個鍵了。如果有當前內核的系統(tǒng)表的話,查看指令計數器以及它如何隨時間變化,對了解代碼在何處循環(huán)非常有幫助。如果想將這些函數到不同的鍵上,每一個函數名

40、都可以做為參數傳遞給 loadkeys。鍵盤表可以任意修改(這是“策略無關的”)。如果 console_loglevel 足夠到的話,這些函數打印的消息會出現在控制臺上。如果不是你運行了一個舊 klogd 和一個新內核的話,默認級應該足夠了。如果沒有出現消息,你可以級?!白銐蚋摺钡木唧w值與你使用的內核版本有關。對于 Linux 2.0象以前說的那樣或更新的版本來說是 5。即系統(tǒng)掛起時,消息也會打印到控制臺上,確認級足夠高是非常重要的。消息是在產生中斷時生成的,因此即便有錯的進程不CPU 也可以運行當然,除非中斷被屏蔽了,不過如果發(fā)生這種情況既不太可能也非常不幸。有時系統(tǒng)看起來象是掛起了,但其實

41、不是。例如,如果鍵盤因某種奇怪的原因被鎖住了就會發(fā)生這種情況。這種假掛起可以通過查看你為探明此種情況而運行的程序輸出來判斷。我有一個程序會不斷地更新 LED 顯示器上的時鐘,我發(fā)現這個對于驗證調度器尚在運行非常有用。你可以不必依賴外部設備就可以檢查調度器,你可以實現一個程序讓鍵盤 LED 閃爍,或是不斷地打開關閉軟盤馬達,或是不斷觸動揚聲器不過我個人認為,通常的蜂鳴聲很煩人,應該盡量避免。看看 ioctl 命令 KDMKTONE。OReilly FTP 站點上的例子程序(misc-progs/heartbeat.c)中有一個是讓鍵盤 LED 不斷閃爍的。如果鍵盤不接收輸入了,最佳的處理是從網絡

42、登錄在系統(tǒng)中,殺掉任何違例的進程,或是重新設置鍵盤(用 kdb_mode -a)。然而,如果你沒有網絡可用來恢復的話,發(fā)現系統(tǒng)掛起是由鍵盤鎖死造成的一點兒用也沒有。如果情況確實是這樣,你應該配置一種替代輸入設備,至少可以保證正常地重啟系統(tǒng)。對于你的計算機來說,關閉系統(tǒng)或重啟比起所謂的按“大紅鈕”要更方便一些,至少它可以免去長時間地fsck 掃描磁盤。這種替代輸入設備可以是桿或是鼠標。在上有一個桿重啟守護進程,gpm-1.10 或更新的鼠標服務器可以通過命令行選項支持類似的功能。如果鍵盤沒有鎖死,但是卻誤入“原始”模式,你kdb 包中文檔介紹的一些小技巧。我建議最好在問題出現以前就看看這些文檔,

43、否則就太晚了。另一種可能是配置 gpm-root 菜單,增添一個 “reboot”或“reset keyboard”菜單項;gpm-root 一個響應控制鼠標事件的守護進程,它用來在屏幕上顯示菜單和執(zhí)行所配置的動作。最好,你會可以按“留意安全鍵”(SAK),一個用于將系統(tǒng)恢復為可用狀態(tài)的特殊鍵。由于不是所有的實現都能用,當前 Linux 版本的默認鍵盤表中沒有為此鍵特設一項。不過你還是可以用 loadkeys 將你的鍵盤上的一個鍵為 SAK。你應該看看 drivers/char 目錄中的 SAK實現。代碼中的注釋解釋了為什么這個鍵在 Linux 2.0 中不是總能工作,這里我就不多說了。不過,

44、如果你運行版本 2.1.9 或是更新的版本,你就可以使用非常可靠地留意安全鍵了。此外,2.1.43 及后續(xù)版本內核還有一個編譯選項選擇是否打開“SysRq 魔法鍵”;我建議你看一看 drivers/char/sysrq.c 中的代碼并使用這項新技術。如果你的驅動程序真的將系統(tǒng)掛起了,而且你有不知道在哪schedule 調用,最佳的處理方法就是加一些打印消息,并將它們打印到控制臺上(通過修改console_loglevel 變量值)。在重演掛起過程時,最好將所有的磁盤都以只讀方式安裝在系統(tǒng)上。如果磁盤是只讀的或沒有安裝,就不會存在破壞文件系統(tǒng)或使其進入不一致狀態(tài)的。至少你可以避免在復位系統(tǒng)后運行

45、 fsck。另一中方法就是使用 NFS 根計算機來測試模塊。在這種情況下,由于 NFS服務器管理文件系統(tǒng)的一致性,而它又不會受你的驅動程序的影響,你可以避免任何的文件系統(tǒng)。使用調試器最后一種調試模塊的方法就是使用調試器來一步步地代碼,查看變量和機器寄存器的值。這種方法非常耗時,應該盡可能地避免。不過,某些情況下通過調試器對代碼進行細粒度的分析是非常有益的。在這里,控制內核,否則不可能一步步所說的被調試的代碼運行在內核空間除非你內核,這會使很多事情變得更加。由于控制很少用到,最后介紹這項技術。所幸的是,在當前版本的內核中可以查看和修改變量。在這一級上熟練地使用調試器需要精通gdb 命令,對匯編碼

46、有一定了解,并且有能夠將源碼與優(yōu)化后的匯編碼對應起來的能力。不幸的是,gdb 更適合與調試而不是模塊,調試模塊化的代碼需要的技術。這的技術就是 kdebug 包,它利用 gdb 的“試器后介紹 kdebug。調試”接口控制本地內核。我將在介紹普通調使用 gdbgdb 在探究系統(tǒng)行為時非常有用。啟動調試器時必須假想內核就是一個應用程序。除了指定內核文件名外,你還應該在命令行中提供內存鏡象文件的名字。典型的 gdb 調用如下所示:(代碼)第一個參數是壓縮的內核可執(zhí)行文件(在你編譯完內核后,這個文件在/usr/src/linux 目錄中)的名字。只有x86 體系結構有 zImage 文件(有時稱為

47、vmlinuz),它是一種解決el處理器實模式下只0KB 限制的一種技巧;而無論在哪個上,vmlinux 都是你所編譯的壓縮的內核。gdb 命令行的第二個參數是是內存鏡象文件的名字。與其他在/proc 下的文件類似,/proc/kcore也是在被時產生的。當 read 系統(tǒng)調用在/proc 文件系統(tǒng)執(zhí)行時,它到一個用于數據的函數上;已在“使用/proc 文件系生成而不是數據節(jié)中介紹了這個功能。系統(tǒng)用 kcore 來表示按內存鏡象文件格式的內核“可執(zhí)行文件”;由于它要表示整個內核地址空間,它是一個非常巨大的文件,對應所有的物理內存。利用 gdb,你可以通過標準 gdb 命令查看內核標量。例如,p

48、 jiffies 可以打印從系統(tǒng)啟動到當前時刻的時鐘滴答數。當你從 gdb 打印數據時,內核還在運行,不同數據項會在不同時刻有不同的數值;然而,gdb會將已經讀到的數據緩存起來。如果你再次查看 jiffies 變?yōu)榱藘?yōu)化對內存鏡象文件的量,你會得到和以前相同的值。緩存變量值防止額外的磁盤操作對普通內存鏡象文件來說是對的,但對“動態(tài)”內存鏡象文件來說就不是很方便了。解決方法是在你想刷新 gdb 緩存的時候執(zhí)行core-file /proc/kcore 命令;調試器將使用新的內存鏡象文件并廢棄舊信息。但是,讀新數據時你并不總是需要執(zhí)行 core-file 命令;gdb 以 1KB 的尺度內存鏡象文

49、件,僅僅緩存它所的若干塊。你不能用普通 gdb 做的是修改內核數據;由于調試器需要在內存鏡象前運行被調試程序,它是不會去修改內存鏡象文件的。當調試內核鏡象時,執(zhí)行run 命令會導致在執(zhí)行若干指令后導致段違例。出于這個原因,/proc/kcore 都沒有實現 write 方法。如果你用調試選項(-g)編譯了內核,結果產生的 vmlinux 比沒有用-g 選項的更適合于 gdb。不過要注意,用-g 選項編譯內核需要大量的磁盤空間支持網絡和很少幾個設備和文件系統(tǒng)的 2.0 內核在 PC 上需要 11KB。不過不管怎樣,你都可以生成 zImage 文件并用它來其他系統(tǒng):在生成可啟動鏡象時由于選項-g

50、而加入的調試信息最終都被去掉了。如果我有足夠的磁盤空間,我會一致打開-g 選項的。在非 PC 計算機上則有不同的方法。在 Alpha 上,make boot 會在生成可啟動鏡象前將調試信息去掉,所以你最終會獲得 vmlinux 和 vmlinux.gz 兩個文件。gdb 可以使用前者,但你只能用后者啟動。在Sparc 上,默認情況下內核(至少是 2.0 內核)不會被去掉調試信息,所以你需要在將其傳遞給 silo(Sparc 的內核加載器)前將調試信息去掉,這樣才能啟動。由,無論 milo(Alpha 的內核加載器)還是 silo 都不能啟動未去掉調試信息的內于尺寸核。當你用-g 選項編譯內核并

51、且用 vmlinux 和/proc/kcore 一起使用調試器,gdb 可以返回很多有關內核結構的信息。例如,你可以使用類似于這樣令, p *module_list , p*module_list-next 和 p *chrdevs4-fops 等顯示這些結構的內容。如果你手頭有內核和源碼的話,這些探測命令是非常有用的。表另一個 gdb 可以在當前內核上執(zhí)行的有用任務是,通過 disassemble 命令(它可以縮寫)或是“檢查指令”(x/i)命令反匯編函數。disassemble 命令的參數可以是函數名或是內存區(qū)范圍,而 x/i 則使用一個內存地址做為參數,也可以用符號名。例如,你可以用 x

52、/20i 反匯編20 條指令。注意,你不能反匯編一個模塊的函數,這是因為調試器處理 vmlinux,它并不知道你的模塊的信息。如果你試圖用模塊的地址反匯編代碼,gdb 很有可能會“不能x 處的內存(Cannot acs memory atx)”。基于同樣的原因,你不查看屬于模塊的數據項。如果你知道你的變量的地址,你可以從/dev/mem 中讀出它的值,但很難弄明白從系統(tǒng)內存中分解出的數據是什么含義。如果你需要反匯編模塊函數,你最好對用 objdump 工具處理你的模塊文件。很不幸,該工具只能對磁盤上的文件進行處理,而不能對運行中的模塊進行處理;因此,objdump 中給出的地址都是重定位的地址

53、,與模塊的運行環(huán)境無關。如你所見,當你的目的是查看內核的運行情況時,gdb 是一個非常有用的工具,但它缺少某些功能,最重要的一些功能就是修改內核項和模塊的功能。這些空白將由 kdebug 包填補。使用 kdebug你可用從一般的 FTP 站點下的 pcm/extras 目錄下拿到 kdebug,但是如果你想確保拿到的的版本,你最好到 /pub/pcm關系,但是這兩個包是同一個作者寫的。/extras/去找。該工具與 pcm是沒kdebug 是一個使用 gdb“調試”接口與內核通信的小工具。使用時首先向內核加載一個模塊,調試器通過/dev/kdebug內核數據。gdb 將該設備當成一個與被調試“應用”通信的串口設備,但它僅僅是一個用于內核空間的通信通道。由于模塊本身運行在內核空間,它可以看到普通調試器無法的內核空間地址。正如你所猜想到的,模塊是一個字符設備驅動程序,并且使用了主設備號動態(tài)分配技術。kdebug 的優(yōu)點在于,你無需打補丁或重新編譯:無論是內核還是調試器都無需修改。你所需要做的就是編譯和安裝包,然后調用 kgdb,kgdb 是一個完成某些配置并調用 gdb,通過新接口內核結構的程序。但是,即便是 kdebug 也沒有提供單步內核代碼和設置斷點

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論