內(nèi)核開發(fā)比用戶空間開發(fā)更難的一個因素就是內(nèi)核調(diào)試艱難。內(nèi)核錯誤往往會導(dǎo)致系統(tǒng)宕機(jī),很難保留出錯時的現(xiàn)場。調(diào)試內(nèi)核的關(guān)鍵在于你的對內(nèi)核的深刻理解。 嵌入式進(jìn)階教程分門別類整理好了,看的時候十分方便,由于內(nèi)容較多,這里就截取一部分圖吧。 調(diào)試前的準(zhǔn)備在調(diào)試一個bug之前,我們所要做的準(zhǔn)備工作有: 有一個被確認(rèn)的bug,包含這個bug的內(nèi)核版本號,需要分析出這個bug在哪一個版本被引入,這個對于解決問題有極大的幫助。可以采用二分查找法來逐步鎖定bug引入版本號。 對內(nèi)核代碼理解越深刻越好,同時還需要一點點運氣,該bug可以復(fù)現(xiàn)。如果能夠找到規(guī)律,那么離找到問題的原因就不遠(yuǎn)了;最小化系統(tǒng)。把可能產(chǎn)生bug的因素逐一排除掉。 內(nèi)核中的bug內(nèi)核中的bug也是多種多樣的。它們的產(chǎn)生有無數(shù)的原因,同時表象也變化多端。從隱藏在源代碼中的錯誤到展現(xiàn)在目擊者面前的bug,其發(fā)作往往是一系列連鎖反應(yīng)的事件才可能觸發(fā)的。雖然內(nèi)核調(diào)試有一定的困難,但是通過你的努力和理解,說不定你會喜歡上這樣的挑戰(zhàn)。 內(nèi)核調(diào)試配置選項學(xué)習(xí)編寫驅(qū)動程序要構(gòu)建安裝自己的內(nèi)核(標(biāo)準(zhǔn)主線內(nèi)核)。最重要的原因之一是:內(nèi)核開發(fā)者已經(jīng)建立了多項用于調(diào)試的功能。但是由于這些功能會造成額外的輸出,并導(dǎo)致能量下降,因此發(fā)行版廠商通常會禁止發(fā)行版內(nèi)核中的調(diào)試功能。 內(nèi)核配置 為了實現(xiàn)內(nèi)核調(diào)試,在內(nèi)核配置上增加了幾項: Kernel hacking ---> [*] Magic SysRq key [*] Kernel debugging [*] 調(diào)試原子操作從內(nèi)核2.5開發(fā),為了檢查各類由原子操作引發(fā)的問題,內(nèi)核提供了極佳的工具。 下面這些選項可以最大限度地利用該特性:
引發(fā)bug并打印信息BUG()和BUG_ON() 一些內(nèi)核調(diào)用可以用來方便標(biāo)記bug,提供斷言并輸出信息。最常用的兩個是BUG()和BUG_ON()。定義在中: #ifndef HAVE_ARCH_BUG #define BUG() do { 當(dāng)調(diào)用這兩個宏的時候,它們會引發(fā)OOPS,導(dǎo)致棧的回溯和錯誤消息的打印。 WARN(x) 和 WARN_ON(x) 而WARN_ON則是調(diào)用dump_stack,打印堆棧信息,不會OOPS。定義在中:
dump_stack() 有些時候,只需要在終端上打印一下棧的回溯信息來幫助你調(diào)試。這時可以使用dump_stack()。這個函數(shù)只是在終端上打印寄存器上下文和函數(shù)的跟蹤線索。 if (!debug_check) { printk(KERN_DEBUG “provide some information…/n”); dump_stack(); } printk()內(nèi)核提供的格式化打印函數(shù)。 printk函數(shù)的健壯性 健壯性是printk最容易被接受的一個特質(zhì),幾乎在任何地方,任何時候內(nèi)核都可以調(diào)用它(中斷上下文、進(jìn)程上下文、持有鎖時、多處理器處理時等)。 printk函數(shù)脆弱之處 在系統(tǒng)啟動過程中,終端初始化之前,在某些地方是不能調(diào)用的。如果真的需要調(diào)試系統(tǒng)啟動過程最開始的地方,有以下方法可以使用: 使用串口調(diào)試,將調(diào)試信息輸出到其他終端設(shè)備。 使用early_printk(),該函數(shù)在系統(tǒng)啟動初期就有打印能力。但它只支持部分硬件體系。 LOG等級 printk和printf一個主要的區(qū)別就是前者可以指定一個LOG等級。內(nèi)核根據(jù)這個等級來判斷是否在終端上打印消息。內(nèi)核把比指定等級高的所有消息顯示在終端。 printk(KERN_CRIT “Hello, world! ”); 注意,第一個參數(shù)并不一個真正的參數(shù),因為其中沒有用于分隔級別(KERN_CRIT)和格式字符的逗號(,)。KERN_CRIT本身只是一個普通的字符串(事實上,它表示的是字符串 '<2>';表 1 列出了完整的日志級別清單)。作為預(yù)處理程序的一部分,C 會自動地使用一個名為 字符串串聯(lián) 的功能將這兩個字符串組合在一起。組合的結(jié)果是將日志級別和用戶指定的格式字符串包含在一個字符串中。 內(nèi)核使用這個指定LOG級別與當(dāng)前終端LOG等級console_loglevel來決定是不是向終端打印。 下面是可使用的LOG等級: #define KERN_EMERG '<0>' /* system is unusable */#define KERN_ALERT '<1>' /* action must be taken immediately */ #define KERN_CRIT '<2>' /* critical conditions */#define KERN_ERR '<3>' /* error conditions */#define KERN_WARNING '<4>' /* warning conditions */#define KERN_NOTICE '<5>' /* normal but significant condition */#define KERN_INFO '<6>' /* informational */#define KERN_DEBUG '<7>' /* debug-level messages */#define KERN_DEFAULT '' /* Use the default kernel loglevel */ 注意,如果調(diào)用者未將日志級別提供給 printk,那么系統(tǒng)就會使用默認(rèn)值 KERN_WARNING '<4>'(表示只有KERN_WARNING 級別以上的日志消息會被記錄)。由于默認(rèn)值存在變化,所以在使用時最好指定LOG級別。有LOG級別的一個好處就是我們可以選擇性的輸出LOG。比如平時我們只需要打印KERN_WARNING級別以上的關(guān)鍵性LOG,但是調(diào)試的時候,我們可以選擇打印KERN_DEBUG等以上的詳細(xì)LOG。而這些都不需要我們修改代碼,只需要通過命令修改默認(rèn)日志輸出級別: mtj@ubuntu :~$ cat /proc/sys/kernel/printk4 4 1 7mtj@ubuntu :~$ cat /proc/sys/kernel/printk_delay0mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit5mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit_burst10 第一項定義了 printk API 當(dāng)前使用的日志級別。這些日志級別表示了控制臺的日志級別、默認(rèn)消息日志級別、最小控制臺日志級別和默認(rèn)控制臺日志級別。printk_delay 值表示的是 printk 消息之間的延遲毫秒數(shù)(用于提高某些場景的可讀性)。注意,這里它的值為 0,而它是不可以通過的 /proc 設(shè)置的。printk_ratelimit 定義了消息之間允許的最小時間間隔(當(dāng)前定義為每 5 秒內(nèi)的某個內(nèi)核消息數(shù))。消息數(shù)量是由 printk_ratelimit_burst 定義的(當(dāng)前定義為 10)。如果您擁有一個非正式內(nèi)核而又使用有帶寬限制的控制臺設(shè)備(如通過串口), 那么這非常有用。注意,在內(nèi)核中,速度限制是由調(diào)用者控制的,而不是在printk 中實現(xiàn)的。如果一個 printk 如果用戶要求進(jìn)行速度限制,那么該用戶就需要調(diào)用printk_ratelimit 函數(shù)。 記錄緩沖區(qū) 內(nèi)核消息都被保存在一個LOG_BUF_LEN大小的環(huán)形隊列中。 #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) ※ 變量CONFIG_LOG_BUF_SHIFT在內(nèi)核編譯時由配置文件定義,對于i386平臺,其值定義如下(在 CONFIG_LOG_BUF_SHIFT=18 記錄緩沖區(qū)操作: ※ 這個紀(jì)錄緩沖區(qū)之所以稱為環(huán)形,是因為它的讀寫都是按照環(huán)形隊列的方式進(jìn)行操作的。 syslogd/klogd 在標(biāo)準(zhǔn)的Linux系統(tǒng)上,用戶空間的守護(hù)進(jìn)程klogd從紀(jì)錄緩沖區(qū)中獲取內(nèi)核消息,再通過syslogd守護(hù)進(jìn)程把這些消息保存在系統(tǒng)日志文件中。klogd進(jìn)程既可以從/proc/kmsg文件中,也可以通過syslog()系統(tǒng)調(diào)用讀取這些消息。默認(rèn)情況下,它選擇讀取/proc方式實現(xiàn)。klogd守護(hù)進(jìn)程在消息緩沖區(qū)有新的消息之前,一直處于阻塞狀態(tài)。一旦有新的內(nèi)核消息,klogd被喚醒,讀出內(nèi)核消息并進(jìn)行處理。默認(rèn)情況下,處理例程就是把內(nèi)核消息傳給syslogd守護(hù)進(jìn)程。syslogd守護(hù)進(jìn)程一般把接收到的消息寫入/var/log/messages文件中。不過,還是可以通過/etc/syslog.conf文件來進(jìn)行配置,可以選擇其他的輸出文件。 dmesg dmesg 命令也可用于打印和控制內(nèi)核緩沖區(qū)。這個命令使用 klogctl 系統(tǒng)調(diào)用來讀取內(nèi)核環(huán)緩沖區(qū),并將它轉(zhuǎn)發(fā)到標(biāo)準(zhǔn)輸出(stdout)。這個命令也可以用來清除內(nèi)核環(huán)緩沖區(qū)(使用 -c 選項),設(shè)置控制臺日志級別(-n 選項),以及定義用于讀取內(nèi)核日志消息的緩沖區(qū)大?。?s 選項)。注意,如果沒有指定緩沖區(qū)大小,那么 dmesg 會使用 klogctl 的SYSLOG_ACTION_SIZE_BUFFER 操作確定緩沖區(qū)大小。 注意 a) 雖然printk很健壯,但是看了源碼你就知道,這個函數(shù)的效率很低:做字符拷貝時一次只拷貝一個字節(jié),且去調(diào)用console輸出可能還產(chǎn)生中斷。所以如果你的驅(qū)動在功能調(diào)試完成以后做性能測試或者發(fā)布的時候千萬記得盡量減少printk輸出,做到僅在出錯時輸出少量信息。否則往console輸出無用信息影響性能。 內(nèi)核printk和日志系統(tǒng)的總體結(jié)構(gòu) 動態(tài)調(diào)試 動態(tài)調(diào)試是通過動態(tài)的開啟和禁止某些內(nèi)核代碼來獲取額外的內(nèi)核信息。 -源文件名 -函數(shù)名 -行號(包括指定范圍的行號) -模塊名 -格式化字符串 將要打印信息的格式寫入/dynamic_debug/control中。 nullarbor:~ # echo 'file svcsock.c line 1603 +p' > /dynamic_debug/control 參考: 內(nèi)存調(diào)試工具MEMWATCH MEMWATCH 由 Johan Lindh 編寫,是一個開放源代碼 C 語言內(nèi)存錯誤檢測工具,您可以自己下載它。只要在代碼中添加一個頭文件并在 gcc 語句中定義了 MEMWATCH 之后,您就可以跟蹤程序中的內(nèi)存泄漏和錯誤了。MEMWATCH 支持ANSIC,它提供結(jié)果日志紀(jì)錄,能檢測雙重釋放(double-free)、錯誤釋放(erroneous free)、沒有釋放的內(nèi)存(unfreedmemory)、溢出和下溢等等。 清單 1. 內(nèi)存樣本(test1.c) #include #include #include 'memwatch.h'int main(void){ char *ptr1; char *ptr2; ptr1 = malloc(512); ptr2 = malloc(512); ptr2 = ptr1; free(ptr2); free(ptr1);} 清單 1 中的代碼將分配兩個 512 字節(jié)的內(nèi)存塊,然后指向第一個內(nèi)存塊的指針被設(shè)定為指向第二個內(nèi)存塊。結(jié)果,第二個內(nèi)存塊的地址丟失,從而產(chǎn)生了內(nèi)存泄漏。
當(dāng)您運行 test1 程序后,它會生成一個關(guān)于泄漏的內(nèi)存的報告。清單 2 展示了示例 memwatch.log 輸出文件。 清單 2. test1 memwatch.log 文件 MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh...double-free: <4> test1.c(15), 0x80517b4 was freed from test1.c(14)...unfreed: <2> test1.c(11), 512 bytes at 0x80519e4{FE FE FE FE FE FE FE FE FE FE FE FE ..............}Memory usage statistics (global): N)umber of allocations made: 2 L)argest memory usage : 1024 T)otal of all alloc() calls: 1024 U)nfreed bytes totals : 512 MEMWATCH 為您顯示真正導(dǎo)致問題出現(xiàn)的信息。如果您釋放一個已經(jīng)釋放過的指針,它會告訴您。對于沒有釋放的內(nèi)存也一樣。日志結(jié)尾部分顯示統(tǒng)計信息,包括泄露了多少內(nèi)存,使用了多少內(nèi)存,以及總共分配了多少內(nèi)存。 YAMD YAMD 軟件包由 Nate Eldredge 編寫,可以查找 C++ 和 C++ 動態(tài)的、與內(nèi)存分配有關(guān)的問題。在撰寫本文時,YAMD 的最新版本為 0.32。請下載 yamd-0.32.tar.gz。執(zhí)行 make 命令來構(gòu)建程序;然后執(zhí)行 make install 命令安裝程序并設(shè)置工具。 一旦您下載了 YAMD 之后,請在 test1.c 上使用它。請刪除 #include memwatch.h 并對 makefile 進(jìn)行如下小小的修改:
清單 3 展示了來自 test1 上的 YAMD 的輸出。 清單 3. 使用 YAMD 的 test1 輸出 YAMD version 0.32Executable: /usr/src/test/yamd-0.32/test1...INFO: Normal allocation of this blockAddress 0x40025e00, size 512...INFO: Normal allocation of this blockAddress 0x40028e00, size 512...INFO: Normal deallocation of this blockAddress 0x40025e00, size 512...ERROR: Multiple freeing Atfree of pointer already freedAddress 0x40025e00, size 512...WARNING: Memory leakAddress 0x40028e00, size 512WARNING: Total memory leaks:1 unfreed allocations totaling 512 bytes*** Finished at Tue ... 10:07:15 2002Allocated a grand total of 1024 bytes 2 allocationsAverage of 512 bytes per allocationMax bytes allocated at one time: 102424 K alloced internally / 12 K mapped now / 8 K maxVirtual program size is 1416 KEnd. YAMD 顯示我們已經(jīng)釋放了內(nèi)存,而且存在內(nèi)存泄漏。讓我們在清單 4 中另一個樣本程序上試試 YAMD。 清單 4. 內(nèi)存代碼(test2.c) #include #include int main(void){ char *ptr1; char *ptr2; char *chptr; int i = 1; ptr1 = malloc(512); ptr2 = malloc(512); chptr = (char *)malloc(512); for (i; i <= 512; i++) { chptr[i] = 'S'; } ptr2 = ptr1; free(ptr2); free(ptr1); free(chptr);} 您可以使用下面的命令來啟動 YAMD: ./run-yamd /usr/src/test/test2/test2 清單 5 顯示了該樣本程序 test2 上使用 YAMD 得到的輸出。YAMD 告訴我們在 for 循環(huán)中有“越界(out-of-bounds)”的情況。 清單 5. 使用 YAMD 的 test2 輸出 Running /usr/src/test/test2/test2Temp output to /tmp/yamd-out.1243*********./run-yamd: line 101: 1248 Segmentation fault (core dumped)YAMD version 0.32Starting run: /usr/src/test/test2/test2Executable: /usr/src/test/test2/test2Virtual program size is 1380 K...INFO: Normal allocation of this blockAddress 0x40025e00, size 512...INFO: Normal allocation of this blockAddress 0x40028e00, size 512...INFO: Normal allocation of this blockAddress 0x4002be00, size 512ERROR: Crash...Tried to write address 0x4002c000Seems to be part of this block:Address 0x4002be00, size 512...Address in question is at offset 512 (out of bounds)Will dump core after checking heap.Done. MEMWATCH 和 YAMD 都是很有用的調(diào)試工具,不過它們的使用方法有所不同。對于 MEMWATCH,您需要添加包含文件memwatch.h 并打開兩個編譯時間標(biāo)記。對于鏈接(link)語句,YAMD 只需要 -g 選項。 Electric Fence 多數(shù) Linux 分發(fā)版包含一個 Electric Fence 包,不過您也可以選擇下載它。Electric Fence 是一個由 Bruce Perens 編寫的malloc()調(diào)試庫。它就在您分配內(nèi)存后分配受保護(hù)的內(nèi)存。如果存在 fencepost 錯誤(超過數(shù)組末尾運行),程序就會產(chǎn)生保護(hù)錯誤,并立即結(jié)束。通過結(jié)合 Electric Fence 和 gdb,您可以精確地跟蹤到哪一行試圖訪問受保護(hù)內(nèi)存。ElectricFence 的另一個功能就是能夠檢測內(nèi)存泄漏。 strace strace 命令是一種強(qiáng)大的工具,它能夠顯示所有由用戶空間程序發(fā)出的系統(tǒng)調(diào)用。strace 顯示這些調(diào)用的參數(shù)并返回符號形式的值。strace 從內(nèi)核接收信息,而且不需要以任何特殊的方式來構(gòu)建內(nèi)核。將跟蹤信息發(fā)送到應(yīng)用程序及內(nèi)核開發(fā)者都很有用。在清單 6 中,分區(qū)的一種格式有錯誤,清單顯示了 strace 的開頭部分,內(nèi)容是關(guān)于調(diào)出創(chuàng)建文件系統(tǒng)操作(mkfs )的。strace 確定哪個調(diào)用導(dǎo)致問題出現(xiàn)。 清單 6. mkfs 上 strace 的開頭部分 execve('/sbin/mkfs.jfs', ['mkfs.jfs', '-f', '/dev/test1'], &...open('/dev/test1', O_RDWR|O_LARGEFILE) = 4stat64('/dev/test1', {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0ioctl(4, 0x40041271, 0xbfffe128) = -1 EINVAL (Invalid argument)write(2, 'mkfs.jfs: warning - cannot setb' ..., 98mkfs.jfs: warning -cannot set blocksize on block device /dev/test1: Invalid argument ) = 98stat64('/dev/test1', {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0open('/dev/test1', O_RDONLY|O_LARGEFILE) = 5ioctl(5, 0x80041272, 0xbfffe124) = -1 EINVAL (Invalid argument)write(2, 'mkfs.jfs: can't determine device'..., ..._exit(1) = ? 清單 6 顯示 ioctl 調(diào)用導(dǎo)致用來格式化分區(qū)的 mkfs 程序失敗。 ioctl BLKGETSIZE64 失敗。( BLKGET-SIZE64 在調(diào)用 ioctl的源代碼中定義。) BLKGETSIZE64 ioctl 將被添加到 Linux 中所有的設(shè)備,而在這里,邏輯卷管理器還不支持它。因此,如果BLKGETSIZE64 ioctl 調(diào)用失敗,mkfs 代碼將改為調(diào)用較早的 ioctl 調(diào)用;這使得 mkfs 適用于邏輯卷管理器。 OOPSOOPS(也稱 Panic)消息包含系統(tǒng)錯誤的細(xì)節(jié),如 CPU 寄存器的內(nèi)容等。是內(nèi)核告知用戶有不幸發(fā)生的最常用的方式。 內(nèi)核只能發(fā)布OOPS,這個過程包括向終端上輸出錯誤消息,輸出寄存器保存的信息,并輸出可供跟蹤的回溯線索。通常,發(fā)送完OOPS之后,內(nèi)核會處于一種不穩(wěn)定的狀態(tài)。 ※ 作為內(nèi)核的開發(fā)者,必定將會經(jīng)常處理OOPS。 ※ OOPS中包含的重要信息,對所有體系結(jié)構(gòu)的機(jī)器都是完全相同的:寄存器上下文和回溯線索(回溯線索顯示了導(dǎo)致錯誤發(fā)生的函數(shù)調(diào)用鏈)。 ksymoops 在 Linux 中,調(diào)試系統(tǒng)崩潰的傳統(tǒng)方法是分析在發(fā)生崩潰時發(fā)送到系統(tǒng)控制臺的 Oops 消息。一旦您掌握了細(xì)節(jié),就可以將消息發(fā)送到 ksymoops 使用程序,它將試圖將代碼轉(zhuǎn)換為指令并將堆棧值映射到內(nèi)核符號。 ※ 如:回溯線索中的地址,會通過ksymoops轉(zhuǎn)化成名稱可見的函數(shù)名。 ksymoops需要幾項內(nèi)容:Oops 消息輸出、來自正在運行的內(nèi)核的 System.map 文件,還有 /proc/ksyms、vmlinux和/proc/modules。 關(guān)于如何使用 ksymoops,內(nèi)核源代碼 清單 7. ksymoops 處理后的 Oops 消息 ksymoops 2.4.0 on i686 2.4.17. Options used... 15:59:37 sfb1 kernel: Unable to handle kernel NULL pointer dereference atvirtual address 0000000... 15:59:37 sfb1 kernel: c01588fc... 15:59:37 sfb1 kernel: *pde = 0000000... 15:59:37 sfb1 kernel: Oops: 0000... 15:59:37 sfb1 kernel: CPU: 0... 15:59:37 sfb1 kernel: EIP: 0010:[jfs_mount+60/704]... 15:59:37 sfb1 kernel: Call Trace: [jfs_read_super+287/688] [get_sb_bdev+563/736] [do_kern_mount+189/336] [do_add_mount+35/208][do_page_fault+0/1264]... 15:59:37 sfb1 kernel: Call Trace: []...... 15:59:37 sfb1 kernel: [>EIP; c01588fc <=====...Trace; c0106cf3 33/40>Code; c01588fc 00000000 <_EIP>:Code; c01588fc <===== 0: 8b 2d 00 00 00 00 mov 0x0,%ebp <=====Code; c0158902 42/2c0> 6: 55 push %ebp 接下來,您要確定 jfs_mount 中的哪一行代碼引起了這個問題。Oops 消息告訴我們問題是由位于偏移地址 3c 的指令引起的。做這件事的辦法之一就是對 jfs_mount.o 文件使用 objdump 使用程序,然后查看偏移地址 3c。Objdump 用來反匯編模塊函數(shù),看看您的 C 源代碼會產(chǎn)生什么匯編指令。清單 8 顯示了使用 objdump 后您將看到的內(nèi)容,接著,我們查看jfs_mount 的 C 代碼,可以看到空值是第 109 行引起的。偏移地址 3c 之所以這么重要,是因為 Oops 消息將該處標(biāo)識為引起問題的位置。 清單 8. jfs_mount 匯編程序清單 109 printk('%d ',*ptr);objdump jfs_mount.ojfs_mount.o: file format elf32-i386Disassembly of section .text:00000000 : 0:55 push %ebp ... 2c: e8 cf 03 00 00 call 400 31: 89 c3 mov %eax,%ebx 33: 58 pop %eax 34: 85 db test %ebx,%ebx 36: 0f 85 55 02 00 00 jne 291 0x291> 3c: 8b 2d 00 00 00 00 mov 0x0,%ebp << problem line above 42: 55 push %ebp kallsyms 開發(fā)版2.5內(nèi)核引入了kallsyms特性,它可以通過定義CONFIG_KALLSYMS編譯選項啟用。該選項可以載入內(nèi)核鏡像所對應(yīng)的內(nèi)存地址的符號名稱(即函數(shù)名),所以內(nèi)核可以打印解碼之后的跟蹤線索。相應(yīng),解碼OOPS也不再需要System.map和ksymoops工具了。另外, 這樣做,會使內(nèi)核變大些,因為地址對應(yīng)符號名稱必須始終駐留在內(nèi)核所在內(nèi)存上。 #cat /proc/kallsyms c0100240 T _stext c0100240 t run_init_process c0100240 T stext c0100269 t init … Kdump什么是 kexec ? Kexec 是實現(xiàn) kdump 機(jī)制的關(guān)鍵,它包括 2 一是組成部分:一是內(nèi)核空間的系統(tǒng)調(diào)用 kexec_load,負(fù)責(zé)在生產(chǎn)內(nèi)核(production kernel 或 first kernel)啟動時將捕獲內(nèi)核(capture kernel 或 sencond kernel)加載到指定地址。二是用戶空間的工具 kexec-tools,他將捕獲內(nèi)核的地址傳遞給生產(chǎn)內(nèi)核,從而在系統(tǒng)崩潰的時候能夠找到捕獲內(nèi)核的地址并運行。沒有 kexec 就沒有 kdump。先有 kexec 實現(xiàn)了在一個內(nèi)核中可以啟動另一個內(nèi)核,才讓 kdump 有了用武之地。kexec 原來的目的是為了節(jié)省時間 kernel 開發(fā)人員重啟系統(tǒng)的時間,誰能想到這個“偷懶”的技術(shù)卻孕育了最成功的內(nèi)存轉(zhuǎn)存機(jī)制呢? 什么是 kdump ? Kdump 的概念出現(xiàn)在 2005 左右,是迄今為止最可靠的內(nèi)核轉(zhuǎn)存機(jī)制,已經(jīng)被主要的 linux? 廠商選用。kdump是一種先進(jìn)的基于 kexec 的內(nèi)核崩潰轉(zhuǎn)儲機(jī)制。當(dāng)系統(tǒng)崩潰時,kdump 使用 kexec 啟動到第二個內(nèi)核。第二個內(nèi)核通常叫做捕獲內(nèi)核,以很小的內(nèi)存啟動以捕獲轉(zhuǎn)儲鏡像。第一個內(nèi)核保留了內(nèi)存的一部分給第二個內(nèi)核啟動用。由于 kdump 利用 kexec 啟動捕獲內(nèi)核,繞過了 BIOS,所以第一個內(nèi)核的內(nèi)存得以保留。這是內(nèi)核崩潰轉(zhuǎn)儲的本質(zhì)。 kdump 需要兩個不同目的的內(nèi)核,生產(chǎn)內(nèi)核和捕獲內(nèi)核。生產(chǎn)內(nèi)核是捕獲內(nèi)核服務(wù)的對象。捕獲內(nèi)核會在生產(chǎn)內(nèi)核崩潰時啟動起來,與相應(yīng)的 ramdisk 一起組建一個微環(huán)境,用以對生產(chǎn)內(nèi)核下的內(nèi)存進(jìn)行收集和轉(zhuǎn)存。 如何使用 kdump 構(gòu)建系統(tǒng)和 dump-capture 內(nèi)核,此操作有 2 種方式可選: 1)構(gòu)建一個單獨的自定義轉(zhuǎn)儲捕獲內(nèi)核以捕獲內(nèi)核轉(zhuǎn)儲; 2) 或者將系統(tǒng)內(nèi)核本身作為轉(zhuǎn)儲捕獲內(nèi)核,這就不需要構(gòu)建一個單獨的轉(zhuǎn)儲捕獲內(nèi)核。 方法(2)只能用于可支持可重定位內(nèi)核的體系結(jié)構(gòu)上;目前 i386,x86_64,ppc64 和 ia64 體系結(jié)構(gòu)支持可重定位內(nèi)核。構(gòu)建一個可重定位內(nèi)核使得不需要構(gòu)建第二個內(nèi)核就可以捕獲轉(zhuǎn)儲。但是可能有時想構(gòu)建一個自定義轉(zhuǎn)儲捕獲內(nèi)核以滿足特定要求。 如何訪問捕獲內(nèi)存 在內(nèi)核崩潰之前所有關(guān)于核心映像的必要信息都用 ELF 格式編碼并存儲在保留的內(nèi)存區(qū)域中。ELF 頭所在的物理地址被作為命令行參數(shù)(fcorehdr=)傳遞給新啟動的轉(zhuǎn)儲內(nèi)核。 在 i386 體系結(jié)構(gòu)上,啟動的時候需要使用物理內(nèi)存開始的 640K,而不管操作系統(tǒng)內(nèi)核轉(zhuǎn)載在何處。因此,這個640K 的區(qū)域在重新啟動第二個內(nèi)核的時候由 kexec 備份。 在第二個內(nèi)核中,“前一個系統(tǒng)的內(nèi)存”可以通過兩種方式訪問: 1) 通過 /dev/oldmem 這個設(shè)備接口。 一個“捕捉”設(shè)備可以使用“raw”(裸的)方式 “讀”這個設(shè)備文件并寫出到文件。這是關(guān)于內(nèi)存的 “裸”的數(shù)據(jù)轉(zhuǎn)儲,同時這些分析 / 捕捉工具應(yīng)該足夠“智能”從而可以知道從哪里可以得到正確的信息。ELF 文件頭(通過命令行參數(shù)傳遞過來的 elfcorehdr)可能會有幫助。 2) 通過 /proc/vmcore。 這個方式是將轉(zhuǎn)儲輸出為一個 ELF 格式的文件,并且可以使用一些文件拷貝命令(比如 cp,scp 等)將信息讀出來。同時,gdb 可以在得到的轉(zhuǎn)儲文件上做一些調(diào)試(有限的)。這種方式保證了內(nèi)存中的頁面都以正確的途徑被保存 ( 注意內(nèi)存開始的 640K 被重新映射了 )。 kdump 的優(yōu)勢 1) 高可靠性 崩潰轉(zhuǎn)儲數(shù)據(jù)可從一個新啟動內(nèi)核的上下文中獲取,而不是從已經(jīng)崩潰內(nèi)核的上下文。 2) 多版本支持 LKCD(Linux Kernel Crash Dump),netdump,diskdump 已被納入 LDPs(Linux Documen-tation Project) 內(nèi)核。SUSE 和 RedHat 都對 kdump 有技術(shù)支持。 配置 kdump 安裝軟件包和實用程序 Kdump 用到的各種工具都在 kexec-tools 中。kernel-debuginfo 則是用來分析 vmcore 文件。從 rhel5 開始,kexec-tools 已被默認(rèn)安裝在發(fā)行版。而 novell 也在 sles10 發(fā)行版中把 kdump 集成進(jìn)來。所以如果使用的是rhel5 和 sles10 之后的發(fā)行版,那就省去了安裝 kexec-tools 的步驟。而如果需要調(diào)試 kdump 生成的 vmcore文件,則需要手動安裝 kernel-debuginfo 包。檢查安裝包操作: 3.3.2 參數(shù)相關(guān)設(shè)置 uli13lp1:/ # rpm -qa|grep kexec kexec-tools-2.0.0-53.43.10 uli13lp1:/ # rpm -qa 'kernel*debuginfo*' kernel-default-debuginfo-3.0.13-0.27.1 kernel-ppc64-debuginfo-3.0.13-0.27.1 系統(tǒng)內(nèi)核設(shè)置選項和轉(zhuǎn)儲捕獲內(nèi)核配置選擇在《使用 Crash 工具分析 Linux dump 文件》一文中已有說明,在此不再贅述。僅列出內(nèi)核引導(dǎo)參數(shù)設(shè)置以及配置文件設(shè)置。 1) 修改內(nèi)核引導(dǎo)參數(shù),為啟動捕獲內(nèi)核預(yù)留內(nèi)存 通過下面的方法來配置 kdump 使用的內(nèi)存大小。添加啟動參數(shù)'crashkernel=Y@X',這里,Y 是為 kdump 捕捉內(nèi)核保留的內(nèi)存,X 是保留部分內(nèi)存的開始位置。 對于 i386 和 x86_64, 編輯 /etc/grub.conf, 在內(nèi)核行的最后添加'crashkernel=128M' 。 對于 ppc64,在 /etc/yaboot.conf 最后添加'crashkernel=128M'。 在 ia64, 編輯 /etc/elilo.conf,添加'crashkernel=256M'到內(nèi)核行。 2) kdump 配置文件 kdump 的配置文件是 /etc/kdump.conf(RHEL6.2);/etc/sysconfig/kdump(SLES11 sp2)。每個文件頭部都有選項說明,可以根據(jù)使用需求設(shè)置相應(yīng)的選項。 啟動 kdump 服務(wù) 在設(shè)置了預(yù)留內(nèi)存后,需要重啟機(jī)器,否則 kdump 是不可使用的。啟動 kdump 服務(wù): Rhel6.2: # chkconfig kdump on # service kdump status Kdump is operational # service kdump start SLES11SP2: # chkconfig boot.kdump on # service boot.kdump start 測試配置是否有效 可以通過 kexec 加載內(nèi)核鏡像,讓系統(tǒng)準(zhǔn)備好去捕獲一個崩潰時產(chǎn)生的 vmcore??梢酝ㄟ^ sysrq 強(qiáng)制系統(tǒng)崩潰。 # echo c > /proc/sysrq-trigger 這造成內(nèi)核崩潰,如配置有效,系統(tǒng)將重啟進(jìn)入 kdump 內(nèi)核,當(dāng)系統(tǒng)進(jìn)程進(jìn)入到啟動 kdump 服務(wù)的點時,vmcore 將會拷貝到你在 kdump 配置文件中設(shè)置的位置。RHEL 的缺省目錄是 : /var/crash;SLES 的缺省目錄是 : /var/log/dump。然后系統(tǒng)重啟進(jìn)入到正常的內(nèi)核。一旦回復(fù)到正常的內(nèi)核,就可以在上述的目錄下發(fā)現(xiàn) vmcore 文件,即內(nèi)存轉(zhuǎn)儲文件。可以使用之前安裝的 kernel-debuginfo 中的 crash 工具來進(jìn)行分析(crash 的更多詳細(xì)用法將在本系列后面的文章中有介紹)。 # crash /usr/lib/debug/lib/modules/2.6.17-1.2621.el5/vmlinux /var/crash/2006-08-23-15:34/vmcore crash> bt 載入“轉(zhuǎn)儲捕獲”內(nèi)核 需要引導(dǎo)系統(tǒng)內(nèi)核時,可使用如下步驟和命令載入“轉(zhuǎn)儲捕獲”內(nèi)核: kexec -p --initrd=for-dump-capture-kernel> --args-linux --append='root= init 1 irqpoll' 裝載轉(zhuǎn)儲捕捉內(nèi)核的注意事項: 轉(zhuǎn)儲捕捉內(nèi)核應(yīng)當(dāng)是一個 vmlinux 格式的映像(即是一個未壓縮的 ELF 映像文件),而不能是 bzImage 格式; 默認(rèn)情況下,ELF 文件頭采用 ELF64 格式存儲以支持那些擁有超過 4GB 內(nèi)存的系統(tǒng)。但是可以指定“--elf32-core-headers”標(biāo)志以強(qiáng)制使用 ELF32 格式的 ELF 文件頭。這個標(biāo)志是有必要注意的,一個重要的原因就是:當(dāng)前版本的 GDB 不能在一個 32 位系統(tǒng)上打開一個使用 ELF64 格式的 vmcore 文件。ELF32 格式的文件頭不能使用在一個“沒有物理地址擴(kuò)展”(non-PAE)的系統(tǒng)上(即:少于 4GB 內(nèi)存的系統(tǒng)); 一個“irqpoll”的啟動參數(shù)可以減低由于在“轉(zhuǎn)儲捕獲內(nèi)核”中使用了“共享中斷”技術(shù)而導(dǎo)致出現(xiàn)驅(qū)動初始化失敗這種情況發(fā)生的概率 ; 必須指定 ,指定的格式是和要使用根設(shè)備的名字。具體可以查看 mount 命令的輸出;“init 1”這個命令將啟動“轉(zhuǎn)儲捕捉內(nèi)核”到一個沒有網(wǎng)絡(luò)支持的單用戶模式。如果你希望有網(wǎng)絡(luò)支持,那么使用“init 3”。 后記 Kdump 是一個強(qiáng)大的、靈活的內(nèi)核轉(zhuǎn)儲機(jī)制,能夠在生產(chǎn)內(nèi)核上下文中執(zhí)行捕獲內(nèi)核是非常有價值的。本文僅介紹在 RHEL6.2 和 SLES11 中如何配置 kdump。望拋磚引玉,對閱讀本文的讀者有益。 |
|
來自: mynotebook > 《待分類》