歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

Linux內核調試技術之printk

1、簡介(基於s3c2440 linux)

在內核調試技術之中,最簡單的就是printk的使用了,它的用法和C語言應用程序中的printf使用類似,在應用程序中依靠的是stdio.h中的庫,而在linux內核中沒有這個庫,所以在linux內核中,使用這個printk就要對內核的實現有一定的了解。

printf和printk的區別:printk會在開頭處加上"<N>"樣式的字符,N的范圍是0~7,表示這個信息的級別。

當printk("<n>"......);中的n < console_loglevel 時候,這個信息才能被打印出來。 

在內核文件中Printk.c (kernel) 中初始化這個console_loglevel 的級別為7.

/* 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 */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 */
};

2、通過命令cat /proc/sys/kernel/printk查看等級配置:

# cat /proc/sys/kernel/printk
7 4 1 7

其中的 7 4 1 7,分別對應與:console_loglevel、default_message_loglevel、minimum_console_loglevel、default_console_loglevel 

3、修改等級配置:

#echo "1 4 1 7">/proc/sys/kernel/printk 改變這四個值,當被console_loglevel設置為1的時候,所有的調試信息都會被打印出來。

4、printk函數記錄的名稱及使用

 在內核文件:Kernel.h (include\linux) 定義了0~7這8個級別的名稱

#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 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])

使用printk:

① printk(KERN_WARNING"there is a warning here!\n");//注意,中間沒有逗號間隔。

② printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

在①和②中,我們需要查看緩沖區log_buf中的數據,才能看到打印出來的信息。需要使用命令 #dmesg

1 2 3 4 5 6 7 8 9 10 11 12 # # dmesg Linux version 2.6.22.6 (book@book-desktop) (gcc version 3.4.5) #19 Thu Dec 8 14:06:03 CST 2016 CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177 Machine: SMDK2440 Memory policy: ECC disabled, Data cache writeback On node 0 totalpages: 16384   DMA zone: 128 pages used for memmap   DMA zone: 0 pages reserved   DMA zone: 16256 pages, LIFO batch:3   Normal zone: 0 pages used for memmap CPU S3C2440A (id 0x32440001)<br>................................

還可以使用 cat /proc/kmsg& 後台運行,實時打印出調試信息。在②中,__FILE__, __FUNCTION__, __LINE__分別對應的是那個文件,那個函數,第幾行。非常實用。

1 2 3 4 5 6 7 8 9 # cat /proc/kmsg& # ./firstdrvtest on <7>/work/LinuxDrives/20.printk_debug/first_drv.c first_drv_open 23 <7>/work/LinuxDrives/20.printk_debug/first_drv.c first_drv_open 25 <7>/work/LinuxDrives/20.printk_debug/first_drv.c first_drv_open 27 # ./firstdrvtest off <7>/work/LinuxDrives/20.printk_debug/first_drv.c first_drv_open 23 <7>/work/LinuxDrives/20.printk_debug/first_drv.c first_drv_open 25 <7>/work/LinuxDrives/20.printk_debug/first_drv.c first_drv_open 27

5、串口與printk函數的關系:   

printk
    vprintk
        vscnprintf //先把輸出信息放入臨時BUFFER
                // Copy the output into log_buf.
                // 把臨時BUFFER裡的數據稍作處理,再寫入log_buf
                // 比如printk("abc")會得到"<4>abc", 再寫入log_buf
                // 可以用dmesg命令把log_buf裡的數據打印出來重現內核的輸出信息

        // 調用硬件的write函數輸出
        release_console_sem    
            call_console_drivers
          //從log_buf得到數據,算出打印級別 _call_console_drivers if ((msg_log_level < console_loglevel)//如果級別足夠則打印 __call_console_drivers con->write //con是console_drivers鏈表中的項,對用具體的輸出函數 在drives/serial/s3c2410.c中查看

在該函數中注冊一個console結構體
static void s3c24xx_serial_console_write(struct console *co, const char *s,
unsigned int count)
{
uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar);
}

串口輸出函數,會調用s3c24xx_serial_console_putchar函數

static int s3c24xx_serial_initconsole(void)
{
    ...........................
    register_console(&s3c24xx_serial_console);
    return 0;
}
static struct console s3c24xx_serial_console =
{
.name = S3C24XX_SERIAL_NAME,//這個宏被定義為:TTYSAC
.device = uart_console_device, //init進程,用戶程序打開/dev/console的時候會調用
.flags = CON_PRINTBUFFER,//打印還沒有初始化化console前保存在log_buf裡面的數據
.index = -1,//選擇那個串口,由uboot中的命令決定
.write = s3c24xx_serial_console_write,//控制台輸出函數
.setup = s3c24xx_serial_console_setup //串口設置函數
}; 

這個函數和硬件相關,讀取寄存器,看數據是否被發送完成,最後將數據一個字節一個字節的寫入寄存器,s3c2440的串口控制器會自動把數據發送出去

static void s3c24xx_serial_console_putchar(struct uart_port *port, int ch)

{
unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON);
while (!s3c24xx_serial_console_txrdy(port, ufcon))
barrier();
wr_regb(cons_uart, S3C2410_UTXH, ch);
}

uboot的console=ttySAC0如果在linux中被識別出來是串口0的,在/kernel/printk.c中有一下代碼,

__setup("console=", console_setup);

static int __init console_setup(char *str)
{
........
  add_preferred_console(name, idx, options);

內核開始執行時,會發現“console=...”的命令行參數時候,就會調用console_setup函數進行數據解析,對於命令行參數"console=ttySAC0",會解析出:設備名(name)為ttySAC,索引index為0,這些信息被保存在類型為console_cmdline、名稱為console_cmdline的全局數組中(注意:數組名和數組的類型相同)

在後面的register_console(&s3c24xx_serial_console);的時候,會將s3c24xx_serial_console結構和console_cmdline數組中的設備進行比較。

①解析出來的name為S3C24XX_SERIAL_NAME 既“ttySAC”,而console_cmdline中的命令中的name也為“ttySAC”

②s3c24xx_serial_console結構體中的索引index為-1,表示使用命令行參數“console=ttySAC0”中的索引,index =  0

如果還想了解uboot的console=ttySAC0如果在linux中被識別出來是串口0的,可以看《嵌入式Linux開發完全手冊》Page 365  PDF下載見 http://www.linuxidc.com/Linux/2011-01/31114.htm

Copyright © Linux教程網 All Rights Reserved