1、基本原理
(1)在UBOOT里設置console=ttySAC0或者console=tty1
這里是設置控制終端,tySAC0 表示串口, tty1 表示lcd
(2)內核用printk打印
(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,然后打印出來?答案是肯定的!下面我們就一步步來完成!