驅動程序調試方法之printk——printk的原理與直接使用


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這個文件里面的!


(3)使用printk
方法一:
我們可以再內核中使用如下打印語句:
#define DEG_PRINTK printk
//#define DEG_PRINTK(x...)
 
DEG_PRINTK("%s %s %d\n",_FILE_,_FUNCTION_,_LINE_);
這行打印語句的意思就是講本行代碼所在的文件的名(包括路徑)、所在的函數、所在的行打印出來!
當我們需要調試的時候,就使用 #define DEG_PRINTK printk這個宏,當不需要調試的時候,就使用 # define DEG_PRINTK(x...)這個宏。其中#define DEG_PRINTK(x...)里面的"..."的意思是DEG_PRINTK的參數是可變的!
當代碼比較少的時候,我們可以在每一行都加上這個打印語句,這樣很容易就會發現錯誤的位置!
當代碼比較多的時候,我們可以采用對半查找的方法!先在代碼中間加上打印語句!然后判斷出錯位置在打印語句之前還是之后,如果出現在之前,就在之前的代碼代碼里面再次采用對半查找!
 
方法二:
上面我們說到過,要打印的信息會存放在log_buf中,通過文件/proc/kmsg可以來訪問這個buf,然后將信息打印出來。由此我們就想了,我們是否可以構造這樣一個mylog_buf,里面存放我們所需要的打印信息,通過一個/proc/kmsg文件可以訪問該buf,然后打印出來?答案是肯定的!下面我們就一步步來完成!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM