學習編寫驅動程序要構建安裝自己的內核(標準主線內核)。最重要的原因之一是:內核開發者已經建立了多項用於調試的功能。但是由於這些功能會造成額外的輸出,並導致能下降,因此發行版廠商通常會禁止發行版內核中的調試功能。
1 內核配置
爲了實現內核調試,在內核配置上增加了幾項:
Kernel hacking --->
[*] Magic SysRq key
[*] Kernel debugging
[*] Debug slab memory allocations
[*] Spinlock and rw-lock debugging: basic checks
[*] Spinlock debugging: sleep-inside-spinlock checking
[*] Compile the kernel with debug info
Device Drivers --->
Generic Driver Options --->
[*] Driver Core verbose debug messages
General setup --->
[*] Configure standard kernel features (for small systems) --->
[*] Load all symbols for debugging/ksymoops
啓用選項例如:
slab layer debugging(slab層調試選項)
high-memory debugging(高端內存調試選項)
I/O mapping debugging(I/O映射調試選項)
spin-lock debugging(自旋鎖調試選項)
stack-overflow checking(棧溢出檢查選項)
sleep-inside-spinlock checking(自旋鎖內睡眠選項)
2 調試原子操作
從內核2.5開發,爲了檢查各類由原子操作引發的問題,內核提供了極佳的工具。
內核提供了一個原子操作計數器,它可以配置成,一旦在原子操作過程中,進城進入睡眠或者做了一些可能引起睡眠的操作,就打印警告信息並提供追蹤線索。
所以,包括在使用鎖的時候調用schedule(),正使用鎖的時候以阻塞方式請求分配內存等,各種潛在的bug都能夠被探測到。
下面這些選項可以最大限度地利用該特性:
CONFIG_PREEMPT = y
CONFIG_DEBUG_KERNEL = y
CONFIG_KLLSYMS = y
CONFIG_SPINLOCK_SLEEP = y
四 引發bug並打印信息
1 BUG()和BUG_ON()
一些內核調用可以用來方便標記bug,提供斷言並輸出信息。最常用的兩個是BUG()和BUG_ON()。
定義在<include/asm-generic>中:
#ifndef HAVE_ARCH_BUG
#define BUG() do {
printk("BUG: failure at %s:%d/%s()! ", __FILE__, __LINE__, __FUNCTION__);
panic("BUG!"); /* 引發更嚴重的錯誤,不但打印錯誤消息,而且整個系統業會掛起 */
} while (0)
#endif
#ifndef HAVE_ARCH_BUG_ON
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while(0)
#endif
當調用這兩個宏的時候,它們會引發OOPS,導致棧的回溯和錯誤消息的打印。
※ 可以把這兩個調用當作斷言使用,如:BUG_ON(bad_thing);
2 dump_stack()
有些時候,只需要在終端上打印一下棧的回溯信息來幫助你調試。這時可以使用dump_stack()。這個函數只在終端上打印寄存器上下文和函數的跟蹤線索。
if (!debug_check) {
printk(KERN_DEBUG “provide some information…/n”);
dump_stack();
}
3 LOG等級
printk和printf一個主要的區別就是前者可以指定一個LOG等級。內核根據這個等級來判斷是否在終端上打印消息。內核把比指定等級高的所有消息顯示在終端。
可以使用下面的方式指定一個LOG級別:
printk(KERN_CRIT “Hello, world!\n”);
注意,第一個參數並不一個真正的參數,因爲其中沒有用於分隔級別(KERN_CRIT)和格式字符的逗號(,)。KERN_CRIT本身只是一個普通的字符串(事實上,它表示的是字符串 "<2>";表 1 列出了完整的日誌級別清單)。作爲預處理程序的一部分,C 會自動地使用一個名爲 字符串串聯 的功能將這兩個字符串組合在一起。組合的結果是將日誌級別和用戶指定的格式字符串包含在一個字符串中。
內核使用這個指定LOG級別與當前終端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 "<d>" /* Use the default kernel loglevel */
注意,如果調用者未將日誌級別提供給 printk,那麼系統就會使用默認值 KERN_WARNING "<4>"(表示只有KERN_WARNING 級別以上的日誌消息會被記錄)。由於默認值存在變化,所以在使用時最好指定LOG級別。有LOG級別的一個好處就是我們可以選擇性的輸出LOG。比如平時我們只需要打印KERN_WARNING級別以上的關鍵性LOG,但是調試的時候,我們可以選擇打印KERN_DEBUG等以上的詳細LOG。而這些都不需要我們修改代碼,只需要通過命令修改默認日誌輸出級別:
mtj@ubuntu :~$ cat /proc/sys/kernel/printk
4 4 1 7
mtj@ubuntu :~$ cat /proc/sys/kernel/printk_delay
0
mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit
5
mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit_burst
10
第一項定義了 printk
API 當前使用的日誌級別。這些日誌級別表示了控制檯的日誌級別、默認消息日誌級別、最小控制檯日誌級別和默認控制檯日誌級別。printk_delay 值表示的是 printk 消息之間的延遲毫秒數(用於提高某些場景的可讀性)。注意,這裏它的值爲 0,而它是不可以通過 /proc 設置的。printk_ratelimit 定義了消息之間允許的最小時間間隔(當前定義爲每 5 秒內的某個內核消息數)。消息數量是由 printk_ratelimit_burst 定義的(當前定義爲 10)。如果您擁有一個非正式內核而又使用有帶寬限制的控制檯設備(如通過串口),
那麼這非常有用。注意,在內核中,速度限制是由調用者控制的,而不是在printk 中實現的。如果一個 printk 用戶要求進行速度限制,那麼該用戶就需要調用printk_ratelimit 函數。
4 記錄緩衝區
內核消息都被保存在一個LOG_BUF_LEN大小的環形隊列中。
關於LOG_BUF_LEN定義:
#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
※ 變量CONFIG_LOG_BUF_SHIFT在內核編譯時由配置文件定義,對於i386平臺,其值定義如下(在linux26/arch/i386/defconfig中): CONFIG_LOG_BUF_SHIFT=18
記錄緩衝區操作:
① 消息被讀出到用戶空間時,此消息就會從環形隊列中刪除。
② 當消息緩衝區滿時,如果再有printk()調用時,新消息將覆蓋隊列中的老消息。
③ 在讀寫環形隊列時,同步問題很容易得到解決。
※ 這個紀錄緩衝區之所以稱爲環形,是因爲它的讀寫都是按照環形隊列的方式進行操作的。
本文來自:https://my.oschina.net/fgq611/blog/113249
http://blog.csdn.net/zxygww/article/details/49782181
http://blog.csdn.net/dumgeewang/article/details/7383460