1.調(diào)試技術(shù) 內(nèi)核編程帶來了它自己的,,獨(dú)特的調(diào)試挑戰(zhàn)。內(nèi)核代碼不能簡單地在調(diào)試器中執(zhí)行,,也不能被簡單地跟蹤,,因?yàn)樗且唤M不與特定進(jìn)程相關(guān)的功能。內(nèi)核代碼的錯(cuò)誤非常難重現(xiàn)并且可能導(dǎo)致整個(gè)系統(tǒng)崩潰,,因此破壞很多用來發(fā)現(xiàn)它們的證據(jù),。 本章將介紹在如此惱人的情況下你可以用來監(jiān)視內(nèi)核代碼和跟蹤錯(cuò)誤的技術(shù)。 1.1.內(nèi)核中的調(diào)試支持 在第二章中,,我們建議你編譯和安裝你自己的內(nèi)核,,而不是運(yùn)行你所使用的發(fā)行版中的原始內(nèi)核。運(yùn)行你自己的內(nèi)核的最有力理由是內(nèi)核開發(fā)者已經(jīng)在內(nèi)核中構(gòu)建了很多調(diào)試特性,。這些特性會(huì)創(chuàng)建額外的輸出并使系統(tǒng)運(yùn)行變慢,,因此它們?cè)诮?jīng)銷商發(fā)行的內(nèi)核中沒有被激活。但是,,作為一個(gè)內(nèi)核開發(fā)者,,你有不同的優(yōu)先權(quán)并樂意地接受(最小的)內(nèi)核調(diào)試支持帶來的額外開銷,。 這里,我們列出應(yīng)該在用于開發(fā)的內(nèi)核中激活的配置選項(xiàng),。除非特別指明,,否則,無論你使用的是你喜歡的任何內(nèi)核配置工具,,所有的這些選項(xiàng)都可以在“kernel hacking”菜單下找到,。注意有些選項(xiàng)并不被所有的體系結(jié)構(gòu)支持。 CONFIG_DEBUG_KERNEL 該選項(xiàng)只是使其它的調(diào)試選項(xiàng)可用,;它必須被打開,,但是,它自己不打開任何特性,。 CONFIG_DEBUG_SLAB 這個(gè)選項(xiàng)打開對(duì)許多種類型的內(nèi)核內(nèi)存分配函數(shù)的檢查;激活這些檢查,,就可能發(fā)現(xiàn)許多內(nèi)存越界和未初始化錯(cuò)誤,。每一個(gè)分配的內(nèi)存字節(jié)在傳遞給調(diào)用者之前設(shè)置為0xa5,釋放之后設(shè)置為0x6b,。如果你曾經(jīng)看到這些“毒物”中的任意一個(gè)重復(fù)出現(xiàn)在你的驅(qū)動(dòng)程序的輸出中(或者經(jīng)常出現(xiàn)在一個(gè)oops列表中),,你就能確切地知道該去找尋什么樣的錯(cuò)誤。當(dāng)調(diào)試選項(xiàng)被激活的時(shí)候,,內(nèi)核在分配內(nèi)存對(duì)象之前和之后都在其中放置特殊的監(jiān)視值,;如果這些值曾經(jīng)被修改,內(nèi)核就知道有內(nèi)存分配已經(jīng)越界,,它就會(huì)不客氣地提出警告,。許多其它不顯眼的錯(cuò)誤檢查也被激活。 CONFIG_DEBUG_PAGEALLOC 頁面被釋放時(shí)是整個(gè)的從內(nèi)核地址空間中移除的,。該選項(xiàng)顯著地降低了速度,,但它也能迅速指出特定類型的內(nèi)存崩潰錯(cuò)誤。 CONFIG_DEBUG_SPINLOCK 激活該選項(xiàng),,內(nèi)核捕捉對(duì)未初始化自旋鎖的操作和各種其它錯(cuò)誤(比如解鎖一個(gè)鎖兩次)。 CONFIG_DEBUG_SPINLOCK_SLEEP 該選項(xiàng)能執(zhí)行對(duì)持有自旋鎖的時(shí)候試圖睡眠的檢查,。事實(shí)上,,如果你調(diào)用一個(gè)可能潛在地睡眠的函數(shù),它會(huì)提出警告,,即使調(diào)用的函數(shù)不可能睡眠,。 CONFIG_INIT_DEBUG 有__init(或__initdata)標(biāo)記的項(xiàng)在系統(tǒng)初始化或模塊加載時(shí)被丟棄。該選項(xiàng)使你能對(duì)在初始化完成之后試圖訪問初始化時(shí)的內(nèi)存的代碼進(jìn)行檢查,。 CONFIG_DEBUG_INFO 該選項(xiàng)使得內(nèi)核編譯時(shí)包含完整的調(diào)試信息,。如果你想使用gdb來調(diào)試內(nèi)核,你就需要這些信息,。如果你計(jì)劃使用gdb,,你可能也想激活CONFIG_FRAME_POINTER選項(xiàng),。 CONFIG_MAGIC_SYSRQ 激活“神奇的系統(tǒng)請(qǐng)求鍵”。我們將在本章隨后的“系統(tǒng)掛起”一節(jié)中看到這些鍵,。 CONFIG_DEBUG_STACKOVERFLOW CONFIG_DEBUG_STACK_USAGE 這些選項(xiàng)可以幫助追蹤內(nèi)核的棧溢出,。棧溢出的確定跡象是一個(gè)沒有任何合理的回溯線索,。第一個(gè)選項(xiàng)給內(nèi)核添加明確的溢出檢查,;第二個(gè)讓內(nèi)核監(jiān)視棧使用并通過神奇的系統(tǒng)請(qǐng)求鍵提供一些可用的數(shù)據(jù)。 CONFIG_KALLSYMS 該選項(xiàng)(位于“General setup/Standard features”)使得內(nèi)核符號(hào)信息編譯到內(nèi)核中,;它默認(rèn)是激活的,。符號(hào)信息用來調(diào)試上下文;沒有它,,oops列表僅能讓你以十六進(jìn)制的方式回溯內(nèi)核,,這種方法沒有什么用處。 CONFIG_IKCONFIG CONFIG_IKCONFIG_PROC 這些選項(xiàng)(在“General setup”菜單下)使完整的內(nèi)核配置狀態(tài)編譯進(jìn)內(nèi)核并通過/proc可用,。大多數(shù)的內(nèi)核開發(fā)者了解他們使用的配置,,所以不需要這個(gè)選項(xiàng)(它使內(nèi)核變得更大)。如果你嘗試調(diào)試其它人編譯的內(nèi)核中的問題,,它會(huì)非常有幫助,。 CONFIG_ACPI_DEBUG 位于“Power management/ACPI”中。該選項(xiàng)打開詳細(xì)的ACPI(Advanced Configuration and Power Interface)調(diào)試信息,,當(dāng)你懷疑出現(xiàn)ACPI相關(guān)的問題時(shí)將非常有用,。 CONFIG_DEBUG_DRIVER 位于“Device drivers”。打開驅(qū)動(dòng)程序核心的調(diào)試信息,,跟蹤底層支持代碼產(chǎn)生的問題時(shí)很有用,。我們將在第14章學(xué)習(xí)驅(qū)動(dòng)程序核心(driver core)。 CONFIG_SCSI_CONSTANTS 該選項(xiàng)位于“Device drivers/SCSI device support”,。它可以建立詳細(xì)的SCSI錯(cuò)誤信息,。如果你編寫一個(gè)SCSI驅(qū)動(dòng)程序,你可能想激活這個(gè)選項(xiàng),。 CONFIG_INPUT_EVBUG 該選項(xiàng)(位于“Device drivers/Input device support”)打開對(duì)輸入事件的詳細(xì)記錄,。如果你在為一個(gè)輸入設(shè)備編寫驅(qū)動(dòng)程序,,這個(gè)選項(xiàng)可能非常有幫助。這個(gè)選項(xiàng)有潛在的安全問題,,因?yàn)樗涗浤爿斎氲乃行畔?,包括你的密碼。 CONFIG_PROFILING 該選項(xiàng)在“Profiling support”中,。Profiling通常用于系統(tǒng)性能調(diào)節(jié),,但是它對(duì)跟蹤一些內(nèi)核掛起及相關(guān)的錯(cuò)誤也非常有用。 我們將在我們學(xué)習(xí)跟蹤內(nèi)核問題的許多方法時(shí)重新遇到上面的其中一些選項(xiàng),。但首先,,我們來看看經(jīng)典的調(diào)試技術(shù):print語句。 1.2.打印調(diào)試信息 最常用的調(diào)試技術(shù)是監(jiān)視,,在應(yīng)用程序中通過在適當(dāng)?shù)牡胤秸{(diào)用printf語句來完成,。當(dāng)你調(diào)試內(nèi)核代碼的時(shí)候,你可以使用printk實(shí)現(xiàn)相同的目標(biāo),。 1.2.1.printk 我們?cè)谇懊娴恼鹿?jié)中使用printk函數(shù)時(shí)簡單地假設(shè)它像pirntf一樣工作?,F(xiàn)在是時(shí)候來介紹一些不同之處了。 一個(gè)不同之處就是printk可以讓你根據(jù)信息的不同記錄級(jí)別或優(yōu)先級(jí)來嚴(yán)格地分類要記錄的信息,。通常用一個(gè)宏來表示記錄級(jí)別。例如,,我們已經(jīng)在一些前面的print語句中看到過的KERN_INFO,,是一個(gè)可能信息的記錄級(jí)別。表示記錄級(jí)別的宏擴(kuò)展為一個(gè)字符串,,在編譯時(shí)連結(jié)到信息文本中,;這就是為什么下面的兩個(gè)例子中在優(yōu)先級(jí)與格式化字符串之間沒有逗號(hào)的原因。這里是prink命令的兩個(gè)例子,,一條調(diào)試信息和個(gè)臨界情況信息: printk(KERN_DEBUG "Here I am: %s:%i\n", __FILE__, __LINE__); printk(KERN_CRIT "I'm trashed; giving up on %p\n", ptr); 有8種可能的記錄級(jí)別字符串,,它們定義在頭文件中;我們按照以重要性遞減的順序來列出它們: KERN_EMERG 用于緊急情況信息,,通常是那些在系統(tǒng)崩潰之前的信息,。 KERN_ALERT 一個(gè)必須被立即處理的錯(cuò)誤。 KERN_CRIT 臨界情況,,常常與嚴(yán)重的硬件或軟件失敗相關(guān),。 KERN_ERR 用于報(bào)告錯(cuò)誤情況;設(shè)備驅(qū)動(dòng)程序常常使用KERN_ERR報(bào)告一個(gè)硬件問題,。 KERN_WARNING 對(duì)一個(gè)有問題的情況提出警告,,這些情況并不對(duì)系統(tǒng)造成嚴(yán)重的問題。 KERN_NOTICE 一個(gè)普通的,,不過也有可能需要注意到的情況,。許多安全相關(guān)的情況都是在這個(gè)級(jí)別被報(bào)告的,。 KERN_INFO 提供信息的消息。很多的驅(qū)動(dòng)程序在這個(gè)級(jí)別打印它們?cè)趩?dòng)時(shí)找到的硬件的信息,。 KERN_DEBUG 用于調(diào)試信息,。 每一個(gè)字符串(宏擴(kuò)展)表示一個(gè)在尖括號(hào)中的整數(shù)。整數(shù)范圍從0到7,,更小的值表示更高的優(yōu)先級(jí),。 沒有指定優(yōu)先級(jí)的prink語句的默認(rèn)是DEFAULT_MESSAGE_LOGLEVEL,在kernel/printk.c中作為一個(gè)整型指定,。在2.6.10內(nèi)核中,,DEFAULT_MESSAGE_LOGLEVEL是KERN_WARNING,它已經(jīng)與過去的不一樣了,。 以記錄級(jí)別為基礎(chǔ),,內(nèi)核可能向當(dāng)前控制臺(tái)打印信息,它可能是一個(gè)文本模式的終端,,一個(gè)串口,,或一個(gè)并行打印機(jī)。如果優(yōu)先級(jí)比整型變量console_loglevel的小,,信息將會(huì)每次向控制臺(tái)傳遞一行(只有遇到換行符的時(shí)候信息才會(huì)被傳遞),。如果klogd和syslogd都在系統(tǒng)中運(yùn)行,內(nèi)核信息追加在/var/log/messages文件(或根據(jù)syslogd的配置來處理)中,,與console_loglevel無關(guān),。如果klogd不在運(yùn)行,除非你讀取/proc/kmsg文件(通常最簡單的方法是使用dmesg方法),,信息將不會(huì)到達(dá)用戶空間,。如果使用klogd,你要記住它不保存相同的連續(xù)的行,;它僅保存第一行,,隨后跟著它收到的重復(fù)的行的數(shù)目。 console_loglevel變量被初始化為DEFAULT_CONSOLE_LOGLEVEL并且它可以通過sys_syslog系統(tǒng)調(diào)用來修改,。一種方法是在調(diào)用klogd的時(shí)候指定-c開關(guān),,就像klogd的幫助頁說明的一樣。注意要改變當(dāng)前值,,你必須首先殺死klogd然后重新使用-c選項(xiàng)啟動(dòng)它,。另外的選擇是寫一個(gè)程序區(qū)改變控制臺(tái)的記錄級(jí)別。你可以在O'Reilly的FTP站點(diǎn)提供的源代碼文件中的找到一個(gè)這樣的程序,,它在misc-progs/setlevel.c文件中,。新的級(jí)別以一個(gè)1和8之間的整型值指定,包含1和8。如果它設(shè)置為1,,那么僅有0級(jí)別(KERN_EMERG)的信息能到達(dá)控制臺(tái),;如果設(shè)置為8,那么所有的信息,,包括調(diào)試信息,,都在控制臺(tái)顯示。 也可以通過修改/proc/sys/kernel/printk文本文件來改變控制臺(tái)的記錄級(jí)別,。這個(gè)文件包含四個(gè)整型值:當(dāng)前記錄級(jí)別,,缺乏明確記錄級(jí)別的信息的默認(rèn)級(jí)別,,允許的最小級(jí)別,,和系統(tǒng)引導(dǎo)時(shí)的默認(rèn)級(jí)別,。向文件中寫入一個(gè)值就將當(dāng)前的記錄級(jí)別改變?yōu)樵撝?;如此,你可以使所有的?nèi)核信息出現(xiàn)在控制臺(tái)上,,例如,,通過輸入: #echo 8 > /proc/sys/kernel/printk 現(xiàn)在應(yīng)該非常清楚為什么hello.c樣例代碼使用KERN_ALERT標(biāo)志了,;它可以保證信息顯示在控制臺(tái)上,。 1.2.2.重定向控制臺(tái)信息 Linux在控制臺(tái)的日志記錄策略允許你把信息發(fā)送到一個(gè)指定的虛擬控制臺(tái)(如果你的控制臺(tái)是一個(gè)文本屏幕)來提供一定的靈活性,。默認(rèn)情況下,“控制臺(tái)”就是當(dāng)前的虛擬終端,。你可以在任何控制臺(tái)設(shè)備上使用ioctl(TIOCLINUX)來選擇一個(gè)不同的虛擬終端來接收信息,。下面的setconsole程序可以用來選擇哪一個(gè)控制臺(tái)接收內(nèi)核信息;它在misc-progs目錄下并且它必須由根用戶來運(yùn)行,。 下面是整個(gè)的程序代碼,。你應(yīng)該在調(diào)用它的時(shí)候使用一個(gè)參數(shù)來指定接收信息的控制臺(tái)數(shù)目。 int main(int argc, char **argv) { /* 11 is the TIOCLINUX cmd number */ char bytes[2] = {11, 0}; if (argc == 2) bytes[1] = atoi(argv[1]); /* the chosen console */ else { fprintf(stderr, "%s: need a single arg\n", argv[0]); exit(1); } /* use stdin */ if (ioctl(STDIN_FILENO, TIOCLINUX, bytes) 1.2.3.信息是怎樣被記錄的 printk函數(shù)把信息寫入到一個(gè)__LOG_BUF_LEN字節(jié)長的循環(huán)緩沖區(qū)中:一個(gè)在配置內(nèi)核的時(shí)候選擇的介于4KB到1MB之間的值,。函數(shù)接著喚醒等待信息的所有進(jìn)程,即那些在syslog系統(tǒng)調(diào)用或讀取/proc/kmsg時(shí)睡眠的進(jìn)程,。這兩個(gè)記錄引擎幾乎是相同的,,但是請(qǐng)注意從/proc/kmsg讀取信息時(shí)會(huì)消耗記錄緩沖區(qū)中的數(shù)據(jù),然而syslog系統(tǒng)調(diào)用能隨意地返回記錄數(shù)據(jù)并把它留給其它進(jìn)程,。一般來說,,讀取/proc文件更簡單并且是klogd的默認(rèn)行為。dmesg命令可以用來查看緩沖區(qū)的內(nèi)容而不沖洗(清除)它的內(nèi)容,;事實(shí)上,,該命令將緩沖區(qū)的所有內(nèi)容返回到stdout(標(biāo)準(zhǔn)輸出)中,不論它是否已經(jīng)被讀取過,。 如果你碰巧手工讀取內(nèi)核信息,,停止klogd后,你會(huì)發(fā)現(xiàn)/proc文件就像一個(gè)FIFO,因?yàn)樽x者阻塞,,等待更多的數(shù)據(jù),。很明顯,如果klogd或其它的進(jìn)程已經(jīng)讀取了相同的數(shù)據(jù),,你就不能使用這種方法,,因?yàn)闀?huì)發(fā)生競爭。 如果循環(huán)緩沖區(qū)被填滿,,prink繞回并且在緩沖區(qū)開始的地方開始添加新數(shù)據(jù),,覆蓋最老的數(shù)據(jù)。因此,,記錄進(jìn)程失去最老的數(shù)據(jù),。與使用循環(huán)緩沖區(qū)的優(yōu)勢相比這個(gè)問題可以忽略不計(jì)。例如,,循環(huán)緩沖區(qū)允許系統(tǒng)在沒有記錄進(jìn)程的情況下運(yùn)行,,雖然因?yàn)楦采w舊的數(shù)據(jù)浪費(fèi)了少量的內(nèi)存。Linux記錄信息的方法的另一個(gè)特點(diǎn)是prink函數(shù)可以從任何地方調(diào)用,,甚至從一個(gè)中斷處理程序,,沒有打印多少數(shù)據(jù)的限制。唯一的缺點(diǎn)就是可能丟失一些數(shù)據(jù),。 如果klogd進(jìn)程正在運(yùn)行,,它取回內(nèi)核信息并發(fā)送它們到syslogd,由syslogd來檢查/etc/syslog.conf文件以決定怎樣處理它們,。syslogd區(qū)分根據(jù)設(shè)施和優(yōu)先級(jí)來區(qū)分?jǐn)?shù)據(jù),;設(shè)施和優(yōu)先級(jí)的允許值都定義在中。內(nèi)核信息以LOG_KERN設(shè)施在一個(gè)printk中使用的相應(yīng)的優(yōu)先級(jí)被記錄(比如,,LOG_ERR對(duì)應(yīng)KERN_ERR信息),。如果klogd沒有運(yùn)行,數(shù)據(jù)存在于循環(huán)緩沖區(qū)中只到被讀取或緩沖區(qū)溢出,。 如果你想避免你的驅(qū)動(dòng)程序的監(jiān)視信息拉跨你的系統(tǒng)記錄,,你能為klogd指定-f(文件)選項(xiàng)以指示klogd在一個(gè)指定的文件中保存信息,或者定制/etc/syslog.conf文件來適應(yīng)你的需要,。還有一種可能就是采用暴力的方法:殺死klogd并在一個(gè)沒有使用的虛擬終端打印詳細(xì)的信息 ,,或在一個(gè)沒有使用的xterm上使用cat /proc/kmsg命令。 1.2.4.打開和關(guān)閉信息 在驅(qū)動(dòng)程序開發(fā)的初始階段,,prink能很好地幫助你調(diào)試和測試你的新代碼,。但是,當(dāng)你正式地發(fā)布你的驅(qū)動(dòng)程序的時(shí)候,,你必須移除,,或者至少禁止這樣的打印語句,。不辛的是,你可能會(huì)發(fā)現(xiàn)一旦你認(rèn)為你不再需要這些信息并移除它們,,你在驅(qū)動(dòng)程序中實(shí)現(xiàn)一個(gè)新功能(或者有人發(fā)現(xiàn)一個(gè)bug),,你又想重新打開至少其中的一個(gè)信息。有很多方法來解決這兩個(gè)問題,,通過全局地允許或禁止你的調(diào)試信息和打開或關(guān)閉單個(gè)的信息,。 下面展示一種編寫printk調(diào)用的方法,你可以單個(gè)地或全局地打開或關(guān)閉它們,;該技術(shù)取決于定義一個(gè)分解printk(或printf)調(diào)用的宏,,這個(gè)宏的功能為: ▪ 每一個(gè)打印語句都能通過移除或添加單個(gè)的子母到宏的名字中來允許或禁止。 ▪ 所有的信息都可以通過在編譯之前改變CFLAGS變量的值來禁止,。 ▪ 相同的打印語句能在內(nèi)核和用戶級(jí)的代碼中使用,,這樣驅(qū)動(dòng)程序和測試程序就能以相同的方式來管理額外的信息。 下面的代碼段實(shí)現(xiàn)了這些功能,,它直接來源于頭文件scull.h: #undef PDEBUG /* undef it, just in case */ #ifdef SCULL_DEBUG # ifdef __KERNEL__ /* This one if debugging is on, and kernel space */ # define PDEBUG(fmt, args...)\ printk( KERN_DEBUG "scull: " fmt, ## args) # else /* This one for user space */ # define PDEBUG(fmt, args...)\ fprintf(stderr, fmt, ## args) # endif #else # define PDEBUG(fmt, args...) /* not debugging: nothing */ #endif #undef PDEBUGG #define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ PDEBUG符號(hào)被定義或不被定義,,取決于SCULL_DEBUG是否定義,顯示信息的方式與代碼運(yùn)行的環(huán)境相關(guān):當(dāng)它在內(nèi)核中時(shí)它使用內(nèi)核調(diào)用printk,,在用戶空間時(shí)使用libc調(diào)用fprintf輸出到標(biāo)準(zhǔn)錯(cuò)誤,。PDEBUGG符號(hào)沒有什么意義;它用來“注釋”打印語句而不完全地移除它們,。 為了使你將來的處理過程簡單一些,,在你的makefile文件中添加以下的行: # Comment/uncomment the following line to # disable/enable debugging DEBUG = y # Add your debugging flag (or not) to CFLAGS ifeq ($(DEBUG), y) # "-O" is needed to expand inlines DEBFLAGS = -O -g -DSCULL_DEBUG else DEBFLAGS = -O2 endif CFLAGS += $(DEBFLAGS) 本節(jié)出現(xiàn)的宏依賴于ANSI C預(yù)處理器的gcc擴(kuò)展,該擴(kuò)展支持可變數(shù)目參數(shù)的宏,。這種gcc依賴并不是一個(gè)難題,,因?yàn)閮?nèi)核也嚴(yán)重地依賴于gcc的特性。此外,,makefile依賴于GNU的make,;再一次,內(nèi)核已經(jīng)依賴于GNU make,,所以該依賴也不是問題,。 如果你熟悉C預(yù)處理器,你可以擴(kuò)展已經(jīng)給出的定義來實(shí)現(xiàn)“調(diào)試級(jí)別”的概念,,定義不同的級(jí)別并指派一個(gè)整型值(或?yàn)檠诖a)來決定每一個(gè)級(jí)別的信息有多么詳細(xì)。 但是每個(gè)驅(qū)動(dòng)程序有它自己的特性和監(jiān)視需求,。好的編程藝術(shù)就是在靈活性和效率之間作出最好的選擇,,但我們也不能告訴你什么是最好的選擇。記住有條件的預(yù)處理代碼(代碼中的常量表達(dá)式也一樣)是在編譯時(shí)執(zhí)行的,,因此你必須通過重新編譯來打開或關(guān)閉信息,。一個(gè)可能的選擇是使用在運(yùn)行時(shí)執(zhí)行的C條件語句,這樣,就允許你在運(yùn)行時(shí)關(guān)閉和打開信息,。這是一個(gè)很好的方法,,但是每次代碼執(zhí)行時(shí)都需要額外的處理,即使信息被禁用時(shí)也會(huì)影響性能,。 本節(jié)展示的宏被證明在許多情況下都非常有用,,唯一的缺陷就是它的信息的任何改變都需要重新編譯該模塊。 1.2.5.速率限制 如果你不小心,,你會(huì)發(fā)現(xiàn)你的printk語句產(chǎn)生了成千上萬的信息,,塞滿控制臺(tái)或可能溢出系統(tǒng)的日志文件。當(dāng)使用的是慢速的控制臺(tái)設(shè)備(比如,,一個(gè)串行端口)時(shí),,過分的信息速率會(huì)拖慢系統(tǒng)速度或使系統(tǒng)變得反應(yīng)遲鈍。當(dāng)控制臺(tái)被不中斷的數(shù)據(jù)擁塞時(shí),,你很難處理并發(fā)現(xiàn)系統(tǒng)出了什么問題,。因此,你必須非常小心你要打印什么樣的信息,,特別是在驅(qū)動(dòng)程序的發(fā)布版本和一旦初始化完成后,。一般來說,產(chǎn)品的代碼中決不該在普通的操作中打印任何信息,;打印出的信息應(yīng)該指示一個(gè)值得注意的異常情況,。 另一方面,你想在你驅(qū)動(dòng)的設(shè)備停止工作之后發(fā)出一個(gè)記錄信息,。但你必須注意不要做過度的事情,。一個(gè)愚蠢的永遠(yuǎn)運(yùn)行的進(jìn)程面對(duì)錯(cuò)誤時(shí)每秒能產(chǎn)生成千上萬的重試操作;如果你的驅(qū)動(dòng)程序每次都打印“設(shè)備壞掉”的信息,,它能產(chǎn)生大量的輸出,,如果控制臺(tái)設(shè)備很慢,它有可能過多地占用CPU──沒有中斷可以用來控制終端,,即使它是一個(gè)串口或行式打印機(jī),。 很多情況下,最好的方法是設(shè)置一個(gè)標(biāo)志來說明,,“我已經(jīng)輸出過這個(gè)提示信息了”,,一旦標(biāo)志被設(shè)置,就不再輸出任何進(jìn)一步的信息,。在有些情況下,,也有偶爾發(fā)出“設(shè)備仍有錯(cuò)誤”的提示的理由。內(nèi)核提供了能在這些情況下有幫助的函數(shù): int printk_ratelimit(void); 這個(gè)函數(shù)應(yīng)該在你考慮打印一個(gè)經(jīng)常重復(fù)的信息之前調(diào)用,。如果函數(shù)返回一個(gè)非0值,,繼續(xù)打印你的信息,,否則跳過它(打印信息的語句)。因此,,典型的調(diào)用像這樣: if (printk_ratelimit()) printk(KERN_NOTICE "The printer is still on fire\n"); printk_ratelimit通過跟蹤有多少信息被發(fā)送到控制臺(tái)來完成工作,。如果輸出的信息超過一個(gè)閥值,printk_ratelimit開始返回0值以使得信息被丟棄,。 可以通過修改/proc/sys/kernel/prink_ratelimit(重新打開信息等待的妙數(shù))和/proc/sys/kernel/printk_ratelimit_burst(速率限制之前接受的信息數(shù))來定制,。 1.2.6.打印設(shè)備號(hào) 有時(shí)侯,當(dāng)從一個(gè)驅(qū)動(dòng)程序中打印信息時(shí),,你想打印與硬件結(jié)合的設(shè)備號(hào)以引起注意,。打印主次設(shè)備號(hào)并不是非常難,但是,,為了一致性,,內(nèi)核提供了一對(duì)工具宏(在中定義)來達(dá)成這個(gè)目的: int print_dev_t(char *buffer, dev_t dev); char *format_dev_t(char *buffer, dev_t dev); 兩個(gè)宏都把設(shè)備號(hào)編碼到給出的buffer中;唯一的區(qū)別是print_dev_t返回的是被打印的字符數(shù)目,,而format_dev_t返回buffer,;因此,它可以直接作為printk調(diào)用的參數(shù),,雖然必須記住printk在遇到換行符之前不會(huì)輸出,。緩沖區(qū)必須足夠大以能保存一個(gè)設(shè)備號(hào);64位的設(shè)備號(hào)在將來的內(nèi)核中是明顯可能的,,緩沖區(qū)至少需要20字節(jié)長,。 1.3.通過查詢調(diào)試 前面的一節(jié)描述了printk怎樣工作及如何使用它。我們沒有談到的是它的缺點(diǎn),。 大量使用printk能使系統(tǒng)顯著地變慢,,即使你降低console_loglevel來避免裝載控制臺(tái)設(shè)備,因?yàn)閟yslogd保持同步它的輸出文件,;因此,,每一行的輸出都導(dǎo)致一個(gè)磁盤操作。從syslogd的視角來看這是正確的實(shí)現(xiàn),。為了防止萬一系統(tǒng)在打印消息之后就崩潰,,它嘗試把所有的信息都寫到磁盤上;當(dāng)然,,你不想因?yàn)檎{(diào)試信息的原因拖慢系統(tǒng)的速度,。這個(gè)問題可以通過給/etc/syslogd.conf文件中出現(xiàn)的記錄文件加一個(gè)連字符前綴來解決 。修改配置文件的問題是在你完成調(diào)試之后你的修改還保存在配置文件中,,即使是普通的系統(tǒng)操作你也想把信息盡可能快地寫到磁盤上,。這樣的持久改變的另一種選擇是運(yùn)行一個(gè)其它程序而不是klogd(比如cat /proc/kmsg,像前邊建議的一樣),,但是這樣可能不能為普通的系統(tǒng)操作提供一個(gè)合適的環(huán)境,。 更多情況下,獲得相應(yīng)信息的最好方法當(dāng)你需要信息的時(shí)候向系統(tǒng)查詢,,而不是持續(xù)地產(chǎn)生數(shù)據(jù),。事實(shí)上,每個(gè)Unix系統(tǒng)都提供了許多工具來獲得系統(tǒng)信息:ps,,netstat,,vmstat,等等,。 驅(qū)動(dòng)開發(fā)者有一些可以用來查詢系統(tǒng)信息的技術(shù):在/proc文件系統(tǒng)中創(chuàng)建一個(gè)文件,,使用ioctl驅(qū)動(dòng)程序方法,和通過sysfs導(dǎo)出屬性,。使用sysfs需要很多驅(qū)動(dòng)程序模式的背景知識(shí),。它在第14章討論。 1.3.1.使用/proc文件系統(tǒng) /proc文件系統(tǒng)是一個(gè)特殊的,,軟件創(chuàng)建的文件系統(tǒng),,內(nèi)核使用它來向外界導(dǎo)出信息。每一個(gè)/proc下的文件都依賴于一個(gè)當(dāng)文件被讀取的時(shí)候匆忙產(chǎn)生文件內(nèi)容的內(nèi)核函數(shù),。我們已經(jīng)看到過一些這樣的文件在運(yùn)作,;例如/proc/modules,總是返回當(dāng)前加載的模塊列表,。 /proc在Linux系統(tǒng)中使用非常多?,F(xiàn)在的Linux發(fā)布版里的許多工具,比如ps,,top,,和uptime,就是從/proc中獲取它們需要的信息,。一些設(shè)備驅(qū)動(dòng)程序也通過/proc導(dǎo)出信息,,當(dāng)然你也可以這樣做。/proc文件系統(tǒng)是動(dòng)態(tài)的,,所以你的模塊可以在任何時(shí)候添加和移除條目,。 完整功能的/proc條目非常的復(fù)雜;其中,,它們被寫入和讀取,。但是大多數(shù)時(shí)候,/proc條目都是只讀文件,。本節(jié)只關(guān)注簡單的只讀情形,。那些對(duì)實(shí)現(xiàn)更復(fù)雜的/proc條目感興趣的人可以通過學(xué)習(xí)這里來打基礎(chǔ);然后可以向內(nèi)核源代碼查閱完整的信息,。 但是在開始之前,,我們并不鼓勵(lì)你在/proc下添加文件,。/proc文件系統(tǒng)被內(nèi)核開發(fā)者認(rèn)為有點(diǎn)混亂,它已經(jīng)超越了它最初的目的(提供系統(tǒng)中運(yùn)行的進(jìn)程的信息),。我們建議你在新編寫的代碼中通過sysfs來提供信息,。使用sysfs需要理解Linux設(shè)備模式,但是我們要等到第14章才能接觸到它,。其實(shí),,在/proc下創(chuàng)建文件是很簡單的,并且它們非常適合用于調(diào)試,,因此我們?cè)谶@里學(xué)習(xí)它,。 1.3.1.1.在/proc中實(shí)現(xiàn)文件 使用/proc的所有模塊都必須包含文件以定義適當(dāng)?shù)暮瘮?shù)。 創(chuàng)建一個(gè)只讀的/proc文件,,你的驅(qū)動(dòng)程序必須實(shí)現(xiàn)一個(gè)當(dāng)文件被讀取時(shí)產(chǎn)生數(shù)據(jù)的函數(shù),。當(dāng)一些進(jìn)程讀取這個(gè)文件時(shí)(使用read系統(tǒng)調(diào)用),該請(qǐng)求借助于到達(dá)你的模塊,。我們首先來看一下這個(gè)函數(shù),,在本節(jié)的后面再來了解注冊(cè)接口。 當(dāng)進(jìn)程從你的/proc文件中讀取數(shù)據(jù)時(shí),,內(nèi)核分配一頁內(nèi)存(比如,,PAGE_SIZE字節(jié))來保存驅(qū)動(dòng)程序?qū)懭氲谋环祷赜脩艨臻g的數(shù)據(jù)。這個(gè)緩沖區(qū)被傳遞到你的函數(shù),,該函數(shù)是一個(gè)名為read_proc的方法,。 int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data); page指針是你寫入數(shù)據(jù)的緩沖區(qū);函數(shù)使用start來表明有趣的信息被寫在page的何處(后面有更詳細(xì)的討論),;offset和count與read方法的意義是一樣的,。eof參數(shù)指向一個(gè)整型,驅(qū)動(dòng)程序必須設(shè)置它來表明沒有更多的數(shù)據(jù)返回,,data是驅(qū)動(dòng)程序特定的數(shù)據(jù)指針,,你可以用它來作為內(nèi)部簿記。 這個(gè)函數(shù)應(yīng)該返回實(shí)際寫入page緩沖區(qū)的字節(jié)數(shù)目,,就象read方法針對(duì)其它文件那樣,。另外的輸出值是*eof和*start。eof是一個(gè)簡單的標(biāo)志,,但是start值的使用稍微更復(fù)雜一些,;它的目的是幫助實(shí)現(xiàn)大/proc文件(大于一頁)。 start參數(shù)多少有些非傳統(tǒng)的使用,。它的目的是表明哪里(頁面內(nèi))可以找到返回給用戶的數(shù)據(jù),。當(dāng)你的proc_read方法被調(diào)用時(shí),*start是NULL值。如果你保持它為NULL,,內(nèi)核假設(shè)數(shù)據(jù)被寫入到頁的offset為0的地方,;換句話來說,它假設(shè)一個(gè)簡單的proc_read版本,,把整個(gè)虛擬文件的內(nèi)容放置在頁中而不管offset參數(shù),。如果你設(shè)置*start為一個(gè)非NULL值,內(nèi)核假設(shè)*start指向的數(shù)據(jù)已經(jīng)把offset計(jì)算在內(nèi)并準(zhǔn)備直接返回給用戶,。一般說來,簡單的proc_read方法忽略start,,返回少量的數(shù)據(jù),。更復(fù)雜一些的方法設(shè)置*start為page并只在請(qǐng)求的數(shù)據(jù)的位移的開始處寫入數(shù)據(jù)。 start也解決了/proc文件的另一個(gè)長久的主要問題,。有的時(shí)候內(nèi)核數(shù)據(jù)結(jié)構(gòu)的ASCII表示被連續(xù)的read調(diào)用改變,,因此讀取數(shù)據(jù)的進(jìn)程必須找到該調(diào)用和下一個(gè)調(diào)用之間的不一致性。如果*start設(shè)置為一個(gè)小的整型值,,調(diào)用者使用它來增加filp->f_pos,,而不管返回的數(shù)據(jù)的數(shù)量。例如,,如果你的read_proc函數(shù)返回一個(gè)大型的結(jié)構(gòu)數(shù)組,,并在第一次調(diào)用時(shí)返回5個(gè)這樣的數(shù)據(jù)結(jié)構(gòu),*start應(yīng)該被設(shè)置為5,。下一個(gè)調(diào)用在位移之上提供相同的值,;驅(qū)動(dòng)程序就知道從數(shù)組中的第六個(gè)數(shù)據(jù)結(jié)構(gòu)返回值。這是一個(gè)被公認(rèn)的它的作者“hack”,,你可以在fs/proc/generic.c中看到它,。 注意有一個(gè)更好的方法來實(shí)現(xiàn)大/proc文件;它被稱為seq_file,,我們不久就會(huì)討論它,。首先,是時(shí)候給出一個(gè)例子了,。這是一個(gè)簡單的(有些丑陋的)scull設(shè)備的read_proc實(shí)現(xiàn): int scull_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data) { int i, j, len = 0; int limit = count - 80; /* Don't print more than this */ for (i = 0; i data; if (down_interruptible(&d->sem)) return -ERESTARTSYS; len += sprintf(buf+len, "\nDevice %i: qset %i, q %i, sz %li\n", i, d->qset, d->quantum, d->size); /* dump only the last item */ if (qs->data && !qs->next) for (j = 0; j qset; j++) { if (qs->data[j]) len += sprintf(buf + len, " %4i: %8p\n", j, qs->data[j]); } up(&scull_devices.sem); } *eof = 1; return len; } 這是相當(dāng)?shù)湫偷膔ead_proc實(shí)現(xiàn),。它假設(shè)從不需要?jiǎng)?chuàng)建多于一頁的數(shù)據(jù),因此忽略start和offset的值,。為了以防萬一,,你必須小心不要緩沖區(qū)越界。 1.3.1.2.舊的接口 如果你通讀內(nèi)核代碼,,你會(huì)遇到用舊的接口實(shí)現(xiàn)/proc文件的代碼: int (*get_info)(char *page, char **start, off_t offset, int count); 所有的參數(shù)都與read_proc中的參數(shù)有相同的意義和作用,,但是它少了eof和data參數(shù)。這個(gè)接口仍然被支持,,不過將來會(huì)被消除,;新的代碼應(yīng)該改為使用read_proc接口,。 1.3.1.3.創(chuàng)建你的/proc文件 一旦你定義了一個(gè)read_proc函數(shù),必須把它與/proc層次結(jié)構(gòu)下的一個(gè)條目相連接,??梢酝ㄟ^調(diào)用create_proc_read_entry來完成: struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t mode, struct proc_dir_entry *base, read_proc_t *read_proc, void *data); 其中,name是要?jiǎng)?chuàng)建的文件名,,mode是文件的保護(hù)掩碼(傳遞給它0值時(shí)使用系統(tǒng)的默認(rèn)值),。base表明文件將在哪個(gè)目錄下被創(chuàng)建(如果base為NULL,文件將在/proc根目錄下創(chuàng)建),,read_proc是實(shí)現(xiàn)文件的read_proc函數(shù),,data被內(nèi)核忽略(但它傳遞給read_proc)。下面是scull中使得/proc函數(shù)對(duì)/proc/scullmem可用的調(diào)用: create_proc_read_entry("scullmem", 0 /* default mode */, NULL /* parent dir */, scull_read_procmem, NULL /* client data */); 其中,,我們?cè)?proc下直接創(chuàng)建了一個(gè)名為scullmem的文件,,使用默認(rèn)的全局可讀的保護(hù)法則。 目錄條目指針能在/proc下創(chuàng)建完整的目錄層次,。但是要注意,,將一個(gè)條目放在以條目名字的一部分命名的目錄下更為簡單──只要目錄本身已經(jīng)存在。例如,,一個(gè)(常常被忽略)常用的協(xié)定是與設(shè)備驅(qū)動(dòng)程序相關(guān)的條目應(yīng)該放在driver/子目錄下,;scull應(yīng)該簡單地把它的條目放在該目錄下并把它的proc文件的名字命名為driver/scullmem。 /proc中的條目當(dāng)然應(yīng)該在模塊被卸載的模塊時(shí)候被移除,。remove_proc_entry函數(shù)可以用來撤消create_proc_read_entry已經(jīng)完成的功能: remove_proc_entry("scullmem", NULL /* parent dir */ 刪除條目失敗會(huì)導(dǎo)致時(shí)間的浪費(fèi),,或者如果你的模塊已經(jīng)被卸載,內(nèi)核就可能崩潰,。 如果你使用/proc文件,,你必須記住它實(shí)現(xiàn)上的一些缺點(diǎn)──現(xiàn)在不鼓勵(lì)使用它你就不必覺得奇怪了。 最重要的問題出現(xiàn)在移除/proc條目的時(shí)候,??赡馨l(fā)生的情況是移除的時(shí)候文件正在被使用,因?yàn)?proc條目沒有相應(yīng)的擁有者,,因此使用它們不會(huì)作用于模塊的引用計(jì)數(shù),。比如,這個(gè)問題可以通過在移除模塊之前運(yùn)行sleep 100 1.3.1.4.seq_file接口 就像我們上面提到的一樣,,在/proc文件下實(shí)現(xiàn)一個(gè)大的文件有點(diǎn)笨拙,。久而久之,/proc方法因?yàn)檩敵龅男畔⒆兊迷絹碓酱?,和?shí)現(xiàn)上的諸多問題而變得臭名昭著,。seq_file接口作為清除/proc代碼并讓內(nèi)核開發(fā)人員的生活變得簡單的方法被添加。該接口提供了一些簡單的函數(shù)集來實(shí)現(xiàn)大容量的內(nèi)核虛擬文件。 seq_file接口假設(shè)你創(chuàng)建一個(gè)必須返回用戶空間的需要訪問一個(gè)系列中的單個(gè)條目的虛擬文件,。使用seq_file,,你必須創(chuàng)建一個(gè)可以在順序中建立位置,向前移動(dòng)并輸出系列中的一個(gè)條目的簡單“迭代器”對(duì)象,。聽起來挺復(fù)雜,,但事實(shí)上,過程相當(dāng)簡單,。我們將一步步學(xué)習(xí)scull驅(qū)動(dòng)程序中創(chuàng)建/proc文件的方法來演示它是怎么做的,。 第一步,不可避免地,,是包含文件,。然后你必須創(chuàng)建四個(gè)迭代器的方法,它們是start,,next,stop和show,。 start方法總是第一個(gè)被調(diào)用,。該函數(shù)的原型為: void *start(struct seq_file *sfile, loff_t *pos); sfile參數(shù)幾乎總是可以被忽略。pos是一個(gè)表明從哪個(gè)位置開始讀取的整型值,。對(duì)位置的解釋完全由實(shí)現(xiàn)決定,;它不應(yīng)該是一個(gè)文件中的字節(jié)位置。因?yàn)閟eq_file典型的實(shí)現(xiàn)是遍歷一個(gè)系列中感興趣的條目,,位置通常被解釋為一個(gè)指向系列中的下一個(gè)條目的光標(biāo),。scull驅(qū)動(dòng)程序把每個(gè)設(shè)備解釋為系列中的一個(gè)條目,所以pos只是一個(gè)scull_devices數(shù)組中的簡單索引,。因此,,在scull中使用的start方法為: static void *scull_seq_start(struct seq_file *s, loff_t *pos) { if (*pos >= scull_nr_devs) return NULL; /* No more to read */ return scull_devices + *pos; } 如果返回值不為NULL,它是一個(gè)只能被迭代器實(shí)現(xiàn)使用的私有值,。 next函數(shù)應(yīng)該把迭代器移動(dòng)到下一個(gè)位置,,如果系列中沒有余下的條目就返回NULL。該方法的原型為: void *next(struct seq_file *sfile, void *v, loff_t *pos); 其中,,v是一個(gè)從上一個(gè)start或next調(diào)用返回的迭代器,,pos是文件中的當(dāng)前位置。next應(yīng)該遞增被pos指向的值,;根據(jù)你的迭代器的工作情況,你可能(雖然可能不會(huì))想遞增pos不止1個(gè)位置,。下面是scull的實(shí)現(xiàn)方法: static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos) { (*pos)++; if (*pos >= scull_nr_devs) return NULL; return scull_devices + *pos; } 當(dāng)內(nèi)核完成迭代器相關(guān)的工作后,,它調(diào)用stop來清除: void stop(struct seq_file *sfile, void *v); scull實(shí)現(xiàn)中沒有清除工作,,所以它的stop方法是空的,。 值得注意的是,,seq_file代碼在設(shè)計(jì)上是在調(diào)用start和stop之間不可睡眠或執(zhí)行其它的非原子操作任務(wù)的,。有一天你肯定會(huì)看到在調(diào)用start之后立即調(diào)用stop的情況,。因此,你的start方法獲得一個(gè)信號(hào)量或自旋鎖是安全的。因?yàn)槟愕钠渌膕eq_file方法是原子的,整個(gè)系列的調(diào)用都是原子的。(如果你還不能理解這一段的內(nèi)容,在你讀了下一章之后再回來讀它)。 在這些調(diào)用中,,內(nèi)核調(diào)用show方法來實(shí)際輸出一些用戶空間感興趣的信息,。該方法的原型是: int show(struct seq_file *sfile, void *v); 該方法輸出系列中由迭代器v標(biāo)識(shí)的條目,。但是,,它不能使用printk,;有用于seq_file輸出的特殊函數(shù)集: int seq_printf(struct seq_file *sfile, const char *fmt, ...); 這是printf函數(shù)的seq_file實(shí)現(xiàn),;它使用常用的格式化字符串和附加的參數(shù)值,。而且你必須給它傳送傳遞給show函數(shù)的seq_file結(jié)構(gòu),。如果seq_printf返回一個(gè)非0值,,就意味著緩沖區(qū)已滿,并且輸出被忽略,。但是大多數(shù)的實(shí)現(xiàn)忽略返回值,。 int seq_putc(struct seq_file *sfile, char c); int seq_puts(struct seq_file *sfile, const char *s); 它們和用戶空間的putc與puts函數(shù)是等價(jià)的。 int seq_escape(struct seq_file *m, const char *s, const char *esc); 該函數(shù)等價(jià)于seq_puts函數(shù),除了在s中的字符如果也在esc中的話就以八進(jìn)制的格式打印這些字符。esc的常用值是“ \t\n\\”,,以防止嵌入的空白字符擾亂輸出并使shell腳本的疑惑,。 int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc); 該函數(shù)可用于輸出與給定的目錄項(xiàng)相應(yīng)的文件名。它在設(shè)備驅(qū)動(dòng)程序中不太可能有用,;我們只是為了完整才在這里包含它,。 回到我們的例子,;scull中使用的show方法為: static int scull_seq_show(struct seq_file *s, void *v) { struct scull_dev *dev = (struct scull_dev *) v; struct scull_qset *d; int i; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n", (int) (dev - scull_devices), dev->qset, dev->quantum, dev-size); for (d = dev->data; d; d = d->next) { /* scan the list */ seq_printf(s, "item at %p, qset at %p\n", d, d->data); /* dump only the last item */ if (d->data && !d->next) for (i = 0; i qset; i++) { if (d->data) seq_printf(s, "%4i: %8p\n", i, d->data); } } up(&dev->sem); return 0; } 其中,,我們最后來解釋我們的“迭代器”值,,它是一個(gè)簡單的指向scull_dev的結(jié)構(gòu)。 現(xiàn)在我們有了迭代器操作的全集,,scull必須將它們打包并連接到/proc中的文件,。第一步是填充一個(gè)seq_operations結(jié)構(gòu): static struct seq_operations scull_seq_ops = { .start = scull_seq_start, .next = scull_seq_next, .stop = scull_seq_stop, .show = scull_seq_show }; 創(chuàng)建好上面的結(jié)構(gòu)后,我們必須創(chuàng)建一個(gè)內(nèi)核可以理解的文件實(shí)現(xiàn)。我們沒有使用前邊描述的read_proc方法;使用seq_file時(shí),,最好從更底層次上來連接/proc。這意味著創(chuàng)建一個(gè)file_operations結(jié)構(gòu)(是的,,它和字符設(shè)備驅(qū)動(dòng)程序使用的結(jié)構(gòu)是相同的)來實(shí)現(xiàn)內(nèi)核處理讀和尋址文件必須的所有操作,。幸運(yùn)的是,,這個(gè)工作很簡單。第一步是創(chuàng)建一個(gè)open方法來連接文件到seq_file操作: static int scull_proc_open(struct inode *inode, struct file *file) { return seq_open(file, &scull_seq_ops); } seq_open調(diào)用使用我們上面定義的順序操作來連接file結(jié)構(gòu),。open是我們必須自己實(shí)現(xiàn)的文件方法,,因此現(xiàn)在我們可以建立我們的file_operations結(jié)構(gòu)了: static struct file_operations scull_proc_ops = { .owner = THIS_MODULE, .open = scull_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; 這里我們指定了我們自己的open方法,但是使用的其它函數(shù)不便,,seq_read,,seq_lseek,和seq_release,。 最后的一步是在/proc中創(chuàng)建實(shí)際的文件: entry = create_proc_entry("scullseq", 0, NULL); if (entry) entry->proc_fops = &scull_proc_ops; 我們使用底層的create_proc_entry,,而不是使用create_proc_read_entry,create_proc_entry的原型如下: struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent); 參數(shù)和create_proc_read_entry中相應(yīng)的參數(shù)相同:文件名字,,文件的保護(hù),,和它的父目錄。 使用上面的代碼,,scull就有了一個(gè)與前面的/proc條目非常相似的新的條目,。但是它更出眾,因?yàn)樗还茌敵鲇卸啻蠖寄芄ぷ?,它正確地處理尋址,,并且它從整體上來說更容易閱讀和維護(hù)。我們建議你在實(shí)現(xiàn)一個(gè)輸出包含不止小數(shù)目的行的文件中使用seq_file。 1.3.2.ioctl方法 ioctl是一個(gè)作用于文件描述符的系統(tǒng)調(diào)用,,我們?cè)诘谝徽轮忻枋鲞^怎么使用它,;它接收一個(gè)表明要執(zhí)行的命令的數(shù)字和(可選地)另一個(gè)參數(shù),,通常是一個(gè)指針,。作為使用/proc文件系統(tǒng)的另外一種選擇,你可以實(shí)現(xiàn)一些為調(diào)試定制的ioctl命令,。這些命令可以從驅(qū)動(dòng)程序拷貝相關(guān)的數(shù)據(jù)結(jié)構(gòu)到用戶空間,,你就可以在用戶空間來檢查它們。 ioctl的這種獲取信息的使用方法比使用/proc稍微困難一些,,因?yàn)槟阈枰硪粋€(gè)程序來調(diào)用ioctl并顯示結(jié)果,。這個(gè)程序被編寫,編譯,,并與你要測試的模塊保持同步,。從另一方面來說,這時(shí)的驅(qū)動(dòng)方代碼將比需要實(shí)現(xiàn)/proc文件的驅(qū)動(dòng)代碼更加簡單,。 ioctl是獲取信息的最好方式已經(jīng)有好長一段時(shí)間了,,因?yàn)樗茸x取/proc更加快速。如果在數(shù)據(jù)寫到屏幕之前必須對(duì)它進(jìn)行處理,,以二進(jìn)制形式獲取數(shù)據(jù)比讀取一個(gè)文本文件更加高效,。此外,ioctl不需要把小于一頁的數(shù)據(jù)分段,。 ioctl的另一個(gè)有趣的優(yōu)點(diǎn)是即使調(diào)試被禁用后獲取信息的命令也能留在驅(qū)動(dòng)程序中,。與/proc文件不同的是,/proc文件是對(duì)查看該目錄的所有人都可見的(好多人可能會(huì)驚奇“這些奇怪的文件是做什么用的”),,未公開的ioctl命令可能不容易被人注意到,。此外,當(dāng)有驅(qū)動(dòng)程序有怪異的情況發(fā)生時(shí),,它仍然在那里,。它們唯一的缺點(diǎn)是模塊可能輕微的更大一些。 1.4.通過監(jiān)視調(diào)試 有的時(shí)候一些小問題可以通過監(jiān)視用戶空間的應(yīng)用程序來跟蹤,。監(jiān)視程序?qū)?chuàng)建一個(gè)可靠性的(驅(qū)動(dòng)程序正確工作)驅(qū)動(dòng)程序也有幫助,。例如,我們?cè)诓榭磖ead實(shí)現(xiàn)怎樣應(yīng)答對(duì)不同數(shù)量數(shù)據(jù)的請(qǐng)求后,,能對(duì)我們的設(shè)備驅(qū)動(dòng)程序更加有把握,。 有很多監(jiān)視用戶空間程序工作的不同方法。你可以使用一個(gè)調(diào)試器單步執(zhí)行它的函數(shù),,添加打印語句,,或在strace下面運(yùn)行該程序。這里我們只討論最后一種技術(shù),它的真實(shí)目的是檢查內(nèi)核代碼,,所以非常有趣,。 strace命令是一個(gè)強(qiáng)大的工具,它顯示用戶空間程序的所有系統(tǒng)調(diào)用,。不僅顯示這些調(diào)用,,它還以符號(hào)化的形式顯示調(diào)用的參數(shù)和它們的返回值。如果系統(tǒng)調(diào)用失敗,,錯(cuò)誤的符號(hào)值(比如,,ENOMEM)和相應(yīng)的字符串(內(nèi)存不足)都被顯示。strace有很多命令行選項(xiàng),;最有用的選項(xiàng)是-t選項(xiàng),,它顯示每個(gè)系統(tǒng)調(diào)用開始執(zhí)行時(shí)的時(shí)間,-T顯示調(diào)用花費(fèi)的時(shí)間,,-e限制被跟蹤的調(diào)用的類型,,-o重定向輸出到一個(gè)文件。默認(rèn)的,,strace打印在stderr打印跟蹤信息,。 strace自己從內(nèi)核接收信息。這就意味著一個(gè)程序可以被跟蹤,,不論它是否在編譯的時(shí)候選擇調(diào)試支持(gcc的-g選項(xiàng)),,不管調(diào)試信息是不是被移除。你還能掛接你的跟蹤到一個(gè)運(yùn)行的進(jìn)程,,就像調(diào)試器連接到運(yùn)行的進(jìn)程并控制進(jìn)程執(zhí)行一樣,。 追蹤信息常常用來支持發(fā)送到用戶程序開發(fā)者的bug報(bào)告,但是它對(duì)內(nèi)核程序員的作用也是不可估量的,。我們可以通過對(duì)系統(tǒng)調(diào)用的反應(yīng)來了解驅(qū)動(dòng)程序是怎么執(zhí)行的,;strace允許我們檢查每一個(gè)調(diào)用的數(shù)據(jù)輸出和輸入的一致性。 例如,,下面的屏幕轉(zhuǎn)儲(chǔ)顯示的是運(yùn)行命令strace ls /dev > /dev/scull0的最后(包括大多數(shù))幾行: open("/dev", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY) = 3 fstat64(3, {st_mode=S_IFDIR|0755, st_size=24576, ...}) = 0 fcntl64(3, F_SETFD, FD_CLOEXEC) = 0 getdents64(3, /* 141 entries */, 4096) = 4088 [...] getdents64(3, /* 0 entries */, 4096) = 0 close(3) = 0 [...] fstat64(1, {st_mode=S_IFCHR|0664, st_rdev=makedev(254, 0), ...}) = 0 write(1, "MAKEDEV\nadmmidi0\nadmmidi1\nadmmid"..., 4096) = 4000 write(1, "b\nptywc\nptywd\nptywe\nptywf\nptyx0\n"..., 96) = 96 write(1, "b\nptyxc\nptyxd\nptyxe\nptyxf\nptyy0\n"..., 4096) = 3904 write(1, "s17\nvcs18\nvcs19\nvcs2\nvcs20\nvcs21"..., 192) = 192 write(1, "\nvcs47\nvcs48\nvcs49\nvcs5\nvcs50\nvc"..., 673) = 673 close(1) = 0 exit_group(0) = ? 很明顯,,ls完成目標(biāo)目錄的查詢后,第一個(gè)write調(diào)用嘗試寫4KB的數(shù)據(jù),。奇怪的是(對(duì)于ls),,僅有4000字節(jié)的數(shù)據(jù)被寫出,然后操作重試,。但是,,我們知道scull中的write實(shí)現(xiàn)每次寫單個(gè)量子,因此我們已經(jīng)預(yù)期到不完全的寫操作,。幾個(gè)步驟之后,,所有的任務(wù)完成,,程序成功退出。 另一個(gè)例子,,我們來讀取scull設(shè)備(使用wc命令): [...] open("/dev/scull0", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFCHR|0664, st_rdev=makedev(254, 0), ...}) = 0 read(3, "MAKEDEV\nadmmidi0\nadmmidi1\nadmmid"..., 16384) = 4000 read(3, "b\nptywc\nptywd\nptywe\nptywf\nptyx0\n"..., 16384) = 4000 read(3, "s17\nvcs18\nvcs19\nvcs2\nvcs20\nvcs21"..., 16384) = 865 read(3, "", 16384) = 0 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 write(1, "8865 /dev/scull0\n", 17) = 17 close(3) = 0 exit_group(0) = ? 正如所期望的那樣,,read每次能獲取4000字節(jié)的數(shù)據(jù),但是總的數(shù)據(jù)量與前面例子中寫入到設(shè)備中的數(shù)據(jù)量是相同的,。注意到這個(gè)例子中是怎樣組織重試的,,你會(huì)發(fā)現(xiàn)非常有意思,它和前面的例子是相對(duì)的,。wc對(duì)快速讀取作了優(yōu)化,,因此,,它繞過了標(biāo)準(zhǔn)庫,,每一個(gè)系統(tǒng)調(diào)用都試圖讀取更多的數(shù)據(jù)。你可以從read的trace輸出行中看到wc試圖一次讀取16KB的數(shù)據(jù),。 Linux專家能在strace的輸出信息中找到很多有用的信息,。如果所有的符號(hào)讓你分心,你可以使用efile標(biāo)志來限制僅跟蹤文件方法(open,,read等等),。 就個(gè)人來說,我們發(fā)現(xiàn)strace能準(zhǔn)確地描述系統(tǒng)調(diào)用的運(yùn)行錯(cuò)誤,。通常應(yīng)用程序中的perror調(diào)用或是演示程序不能提供足夠的信息以供調(diào)試,,并且能準(zhǔn)確地發(fā)現(xiàn)哪一個(gè)參數(shù)引發(fā)了錯(cuò)誤能有很大的幫助。 1.5.調(diào)試系統(tǒng)錯(cuò)誤 即使你使用了所有的監(jiān)視和調(diào)試技術(shù),,有時(shí)bugs仍然存在于你的驅(qū)動(dòng)程序中,,并在驅(qū)動(dòng)程序被執(zhí)行時(shí)引發(fā)系統(tǒng)錯(cuò)誤。如果這種情況發(fā)生,,收集盡可能多的信息用于解決問題將非常重要,。 注意“錯(cuò)誤”并不意味這“panic”。Linux代碼已經(jīng)足夠健壯,,能優(yōu)美地對(duì)錯(cuò)誤作出回應(yīng):一個(gè)錯(cuò)誤通常導(dǎo)致當(dāng)前進(jìn)程被破壞,,系統(tǒng)繼續(xù)運(yùn)行。如果錯(cuò)誤發(fā)生在進(jìn)程上下文之外或系統(tǒng)中一些重要部分被損壞時(shí),,系統(tǒng)就會(huì)panic,。但是問題是由驅(qū)動(dòng)程序錯(cuò)誤引起的,它通常僅僅導(dǎo)致不辛使用驅(qū)動(dòng)程序的進(jìn)程突然死亡,。唯一的不可恢復(fù)的損壞是當(dāng)分配給進(jìn)程上下文的內(nèi)存丟失時(shí),;舉例來說,驅(qū)動(dòng)程序通過kmalloc分配的動(dòng)態(tài)鏈表可能丟失,。但是,,因?yàn)閮?nèi)核在進(jìn)程死亡時(shí)對(duì)任何打開的設(shè)備調(diào)用close操作,,你的驅(qū)動(dòng)程序可以釋放open方法分配的內(nèi)存。 雖然oops通常不會(huì)讓你的系統(tǒng)整個(gè)崩潰,,你可能發(fā)現(xiàn)發(fā)生oops后你需要重啟計(jì)算機(jī),。一個(gè)有bug的驅(qū)動(dòng)程序會(huì)使硬件處于不可用的狀態(tài),使內(nèi)核資源處于不一致的狀態(tài),,或者更糟的情況是在任意位置破壞內(nèi)核內(nèi)存,。通常情況下,你可以卸載有bug的驅(qū)動(dòng)程序并在oops發(fā)生后重試,。但是如果你看到任何關(guān)于系統(tǒng)整體運(yùn)轉(zhuǎn)不良好的信息,,你最好的選擇常常是馬上重啟系統(tǒng)。 我們提到當(dāng)內(nèi)核代碼運(yùn)行失常時(shí),,終端會(huì)打印出相關(guān)的信息,。下一節(jié)我們解釋怎樣解碼和使用這些信息。雖然對(duì)于新手來說,,它們看起來非常難解,,不過處理器轉(zhuǎn)儲(chǔ)包含許多有趣的信息,不用額外的測試就足夠精確地定位一個(gè)程序bug,。 1.5.1.oops信息 許多bug是由于解析NULL指針或使用其它錯(cuò)誤的指針值引起,。通常這些bug的輸出結(jié)果是oops信息。 幾乎任何處理器使用的地址都是虛擬地址,,它們通過一個(gè)復(fù)雜的頁表結(jié)構(gòu)(也有這樣的例外情況,,就是物理地址使用它自己的內(nèi)存管理子系統(tǒng))來映射到物理地址。當(dāng)一個(gè)無效(非法)的指針被解析時(shí),,頁機(jī)制映射指針到物理地址時(shí)就會(huì)失敗,,處理器向操作系統(tǒng)通知一個(gè)頁錯(cuò)誤。如果這個(gè)地址是不正確的,,內(nèi)核就不能“調(diào)入”遺失的頁面,;如果處理器在特權(quán)模式下,一旦有這種情況發(fā)生,,內(nèi)核就會(huì)產(chǎn)生oops,。 oops顯示錯(cuò)誤發(fā)生時(shí)刻的處理器狀態(tài),包括CPU寄存器的內(nèi)容和其它一些表面上看起來不可理解的信息,。這些信息是由錯(cuò)誤處理代碼中的printk語句產(chǎn)生的并以前面“printk”節(jié)描述的那樣被派發(fā)的,。 讓我們來看一個(gè)這樣的信息。下面的例子是在一臺(tái)運(yùn)行2.6版本內(nèi)核的機(jī)器上解析一個(gè)NULL指針的結(jié)果,。這里最相關(guān)的信息是指令指針(EIP),,即錯(cuò)誤指令的地址: Unable to handle kernel NULL pointer dereference at virtual address 00000000 printing eip: d083a064 Oops: 0002 [#1] SMP CPU: 0 EIP: 0060:[] Not tainted EFLAGS: 00010246 (2.6.6) EIP is at faulty_write+0x4/0x10 [faulty] eax: 00000000 ebx: 00000000 ecx: 00000000 edx: 00000000 esi: cf8b2460 edi: cf8b2480 ebp: 00000005 esp: c31c5f74 ds: 007b es: 007b ss: 0068 Process bash (pid: 2086, threadinfo=c31c4000 task=cfa0a6c0) Stack: c0150558 cf8b2460 080e9408 00000005 cf8b2480 00000000 cf8b2460 cf8b2460 fffffff7 080e9408 c31c4000 c0150682 cf8b2460 080e9408 00000005 cf8b2480 00000000 00000001 00000005 c0103f8f 00000001 080e9408 00000005 00000005 Call Trace: [] vfs_write+0xb8/0x130 [] sys_write+0x42/0x70 [] syscall_call+0x7/0xb Code: 89 15 00 00 00 00 c3 90 8d 74 26 00 83 ec 0c b8 00 a6 83 d0 該信息是由于向一個(gè)faulty模塊擁有的設(shè)備寫入的時(shí)候產(chǎn)生的,一個(gè)為了演示錯(cuò)誤而故意編寫的模塊,。faulty.c中的write方法的實(shí)現(xiàn)很普通: ssize_t faulty_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos) { /* make a simple fault by dereferencing a NULL pointer */ *(int *)0 = 0; return 0; } 從代碼中可以看出,,我們這里所做的就是解析一個(gè)NULL指針,。因?yàn)?從來都不是一個(gè)有效的指針值,所以產(chǎn)生一個(gè)錯(cuò)誤,,內(nèi)核進(jìn)入錯(cuò)誤處理代碼并輸出前面顯示的信息,。然后調(diào)用該函數(shù)的進(jìn)程就被殺死。 faulty模塊的read實(shí)現(xiàn)有不同的錯(cuò)誤條件: ssize_t faulty_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { int ret; char stack_buf[4]; /* Let's try a buffer overflow */ memset(stack_buf, oxff, 20); if (count > 4) count = 4; */ copy 4 bytes to the user */ ret = copy_to_user(buf, stack_buf, count); if (!ret) return count; return ret; } 該方法拷貝一個(gè)字符串到局部變量中,;不辛的是,,這個(gè)字符串比目的數(shù)組要長。該緩沖區(qū)溢出的結(jié)果是在函數(shù)返回的時(shí)候引發(fā)oops,。return指令把指令指針指向毫無意義的地方,,因此這類錯(cuò)誤非常難于跟蹤,不過你可以得到下面的一些信息: EIP: 0010:[] Unable to handle kernel paging request at virtual address ffffffff printing eip: ffffffff Oops: 0000 [#5] SMP CPU: 0 EIP: 0060:[] Not tainted EFLAGS: 00010296 (2.6.6) EIP is at 0xffffffff eax: 0000000c ebx: ffffffff ecx: 00000000 edx: bfffda7c esi: cf434f00 edi: ffffffff ebp: 00002000 esp: c27fff78 ds: 007b es: 007b ss: 0068 Process head (pid: 2331, threadinfo=c27fe000 task=c3226150) Stack: ffffffff bfffda70 00002000 cf434f20 00000001 00000286 cf434f00 fffffff7 bfffda70 c27fe000 c0150612 cf434f00 bfffda70 00002000 cf434f20 00000000 00000003 00002000 c0103f8f 00000003 bfffda70 00002000 00002000 bfffda70 Call Trace: [] sys_read+0x42/0x70 [] syscall_call+0x7/0xb Code: Bad EIP value. 在上面的例子中,,我們僅能看到一部分調(diào)用棧(vfs_read和faulty_read丟失了),,并且內(nèi)核發(fā)出“錯(cuò)誤的EIP值”的警告。這個(gè)警告與開始處列出的地址(ffffffff)都是內(nèi)核棧崩潰的提示,。 當(dāng)你遇到一個(gè)oops,,通常情況下你要作的第一件事就是查看錯(cuò)誤發(fā)生的地方,,通常它(錯(cuò)誤位置)和調(diào)用棧是分開列出的,。在上面提供的第一個(gè)oops中相應(yīng)的行為: EIP is at faulty_write+0x4/0x10 [faulty] 其中我們可以看到我們位于faulty模塊(在方括號(hào)中列出)的faulty_write函數(shù)中。十六進(jìn)制數(shù)字表示指令指針在函數(shù)的4字節(jié)偏移處,,它有10(十六進(jìn)制)字節(jié)長,。這通常已經(jīng)足夠指出問題的所在了。 如果你需要更多的信息,,調(diào)用??梢蕴峁┰趺凑业酱a崩潰的地方的信息。調(diào)用棧是以十六進(jìn)制的形式打印的,;通過少許的工作,,你就能通過棧列表決定局部變量的值和函數(shù)的參數(shù)。有經(jīng)驗(yàn)的內(nèi)核開發(fā)者能通過特定的模式來獲得重要信息,;比如,,如果我們來觀察faulty_read的oops中的棧列表: Stack: ffffffff bfffda70 00002000 cf434f20 00000001 00000286 cf434f00 fffffff7 bfffda70 c27fe000 c0150612 cf434f00 bfffda70 00002000 cf434f20 00000000 00000003 00002000 c0103f8f 00000003 bfffda70 00002000 00002000 bfffda70 在棧頂部的ffffffff是產(chǎn)生錯(cuò)誤的字符串的一部分。在x86體系結(jié)構(gòu)中,,用戶空間棧的默認(rèn)地址底于0xc0000000,;因此,緊跟在它后邊的值0xbfffda70可能是一個(gè)用戶空間的棧地址,;實(shí)際上,,它是傳遞到read系統(tǒng)調(diào)用的緩沖區(qū)的地址,每次都隨內(nèi)核調(diào)用鏈被復(fù)制并傳遞,。在x86結(jié)構(gòu)上(又一次,,默認(rèn)的),,內(nèi)核空間從0xc0000000開始,因此高于該地址的值幾乎應(yīng)該是內(nèi)核空間地址,,以此類推,。 最后,當(dāng)我們查看oops列表的時(shí)候,,應(yīng)該時(shí)刻小心本章一開始提到的“slab毒物”,。比如,如果你得到的oops中存在0xa5a5a5a5這樣的地址,,你就幾乎可以肯定在其它地方忘記初始化動(dòng)態(tài)內(nèi)存,。 請(qǐng)注意,只有你的內(nèi)核在編譯的時(shí)候激活CONFIG_KALLSYMS選項(xiàng),,你才能看到符號(hào)化的調(diào)用棧(就像上面顯示的一樣),。否則,你只能看到單純的十六進(jìn)制列表,,在你使用其它的方法解碼它之前基本上沒什么用處,。 1.5.2.系統(tǒng)掛起 雖然大多數(shù)內(nèi)核代碼中的bug都以oops信息的方式結(jié)束,有的時(shí)候它們可能完全地掛起系統(tǒng),。如果系統(tǒng)掛起,,沒有信息被打印。比如,,如果代碼進(jìn)入一個(gè)死循環(huán) ,,內(nèi)核就停止調(diào)度,系統(tǒng)不對(duì)任何事件作出反應(yīng),,包括神奇的Ctrl-Alt-Del組合,。你有兩種選擇可以用來處理系統(tǒng)掛起──事先阻止它們或在事后來調(diào)試它們。 你可以通過在關(guān)鍵點(diǎn)插入schedule調(diào)用來阻止死循環(huán),。schedule調(diào)用(正如你所想的一樣)通過請(qǐng)求調(diào)度器來允許其它進(jìn)程從當(dāng)前進(jìn)程獲取CPU時(shí)間,。如果進(jìn)程因?yàn)槟愕尿?qū)動(dòng)程序的bug在內(nèi)核空間循環(huán),schedule調(diào)用能讓你在跟蹤發(fā)生的情況之后殺死進(jìn)程,。 你當(dāng)然應(yīng)該意識(shí)到,,任何對(duì)schedule的調(diào)用都可能產(chǎn)生對(duì)你的驅(qū)動(dòng)程序的可重入調(diào)用,因?yàn)樗试S其它進(jìn)程運(yùn)行,。假設(shè)你已經(jīng)在你的驅(qū)動(dòng)程序中使用了適當(dāng)?shù)逆i機(jī)制,,這種重入情況一般不是什么大問題。但是,,你必須確定,,不在你的驅(qū)動(dòng)程序持有一個(gè)自旋鎖的任何時(shí)刻調(diào)用schedule。 如果你的驅(qū)動(dòng)程序真的使系統(tǒng)掛起,,而且你不知道在什么地方插入schedule調(diào)用,,最好的方法就是加入一些打印信息并把它們寫到控制臺(tái)(如果需要,,你得改變console_loglevel值)。 有的時(shí)候系統(tǒng)可能顯得已經(jīng)掛起,,但是實(shí)際上并沒有,。這有可能發(fā)生,比如,,如果鍵盤由于一些奇怪的原因被持續(xù)鎖住,。這些錯(cuò)誤掛起可以通過查看你專門為這一目的運(yùn)行的程序的輸出來發(fā)現(xiàn)。時(shí)鐘或系統(tǒng)負(fù)載計(jì)量器是極好的狀態(tài)監(jiān)視器,;只要它們保持更新,,調(diào)度器就在運(yùn)行。 處理很多掛起事件的一個(gè)不可或缺的工具是“神奇的系統(tǒng)請(qǐng)求鍵”,,它在許多體系結(jié)構(gòu)上都可以使用,。神奇的系統(tǒng)請(qǐng)求鍵通過PC鍵盤上的Alt鍵和系統(tǒng)請(qǐng)求鍵的組合來產(chǎn)生一個(gè)請(qǐng)求,或者在其它平臺(tái)(查看Documentation/sysrq.txt來獲得詳細(xì)的信息)上使用其它的特定鍵,,它還可以在串口控制臺(tái)上使用,。第三個(gè)鍵必須和這兩個(gè)鍵同時(shí)按下,它完成其中的一系列的有用操作: r 關(guān)閉鍵盤的原始模式,;在崩潰程序(比如X服務(wù)器)使你的鍵盤處于奇怪的狀態(tài)時(shí)使用,。 k 請(qǐng)求“secure attention key”(SAK)函數(shù)。SAK殺死所有在當(dāng)前控制臺(tái)運(yùn)行的進(jìn)程,,留給你一個(gè)干凈的終端,。 s 執(zhí)行所有磁盤的緊急同步操作。 u 卸載,。嘗試以只讀的方式重新掛載所有的磁盤。該操作通常在s之后馬上調(diào)用,,它在系統(tǒng)處于嚴(yán)重問題時(shí)可以節(jié)省很多文件系統(tǒng)檢查時(shí)間,。 b 引導(dǎo)??焖僦貑⑾到y(tǒng),。確定首先同步并重新掛載磁盤。 p 打印處理器的寄存器信息,。 t 打印當(dāng)前的任務(wù)鏈表,。 m 打印內(nèi)存信息。 存在其它神奇的系統(tǒng)請(qǐng)求函數(shù),;查看內(nèi)核源代碼Documentation目錄下的sysrq.txt文件可以獲得完整列表,。注意神奇的系統(tǒng)請(qǐng)求鍵必須在內(nèi)核配置的時(shí)候明確地被激活,很多發(fā)行版因?yàn)槊黠@的安全原因都沒有激活它,。但是一個(gè)用于開發(fā)驅(qū)動(dòng)程序的內(nèi)核,,激活神奇的系統(tǒng)請(qǐng)求鍵是值得的,。神奇的系統(tǒng)請(qǐng)求鍵可以在運(yùn)行的時(shí)候通過下面的命令來禁用: echo 0 > /proc/sys/kernel/sysrq 如果非特權(quán)用戶可以接觸到你的鍵盤,你應(yīng)該考慮禁用它,,以防止無意或有意的系統(tǒng)破壞,。一些以前的內(nèi)核版本的系統(tǒng)請(qǐng)求鍵默認(rèn)是被禁用的,因此你必須在運(yùn)行的時(shí)候向相同的/proc/sys文件寫入1來激活它,。 系統(tǒng)請(qǐng)求操作非常有用,,因此它們可以被不能接觸控制臺(tái)管理員使用。/proc/sysrq_trigger文件是一個(gè)只寫的接入點(diǎn),,在那你能通過寫入相應(yīng)的命令字符來觸發(fā)指定的系統(tǒng)請(qǐng)求操作,;然后你就能從內(nèi)核日志中收集任何的輸出數(shù)據(jù)。系統(tǒng)請(qǐng)求的這一接入點(diǎn)總是可用,,即使你在控制臺(tái)禁用系統(tǒng)請(qǐng)求,。 如果你遇到一個(gè)“活動(dòng)的掛起”,即你的驅(qū)動(dòng)程序處于一個(gè)循環(huán)中但是系統(tǒng)作為一個(gè)整體仍然在運(yùn)行,,有許多技術(shù)值得我們來了解,。一般情況下,系統(tǒng)請(qǐng)求p函數(shù)能直接找到錯(cuò)誤的例程,。如果失敗,,你可以使用內(nèi)核的審計(jì)函數(shù)。編譯內(nèi)核的時(shí)候激活審計(jì)選項(xiàng),,并在命令行啟動(dòng)時(shí)提供profile=2參數(shù),。使用readprofile工具重置審計(jì)計(jì)數(shù)器,然后讓驅(qū)動(dòng)程序進(jìn)入循環(huán)執(zhí)行,。一段時(shí)間后,,再次使用readprofile工具來查看內(nèi)核在各個(gè)部分花費(fèi)的時(shí)間。Documentation/basic_profiling.txt文件中有你開始使用審計(jì)工具所需的所有信息,。 追捕系統(tǒng)掛起的一個(gè)值得使用的預(yù)防措施是以只讀的方式掛載所有的磁盤(或者卸載它們),。如果磁盤是只讀的或是沒被掛載的,就沒有破壞文件系統(tǒng)或使它處于不一致狀態(tài)的風(fēng)險(xiǎn),。另外的一個(gè)可能的方法是網(wǎng)絡(luò)文件系統(tǒng),,使用一臺(tái)通過NFS掛載它所有文件系統(tǒng)的電腦?!癗FS-Root”特性必須在內(nèi)核中打開,,并且在系統(tǒng)啟動(dòng)時(shí)必須給它傳遞特殊的參數(shù)。這樣,,你甚至不用使用神奇的系統(tǒng)請(qǐng)求鍵就可以避免文件系統(tǒng)崩潰,,因?yàn)槲募到y(tǒng)的一致性是由NFS服務(wù)器來管理的,它不可能被你的設(shè)備驅(qū)動(dòng)程序影響。 1.6.調(diào)試器和相關(guān)的工具 調(diào)試模塊的最后一種方法是使用一個(gè)調(diào)試器來單步執(zhí)行代碼,,監(jiān)視變量的值和機(jī)器的寄存器,。這是一件耗時(shí)的工作,應(yīng)該盡可能地避免,。雖然如此,,通過調(diào)試器來完成對(duì)代碼的細(xì)粒度調(diào)試的價(jià)值是不可估量的。 在內(nèi)核上使用一個(gè)交互性的調(diào)試器是一種挑戰(zhàn),。內(nèi)核在自己的地址空間代表系統(tǒng)的所有進(jìn)程執(zhí)行,。因此,許多用戶空間調(diào)試器提供的很多公用功能,,例如斷點(diǎn)和單步執(zhí)行,,在內(nèi)核中很難獲得。本節(jié)我們討論許多調(diào)試內(nèi)核的方法,;它們各有優(yōu)缺點(diǎn),。 1.6.1.使用gdb gdb對(duì)檢查系統(tǒng)內(nèi)部非常有用。在這個(gè)級(jí)別上高效率地使用gdb要求一些使用gdb命令的信心,,對(duì)目標(biāo)平臺(tái)的匯編代碼的一定了解,,和配對(duì)源代碼與被優(yōu)化過的匯編代碼的能力。 調(diào)試器必須把內(nèi)核看成一個(gè)應(yīng)用程序方式來調(diào)用,。除了指定ELF內(nèi)核境象的文件名外,,你必須在命令行提供一個(gè)core文件名。對(duì)于一個(gè)運(yùn)行的內(nèi)核,,這個(gè)core文件是內(nèi)核的核心境象,,/proc/kcore。典型的gdb調(diào)用像下面這樣: gdb /usr/src/linux/vmlinux /proc/kcore 第一個(gè)參數(shù)是沒有壓縮過的可執(zhí)行的ELF內(nèi)核名,,不是zImage或bzImage或其它任何為特殊啟動(dòng)環(huán)境編譯的內(nèi)核,。 gdb命令行的第二個(gè)參數(shù)是core文件的名字。像其它/proc中的文件一樣,,/proc/kcore是讀取的時(shí)候產(chǎn)生的,。當(dāng)read系統(tǒng)調(diào)用在/proc文件系統(tǒng)中執(zhí)行時(shí),它映射到一個(gè)數(shù)據(jù)產(chǎn)生函數(shù)而不是一個(gè)數(shù)據(jù)獲取函數(shù),;我們已經(jīng)在前面的“使用/proc文件系統(tǒng)”一節(jié)中使用過這一特性。kcore以一個(gè)core文件的格式來表示“可執(zhí)行的”的內(nèi)核,;它是一個(gè)很大的文件,,因?yàn)樗碚麄€(gè)與物理內(nèi)存相應(yīng)的內(nèi)核地址空間。在gdb中,,你能通過標(biāo)準(zhǔn)的gdb命令來查看內(nèi)核變量的值,。比如,p jiffies打印從系統(tǒng)啟動(dòng)到當(dāng)前時(shí)間的時(shí)鐘滴答數(shù)。 當(dāng)你在gdb中打印數(shù)據(jù)時(shí),,內(nèi)核仍然在運(yùn)行,,并且不同的數(shù)據(jù)條目在不同的時(shí)間有不一樣的值;但是,,gdb通過緩存已經(jīng)讀取的數(shù)據(jù)來優(yōu)化對(duì)core文件的訪問,。如果你嘗試再次讀取jiffies變量,你會(huì)得到與前面相同的值,。對(duì)于常規(guī)core文件來說,,緩存值可以避免額外的磁盤訪問,它是正確的選擇,,但是但使用“動(dòng)態(tài)”core境象的時(shí)候很不方便,。解決方法是在你需要更新gdb緩存的任何時(shí)刻發(fā)出core-file /proc/kcore命令;調(diào)試器使用新的core文件并丟棄所有的舊信息,。但是你并不總是需要在讀取新數(shù)據(jù)的時(shí)候都使用core-file命令,;gdb每次讀取幾千字節(jié)的一塊數(shù)據(jù)而且僅緩存它已經(jīng)被它引用過的塊。 許多標(biāo)準(zhǔn)gdb提供的功能在調(diào)試內(nèi)核是不能使用,。比如,,gdb不能修改內(nèi)核數(shù)據(jù);在處理內(nèi)存境象之前,,它被期望在自己的控制下運(yùn)行一個(gè)被調(diào)試的程序,。也不能設(shè)置斷點(diǎn)或觀察點(diǎn),或是單步執(zhí)行內(nèi)核函數(shù),。 注意,,為了讓gdb能使用符號(hào)信息,你必須在編譯內(nèi)核的時(shí)候打開CONFIG_DEBUG_INFO選項(xiàng)集,。這樣的做的結(jié)果是磁盤上更大的內(nèi)核境象,,但是,沒有那些信息,,獲取內(nèi)核變量的信息幾乎是不可能的,。 如果調(diào)試信息可以使用,你就能了解許多內(nèi)核內(nèi)部正在進(jìn)行什么工作的信息,。gdb能打印出結(jié)構(gòu),,跟隨指針,等等,。但是檢查模塊非常困難,。因?yàn)槟K不是傳遞到gdb的vmlinux境象的一部分,調(diào)試器對(duì)它一無所知,。辛運(yùn)的是,,在2.6.7內(nèi)核中,你可以傳遞給gdb檢查可加載模塊所需要的信息。 Linux的可加載模塊是ELF格式的可執(zhí)行境象,;它們被劃分為很多段,。一個(gè)典型的模塊可以包含一打或更多的段,但是與調(diào)試期間相應(yīng)的段只有典型的下面三個(gè): .text 該段包含模塊的可執(zhí)行代碼,。調(diào)試器要能跟蹤或設(shè)置斷點(diǎn)就必須指定這一段的位置,。(這些操作都與在/proc/kcore上運(yùn)行調(diào)試器無關(guān),但是當(dāng)使用kgdb是它們很有用,,kgdb在后面討論),。 .bss .data 這兩個(gè)段包含模塊的變量。編譯的時(shí)候沒有被初始化的變量保存在.bss中,,而那些被初始化的變量保存在.data段,。 要讓gdb能在可加載模塊上工作,需要給調(diào)試器提供被加載模塊的各個(gè)段的位置,。這些信息可以在/sys/module下的sysfs中找到,。例如,加載scull模塊后,,/sys/module/scull/sections目錄包含以各個(gè)段命名的文件,,比如.text文件;每個(gè)文件的內(nèi)容就是各個(gè)段的基地址,。 現(xiàn)在是我們執(zhí)行g(shù)db命令來告訴它我們模塊的信息的時(shí)候了,。我們需要的命令是add-symbol-file;該命令需要的參數(shù)是模塊目標(biāo)文件的名字,,.text的基地址,,和一些可選的描述其它段的位置的參數(shù)。通過查看sysfs中的模塊的各個(gè)段的相關(guān)數(shù)據(jù)后,,我們就可以構(gòu)建命令: (gdb)add-symbol-file …/scull.ko 0xd0832000 \ -s .bss 0xd0837100 \ -s .data 0xd0836be0 我們?cè)跇永╣dbline)中包含一個(gè)簡單的腳本,,它可以為指定的模塊創(chuàng)建該命令。 現(xiàn)在我們可以使用gdb來檢查我們的可加載模塊了,。下面是一個(gè)scull調(diào)試過程中的一個(gè)例子: (gdb)add-symbol-file scull.ko 0xd0832000 \ -s .bss 0xd0837100 \ -s .data 0xd0836be0 add symbol table from file "scull.ko" at .text_addr = 0xd0832000 .bss_addr = 0xd0837100 .data_addr = 0xd0836be0 (y or n) y Reading symbols from scull.ko...done. (gdb) p scull_devices[0] $1 = {data = 0xcfd66c50, quantum = 4000, qset = 1000, size = 20881, access_key = 0, ...} 其中我們可以看出第一個(gè)scull設(shè)備現(xiàn)在持有20,881字節(jié)的數(shù)據(jù),。如果我們?cè)敢猓覀兛梢愿S數(shù)據(jù)鏈,,或查看模塊中其它的我們感興趣的任何信息,。 另一個(gè)值得學(xué)習(xí)的有用的訣竅是: (gdb)print *(address) 在address中填入一個(gè)十六進(jìn)制的地址;輸出是一個(gè)文件和與該地址對(duì)應(yīng)的代碼的行號(hào),。這一技術(shù)可能有用,,例如,找出一個(gè)函數(shù)指針的確確指向,。 我們?nèi)匀徊荒軋?zhí)行像設(shè)置斷點(diǎn)或修改數(shù)據(jù)的典型調(diào)試任務(wù);要執(zhí)行這些操作,我們必須使用像kdb(下一個(gè)討論)或kgdb(馬上就會(huì)學(xué)習(xí)到)這樣的工具,。 1.6.2.kdb內(nèi)核調(diào)試器 很多的讀者可能會(huì)奇怪為什么內(nèi)核中沒有集成更高級(jí)的調(diào)試特性,。答案很簡單,因?yàn)長inus不信任交互性的調(diào)試器,。他當(dāng)心調(diào)試器會(huì)引入不良的修正,,它們修補(bǔ)癥狀而不是尋找到問題的真正根源。因此,,沒有內(nèi)置的調(diào)試器,。 但是其它的內(nèi)核開發(fā)者,偶爾會(huì)使用交互型的調(diào)試工具,。其中的一個(gè)是kdb,,它是編譯到內(nèi)核中的調(diào)試器,可以從oss.sgi.com獲得一個(gè)非官方的補(bǔ)丁包,。使用kdb,,你必須獲取補(bǔ)丁包(確定獲取的是與你的內(nèi)核版本相匹配的補(bǔ)丁包),應(yīng)用它,,并重新編譯和重新安裝內(nèi)核,。注意,在編寫本書時(shí),,kdb僅僅能在IA-32(x86)系統(tǒng)上使用(雖然針對(duì)IA-64版本的補(bǔ)丁包在正式的內(nèi)核源代碼包中存在了一段時(shí)間,,但不久就被移除了)。 一旦你運(yùn)行的是一個(gè)能使用kdb的內(nèi)核,,有很多方法進(jìn)入調(diào)試器,。在控制臺(tái)按下Pause(或Bread)鍵啟動(dòng)調(diào)試器。當(dāng)內(nèi)核oops發(fā)生或到達(dá)斷點(diǎn)時(shí),,kdb也會(huì)啟動(dòng),。不管怎樣,你將看到像下面這樣的信息: Entering kdb (0xc0347b80) on processor 0 due to Keyboard Entry [0]kdb> 注意在kdb運(yùn)行的時(shí)候,,內(nèi)核的所有部分都停止運(yùn)行,。當(dāng)你調(diào)用kdb時(shí),不應(yīng)該在系統(tǒng)上運(yùn)行其它的任何東西,;特別是,,你不能有網(wǎng)絡(luò)連接打開──除非你在調(diào)試一個(gè)網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序。如果你將使用kdb,,以單用戶模式啟動(dòng)系統(tǒng)通常是一個(gè)好注意,。 我們使用scull的調(diào)試過程作為例子。假設(shè)驅(qū)動(dòng)程序已經(jīng)加載,,我們就能像下面一樣告訴kdb在scull_read中設(shè)置一個(gè)斷點(diǎn): [0]kdb> bp scull_read Instruction(i) BP #0 at 0xcd087c5dc (scull_read) is enabled globally adjust 1 [0]kdb> go bp命令告訴kdb在下一次內(nèi)核進(jìn)入scull_read時(shí)停止執(zhí)行,。然后你鍵入go來繼續(xù)執(zhí)行,。在給其中的一個(gè)scull設(shè)備添加數(shù)據(jù)之后,我們就能在另一個(gè)終端的shell下執(zhí)行cat來讀取它的內(nèi)容,,輸出如下: Instruction(i) breakpoint #0 at 0xd087c5dc (adjusted) 0xd087c5dc scull_read: int3 Entering kdb (current=0xcf09f890, pid 1575) on processor 0 due to Breakpoint @ 0xd087c5dc [0]kdb> 現(xiàn)在我們處于scull_read函數(shù)的起始處,。想了解我們是怎樣到達(dá)該位置的,我們可以獲得堆棧痕跡: [0]kdb> bt ESP EIP Function (args) 0xcdbddf74 0xd087c5dc [scull]scull_read 0xcdbddf78 0xc0150718 vfs_read+0xb8 0xcdbddfa4 0xc01509c2 sys_read+0x42 0xcdbddfc4 0xc0103fcf syscall_call+0x7 [0]kdb> kdb試著打印調(diào)用棧的每一個(gè)函數(shù)的參數(shù),。但是它被編譯器使用的優(yōu)化訣竅給搞混了,。因此,它沒有能打印出scull_read的參數(shù),。 是查看數(shù)據(jù)的時(shí)候了,。mds命令用來操縱數(shù)據(jù);我們可以使用下面的命令來查詢scull_devices指針的值: [0]kdb> mds scull_devices 1 0xd0880de8 cf36ac00 .... 其中我們請(qǐng)求一個(gè)從scull_devices位置開始處的一個(gè)(4字節(jié))字的數(shù)據(jù),;結(jié)果告訴我們我們的設(shè)備數(shù)組的地址為0xd0880de8,;第一個(gè)設(shè)備結(jié)構(gòu)的地址為0xcf36ac00。為了查看這個(gè)設(shè)備結(jié)構(gòu),,我們必須使用這個(gè)地址: [0]kdb> mds cf36ac00 0xcf36ac00 ce137dbc .... 0xcf36ac04 00000fa0 .... 0xcf36ac08 000003e8 .... 0xcf36ac0c 0000009b .... 0xcf36ac10 00000000 .... 0xcf36ac14 00000001 .... 0xcf36ac18 00000000 .... 0xcf36ac1c 00000001 .... 上面的8行輸出與scull_dev結(jié)構(gòu)中的各個(gè)數(shù)據(jù)項(xiàng)的起始地址相對(duì)應(yīng),。因此,我們看到第一個(gè)設(shè)備的內(nèi)存是在地址0xce137dbc分配的,,量子大小是4000(十六進(jìn)制的fa0),,量子集的大小是1000(十六進(jìn)制的3e8),現(xiàn)在有155(十六進(jìn)制的9b)字節(jié)存儲(chǔ)在設(shè)備中,。 kdb可以修改數(shù)據(jù)的值,。假設(shè)我們想削減一些設(shè)備中的數(shù)據(jù): [0]kdb> mm cf26ac0c 0x50 0xcf26ac0c = 0x50 緊接著的cat命令將比之前的命令返回更少的數(shù)據(jù)。 kdb有很多其它的功能,,包括單步執(zhí)行(按指令,,而不是按C源代碼的行數(shù)),設(shè)置數(shù)據(jù)訪問的斷點(diǎn),,反匯編代碼,,遍歷鏈表,訪問寄存器數(shù)據(jù),,等,。使用kdb補(bǔ)丁包后,可以在內(nèi)核源代碼樹的Documentation/kdb目錄下找到一套完整的手冊(cè)頁,。 1.6.3.kgdb補(bǔ)丁 我們迄今為止看到的交互型調(diào)試方法(在/proc/kcore上使用gdb和kdb)都缺乏用戶空間應(yīng)用程序開發(fā)者所熟悉的那種環(huán)境,。難道就沒有更好的供內(nèi)核使用的能支持像修改變量值,設(shè)置斷點(diǎn)等等特性的調(diào)試器了嗎,? 像所描述的一樣,,解決方法確實(shí)存在。在編寫本書時(shí),,有兩個(gè)獨(dú)立的補(bǔ)丁在流通使用,,它們?cè)试Sgdb在調(diào)試內(nèi)核使用它的全部功能,。令人迷惑的是,兩個(gè)補(bǔ)丁都叫做kgdb,。它們通過把運(yùn)行測試內(nèi)核的系統(tǒng)與運(yùn)行調(diào)試器的系統(tǒng)分開來完成工作,;它們都是通過一個(gè)串口線連接兩個(gè)系統(tǒng)的。因此,,開發(fā)人員可以在他或她的穩(wěn)定版的桌面系統(tǒng)上運(yùn)行g(shù)db,而在一個(gè)運(yùn)行在作為犧牲的測試盒里的內(nèi)核上操作,。以這種模式建立gdb需要在開始時(shí)花費(fèi)一些時(shí)間,,但是這些投資在一個(gè)非常困難的bug顯現(xiàn)的時(shí)候就能迅速得到回報(bào)。 這些補(bǔ)丁處于很強(qiáng)的交互狀態(tài),,甚至有些特性是合并在一起的,,因此我們避免談?wù)撍鼈兊母嘈畔ⅲ嗽谀睦铽@得它們和它們的基本特性外,。感興趣的讀者我們支持你了解事件的當(dāng)前狀態(tài),。 第一個(gè)kgdb補(bǔ)丁現(xiàn)在可以在-mm內(nèi)核樹下找到──加入2.6主內(nèi)核樹必須經(jīng)歷的路徑。這一版本的補(bǔ)丁支持x86,,SuperH,,ia64,x86_64,,SPARC,,和32位的PPC體系結(jié)構(gòu)。除了通過普通模式下的串口操作外,,該版本的kgdb能通過局域網(wǎng)來交互,。只要激活以太網(wǎng)模式并在啟動(dòng)時(shí)提供kgdboe參數(shù)設(shè)置來設(shè)置可以發(fā)出調(diào)試命令的IP地址。Documentation/i386/kgdb目錄下的文檔包含怎樣設(shè)置的參數(shù)的內(nèi)容 ,。 你可以有另外選擇,,使用在 http://kgdb./ 找到的補(bǔ)丁。這一版本的調(diào)試器不支持網(wǎng)絡(luò)交互模式(不過聽說已經(jīng)在開發(fā)當(dāng)中),,但是它有調(diào)試可加載模塊的內(nèi)置支持,。它支持x86,x86,,x86_64,,PowerPC,和S/390體系結(jié)構(gòu),。 1.6.4.用戶模式的Linux端口 用戶模式Linux(UML)是一個(gè)非常有趣的概念,。它在它自己的arch/um子目錄下結(jié)構(gòu)化一個(gè)獨(dú)立的Linux內(nèi)核端口。但是它不能在新型的硬件上運(yùn)行,;它運(yùn)行在一個(gè)通過Linux系統(tǒng)調(diào)用接口實(shí)現(xiàn)的虛擬機(jī)上,。因此,,UML允許Linux內(nèi)核作為一個(gè)獨(dú)立的,用戶模式進(jìn)程在Linux系統(tǒng)上運(yùn)行,。 有一個(gè)作為用戶空間進(jìn)程運(yùn)行的內(nèi)核好處,。因?yàn)樗\(yùn)行在一個(gè)被約束的,虛擬處理器上,,一個(gè)有bug的內(nèi)核就不能破壞“真正的”系統(tǒng),。不同的硬件和軟件配置就可以簡單地在相同的盒子下測試??赡軐?duì)于內(nèi)核開發(fā)者最重要的是,,用戶模式的內(nèi)核能非常簡單地被gdb或其它的調(diào)試器操縱。 不過別忘了,,它僅僅只是另一個(gè)進(jìn)程,。UML有潛力來加速內(nèi)核的開發(fā)。 但是,,從驅(qū)動(dòng)程序編寫者的角度來看,,UML有一個(gè)很大的不足之處:用戶模式的內(nèi)核不能訪問宿主系統(tǒng)的硬件。因此,,雖然它能對(duì)調(diào)試本書中的大多數(shù)樣例代碼有用,,但UML還不能對(duì)處理真正硬件設(shè)備的驅(qū)動(dòng)程序的調(diào)試有幫助。 查看 http://user-mode-linux./ 以獲得UML的更多信息,。 1.6.5.Linux Trace Toolkit Linux Trace Toolkit(LTT)是一個(gè)內(nèi)核補(bǔ)丁和一系列允許跟蹤內(nèi)核事件的工具,。跟蹤信息包含計(jì)時(shí)信息并能對(duì)特定時(shí)期內(nèi)核發(fā)生了什么創(chuàng)建一個(gè)合理完整的描述。因此,,它不僅用來調(diào)試,,還用來追蹤性能問題。 LTT和它的廣泛文檔可以在 http://www./LTT 上找到,。 4.6.6.Dynamic Probes Dynamic Probes(或者Dprobes)是一個(gè)IBM發(fā)行的(遵循GPL)針對(duì)IA-32體系結(jié)構(gòu)的調(diào)試工具,。它允許在幾乎系統(tǒng)中的所有地方放置“探測點(diǎn)”,不管是用戶空間還是內(nèi)核空間,。探測點(diǎn)包含一些代碼(是由特殊的,,面向棧的語言編寫的),這些代碼在控制到達(dá)給定的點(diǎn)時(shí)執(zhí)行,。這些代碼能向用戶空間報(bào)告信息,,改變寄存器,或者做一些其它的事情,。DProbes的有用特性就是一旦該功能被編譯進(jìn)內(nèi)核,,就能在運(yùn)行中的系統(tǒng)中的任何地方插入探測點(diǎn)而不需要改變內(nèi)核代碼或重啟。DProbes能與LTT一起工作,在任何地方插入新的跟蹤事件,。 DPorbes工具可以從IBM的開源網(wǎng)站: http://oss.software.ibm.com 下載,。 本文來自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):http://blog./u/1214/showart_431352.html |
|