驅動程序調試方法之printk――printk的原理與直接使用
睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接
1、基本原理
(1)在UBOOT里設置console=ttySAC0或者console=tty1
這里是設置控制終端,tySAC0 表示串口, tty1 表示lcd
(2)內核用printk打印 內核就會根據命令行參數來找到對應的硬件操作函數,并將信息通過對應的硬件終端打印出來! 2、printk的使用 (1)printk函數的信息如何才能在終端顯示出來 在內核代碼include/linux/kernel.h中,定義了控制臺的級別: extern int console_printk[]; #define console_loglevel (console_printk[0]) #define default_message_loglevel (console_printk[1]) #define minimum_console_loglevel (console_printk[2]) #define default_console_loglevel (console_printk[3]) 我們在到kernel/printk.c里找到console_printk的定義: /* printk's without a loglevel use this.. */ #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */ /* We show everything that is MORE important than this.. */ #define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */ #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */ DECLARE_WAIT_QUEUE_HEAD(log_wait); int console_printk[4] = { DEFAULT_CONSOLE_LOGLEVEL, /* console_loglevel */ DEFAULT_MESSAGE_LOGLEVEL, /* default_message_loglevel */ MINIMUM_CONSOLE_LOGLEVEL, /* minimum_console_loglevel */ DEFAULT_CONSOLE_LOGLEVEL, /* default_console_loglevel */ }; 于是我們知道控制臺的級別是: 7 4 1 7 我們當然可以再這里修改,但是還有一個更簡單的修改方法,即在用戶空間使用下面的命令: echo “1 4 1 7” > /proc/sys/kernel/printk 將1 4 1 7寫入 /proc/sys/kernel/printk即可! 當我們使用printk函數時往往要加上信息級別,比如: printk(KERN_WARNING"there is a warning here!\n") 其中KERN_WARNING就表示信息的級別,相關宏在函數include/linux/kernel.h中: #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 default_message_loglevel (console_printk[1]) 沒有改動的情況下是4 上面我們說到了信息級別和控制臺級別,下面我們要說到重點了!當信息級別的數值小于控制臺的級別時,printk要打印的信息才會在終端打印出來,否則不會顯示在終端! (2)串口控制臺 printk vprintk(fmt, args); vscnprintf(printk_buf, sizeof(printk_buf), fmt, args); vsnprintf(buf,size,fmt,args);//先把輸出信息輸入到臨時buffer //把臨時buffer里面的數據稍作處理,寫入log_buffer //可以將信息級別與信息合并 //在用戶空間使用命令dmesg可以把log_buffer里面的數據打印出來 release_console_sem(); call_console_drivers(_con_start, _log_end); _call_console_drivers(start_print, cur_index, msg_level); __call_console_drivers(start, end); con->write(con, &LOG_BUF(start), end - start);//調用具體的輸出函數 這個輸出函數要把數據從串口輸出的話,肯定要調用到串口硬件相關的函數 我們到文件:drivers/serial/s3c2410.c里面去這里有個串口初始化函數: s3c24xx_serial_initconsole register_console(&s3c24xx_serial_console); 我們來看看它的注冊函數: static struct console s3c24xx_serial_console = { .name = S3C24XX_SERIAL_NAME, .device = uart_console_device, .flags = CON_PRINTBUFFER, .index = -1, .write = s3c24xx_serial_console_write, .setup = s3c24xx_serial_console_setup }; 里面的確有個write函數! 但是我們還不知道printk選擇的控制臺為什么是串口呢? 我們知道uboot傳入了參數:console=ttySAC0 或者 console=tty1 內核就通過如下的函數來處理傳入的參數: __setup("console=", console_setup); 這是個宏 ,它的作用就是用函數console_setup來處理我們傳入的參數 console_ setup //先解碼字符串為:name, idx, options,然后就使用這些:name, idx, options add_preferred_console(name, idx, options); strcmp(console_cmdline[i].name, name) console_cmdline[i].index == idx //將索引和名字都記錄在了console_cmdline數組中了 我們記住這個數組,回過頭來再來看: register_console(&s3c24xx_serial_console); if (strcmp(console_cmdline[i].name, console->name) != 0) continue; 我們看到在注冊串口控制臺的時候,會將串口控制臺的名字與uboot傳進來的參數相比較,一旦匹配才會注冊。這樣的話,只有與uboot傳進來的控制臺參數相一致的控制臺才能注冊成功。那么也就是說,printk會通過uboot設置的控制臺的write函數,將信息打印出來! 另外還有一點需要牢記的就是,printk輸出的信息會先保存在緩沖區log_buf中,所以我們當然可以通過查看log_buf來看輸出信息了!而這個查看命令就是:dmesg 實際上,dmesg這個命令的作用就是去讀/proc/kmsg這個文件。也就是說log_buf里面的內容是存放在/proc/kmsg這個文件里面的!
(2)內核用printk打印 內核就會根據命令行參數來找到對應的硬件操作函數,并將信息通過對應的硬件終端打印出來! 2、printk的使用 (1)printk函數的信息如何才能在終端顯示出來 在內核代碼include/linux/kernel.h中,定義了控制臺的級別: extern int console_printk[]; #define console_loglevel (console_printk[0]) #define default_message_loglevel (console_printk[1]) #define minimum_console_loglevel (console_printk[2]) #define default_console_loglevel (console_printk[3]) 我們在到kernel/printk.c里找到console_printk的定義: /* printk's without a loglevel use this.. */ #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */ /* We show everything that is MORE important than this.. */ #define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */ #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */ DECLARE_WAIT_QUEUE_HEAD(log_wait); int console_printk[4] = { DEFAULT_CONSOLE_LOGLEVEL, /* console_loglevel */ DEFAULT_MESSAGE_LOGLEVEL, /* default_message_loglevel */ MINIMUM_CONSOLE_LOGLEVEL, /* minimum_console_loglevel */ DEFAULT_CONSOLE_LOGLEVEL, /* default_console_loglevel */ }; 于是我們知道控制臺的級別是: 7 4 1 7 我們當然可以再這里修改,但是還有一個更簡單的修改方法,即在用戶空間使用下面的命令: echo “1 4 1 7” > /proc/sys/kernel/printk 將1 4 1 7寫入 /proc/sys/kernel/printk即可! 當我們使用printk函數時往往要加上信息級別,比如: printk(KERN_WARNING"there is a warning here!\n") 其中KERN_WARNING就表示信息的級別,相關宏在函數include/linux/kernel.h中: #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 default_message_loglevel (console_printk[1]) 沒有改動的情況下是4 上面我們說到了信息級別和控制臺級別,下面我們要說到重點了!當信息級別的數值小于控制臺的級別時,printk要打印的信息才會在終端打印出來,否則不會顯示在終端! (2)串口控制臺 printk vprintk(fmt, args); vscnprintf(printk_buf, sizeof(printk_buf), fmt, args); vsnprintf(buf,size,fmt,args);//先把輸出信息輸入到臨時buffer //把臨時buffer里面的數據稍作處理,寫入log_buffer //可以將信息級別與信息合并 //在用戶空間使用命令dmesg可以把log_buffer里面的數據打印出來 release_console_sem(); call_console_drivers(_con_start, _log_end); _call_console_drivers(start_print, cur_index, msg_level); __call_console_drivers(start, end); con->write(con, &LOG_BUF(start), end - start);//調用具體的輸出函數 這個輸出函數要把數據從串口輸出的話,肯定要調用到串口硬件相關的函數 我們到文件:drivers/serial/s3c2410.c里面去這里有個串口初始化函數: s3c24xx_serial_initconsole register_console(&s3c24xx_serial_console); 我們來看看它的注冊函數: static struct console s3c24xx_serial_console = { .name = S3C24XX_SERIAL_NAME, .device = uart_console_device, .flags = CON_PRINTBUFFER, .index = -1, .write = s3c24xx_serial_console_write, .setup = s3c24xx_serial_console_setup }; 里面的確有個write函數! 但是我們還不知道printk選擇的控制臺為什么是串口呢? 我們知道uboot傳入了參數:console=ttySAC0 或者 console=tty1 內核就通過如下的函數來處理傳入的參數: __setup("console=", console_setup); 這是個宏 ,它的作用就是用函數console_setup來處理我們傳入的參數 console_ setup //先解碼字符串為:name, idx, options,然后就使用這些:name, idx, options add_preferred_console(name, idx, options); strcmp(console_cmdline[i].name, name) console_cmdline[i].index == idx //將索引和名字都記錄在了console_cmdline數組中了 我們記住這個數組,回過頭來再來看: register_console(&s3c24xx_serial_console); if (strcmp(console_cmdline[i].name, console->name) != 0) continue; 我們看到在注冊串口控制臺的時候,會將串口控制臺的名字與uboot傳進來的參數相比較,一旦匹配才會注冊。這樣的話,只有與uboot傳進來的控制臺參數相一致的控制臺才能注冊成功。那么也就是說,printk會通過uboot設置的控制臺的write函數,將信息打印出來! 另外還有一點需要牢記的就是,printk輸出的信息會先保存在緩沖區log_buf中,所以我們當然可以通過查看log_buf來看輸出信息了!而這個查看命令就是:dmesg 實際上,dmesg這個命令的作用就是去讀/proc/kmsg這個文件。也就是說log_buf里面的內容是存放在/proc/kmsg這個文件里面的!