日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux内核printk调试

發(fā)布時間:2025/3/15 linux 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux内核printk调试 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
linux內(nèi)核printk調(diào)試(摘錄《Linux安全體系分析與編程》)

1? printk及控制臺的日志級別

函數(shù)printk的使用方法和printf相似,用于內(nèi)核打印消息。printk根據(jù)日志級別(loglevel)對消息進行分類。

日志級別用宏定義,日志級別宏展開為一個字符串,在編譯時由預(yù)處理器將它和消息文本拼接成一個字符串,因此printk?函數(shù)中日志級別宏和格式字符串間不能有逗號。

下面是兩個printk的例子,一個用于打印調(diào)試信息,另一個用于打印臨界條件信息。

printk(KERN_DEBUG "Here I am: %s:%i/n", _ _FILE_ _, _ _LINE_ _);

printk(KERN_CRIT "I'm trashed; giving up on %p/n", ptr);

printk的日志級別定義如下(在linux26/includelinux/kernel.h中):

#defineKERN_EMERG"<0>"/*緊急事件消息,系統(tǒng)崩潰之前提示,表示系統(tǒng)不可用*/

#defineKERN_ALERT"<1>"/*報告消息,表示必須立即采取措施*/

#defineKERN_CRIT"<2>"/*臨界條件,通常涉及嚴重的硬件或軟件操作失敗*/

#defineKERN_ERR"<3>"/*錯誤條件,驅(qū)動程序常用KERN_ERR來報告硬件的錯誤*/

#defineKERN_WARNING"<4>"/*警告條件,對可能出現(xiàn)問題的情況進行警告*/

#defineKERN_NOTICE"<5>"/*正常但又重要的條件,用于提醒。常用于與安全相關(guān)的消息*/

#defineKERN_INFO"<6>"/*提示信息,如驅(qū)動程序啟動時,打印硬件信息*/

#defineKERN_DEBUG"<7>"/*調(diào)試級別的消息*/

?

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

日志級別的范圍是0~7,沒有指定日志級別的printk語句默認采用的級別是DEFAULT_ MESSAGE_LOGLEVEL,其定義列出如下(在linux26/kernel/printk.c中):

/*沒有定義日志級別的printk使用下面的默認級別*/

#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING?警告條件*/

內(nèi)核可把消息打印到當(dāng)前控制臺上,可以指定控制臺為字符模式的終端或打印機等。默認情況下,“控制臺”就是當(dāng)前的虛擬終端。

為了更好地控制不同級別的信息顯示在控制臺上,內(nèi)核設(shè)置了控制臺的日志級別console_loglevel。printk日志級別的作用是打印一定級別的消息,與之類似,控制臺只顯示一定級別的消息。

當(dāng)日志級別小于console_loglevel時,消息才能顯示出來。控制臺相應(yīng)的日志級別定義如下:

/*?顯示比這個級別更重發(fā)的消息*/

#define MINIMUM_CONSOLE_LOGLEVEL ?1   /*可以使用的最小日志級別*/

#define DEFAULT_CONSOLE_LOGLEVEL? 7 /*比KERN_DEBUG?更重要的消息都被打印*/

?

int console_printk[4] = {

DEFAULT_CONSOLE_LOGLEVEL,/*控制臺日志級別,優(yōu)先級高于該值的消息將在控制臺顯示*/

/*默認消息日志級別,printk沒定義優(yōu)先級時,打印這個優(yōu)先級以上的消息*/

DEFAULT_MESSAGE_LOGLEVEL,

/*最小控制臺日志級別,控制臺日志級別可被設(shè)置的最小值(最高優(yōu)先級)*/

MINIMUM_CONSOLE_LOGLEVEL,

DEFAULT_CONSOLE_LOGLEVEL,/*?默認的控制臺日志級別*/

};

如果系統(tǒng)運行了klogd和syslogd,則無論console_loglevel為何值,內(nèi)核消息都將追加到/var/log/messages中。如果klogd沒有運行,消息不會傳遞到用戶空間,只能查看/proc/kmsg。

變量console_loglevel的初始值是DEFAULT_CONSOLE_LOGLEVEL,可以通過sys_syslog系統(tǒng)調(diào)用進行修 改。調(diào)用klogd時可以指定-c開關(guān)選項來修改這個變量。如果要修改它的當(dāng)前值,必須先殺掉klogd,再加-c選項重新啟動它。
通過讀寫/proc/sys/kernel/printk文件可讀取和修改控制臺的日志級別。查看這個文件的方法如下:

#cat /proc/sys/kernel/printk

6???4??1???7

上面顯示的4個數(shù)據(jù)分別對應(yīng)控制臺日志級別、默認的消息日志級別、最低的控制臺日志級別和默認的控制臺日志級別。

可用下面的命令設(shè)置當(dāng)前日志級別:

# echo 8 > /proc/sys/kernel/printk

2? printk打印消息機制

在內(nèi)核中,函數(shù)printk將消息打印到環(huán)形緩沖區(qū)__log_buf中,并將消息傳給控制臺進行顯示。控制臺驅(qū)動程序根據(jù)控制臺的日志級別顯示日志消息。

應(yīng)用程序通過系統(tǒng)調(diào)用sys_syslog管理環(huán)形緩沖區(qū)__log_buf,它可以讀取數(shù)據(jù)、清除緩沖區(qū)、設(shè)置日志級別、開/關(guān)控制臺等。

當(dāng)系統(tǒng)調(diào)用sys_syslog從環(huán)形緩沖區(qū)__log_buf讀取數(shù)據(jù)時,如果緩沖區(qū)沒有數(shù)據(jù),系統(tǒng)調(diào)用sys_syslog所在進程將被加入到 等待隊列l(wèi)og_wait中進行等待。當(dāng)printk將數(shù)據(jù)打印到緩沖區(qū)后,將喚醒系統(tǒng)調(diào)用sys_syslog所在進程從緩沖區(qū)中讀取數(shù)據(jù)。等待隊列?log_wait定義如下:

DECLARE_WAIT_QUEUE_HEAD(log_wait);//等待隊列l(wèi)og_wait

環(huán)形緩沖區(qū)__log_buf在使用之前就是已定義好的全局變量,緩沖區(qū)的長度為1 << CONFIG_LOG_ BUF_SHIFT。變量CONFIG_LOG_BUF_SHIFT在內(nèi)核編譯時由配置文件定義,對于i386平臺,其值定義如下(在?linux26/arch/i386/defconfig中):

CONFIG_LOG_BUF_SHIFT=18

在內(nèi)核編譯時,編譯器根據(jù)配置文件的設(shè)置,產(chǎn)生如下的宏定義:

#define CONFIG_LOG_BUF_SHIFT??18

環(huán)形緩沖區(qū)__log_buf定義如下(在linux26/kernel/printk.c中):

#define __LOG_BUF_LEN(1 << CONFIG_LOG_BUF_SHIFT) //定義環(huán)形緩沖區(qū)的長度,i386平臺為

static char __log_buf[__LOG_BUF_LEN]; //printk的環(huán)形緩沖區(qū)

static char *log_buf = __log_buf;

static int log_buf_len = __LOG_BUF_LEN;

/*互斥鎖logbuf_lock保護log_buf、log_start、log_end、con_start和logged_chars */

static DEFINE_SPINLOCK(logbuf_lock);

通過宏定義LOG_BUF,緩沖區(qū)__log_buf具備了環(huán)形緩沖區(qū)的操作行為。宏定義LOG_BUF得到緩沖區(qū)指定位置序號的字符,位置序號超過緩沖區(qū)長度時,通過與長度掩碼LOG_BUF_MASK進行邏輯與操作,位置序號循環(huán)回到環(huán)形緩沖區(qū)中的位置。

宏定義LOG_BUF及位置序號掩碼LOG_BUF_MASK的定義列出如下:

#define LOG_BUF_MASK (log_buf_len-1)

#define LOG_BUF(idx) ?(log_buf[(idx) & LOG_BUF_MASK])

為了指明環(huán)形緩沖區(qū)__log_buf字符讀取位置,定義了下面的位置變量:

static unsigned long log_start;/*系統(tǒng)調(diào)用syslog讀取的下一個字符*/

static unsigned long con_start;/*送到控制臺的下一個字符*/

static unsigned long log_end;/*最近已寫字符序號加1 */

static unsigned long logged_chars; /*自從上一次read+clear?操作以來產(chǎn)生的字符數(shù)*/

任何地方的內(nèi)核調(diào)用都可以調(diào)用函數(shù)printk打印調(diào)試、安全、提示和錯誤消息。函數(shù)printk嘗試得到控制臺信號量(console_sem),如果得到,就將信息輸出到環(huán)形緩沖區(qū)__log_buf中,然后函數(shù)release_console_sem()在釋放信號 量之前把環(huán)形緩沖區(qū)中的消息送到控制臺,調(diào)用控制臺驅(qū)動程序顯示打印的信息。如果沒得到信號量,就只將信息輸出到環(huán)形緩沖區(qū)后返回。函數(shù)printk的調(diào) 用層次如圖1所示。



圖1 函數(shù)printk的調(diào)用層次圖


函數(shù)printk列出如下(在linux26/kernel/printk.c中):

asmlinkage int printk(const char *fmt, ...)

{

va_list args;

int r;

????

va_start(args, fmt);

r = vprintk(fmt, args);

va_end(args);

?

return r;

}

?

asmlinkage int vprintk(const char *fmt, va_list args)

{

unsigned long flags;

int printed_len;

char *p;

static char printk_buf[1024];

static int log_level_unknown = 1;

?

preempt_disable(); //關(guān)閉內(nèi)核搶占

if (unlikely(oops_in_progress) && printk_cpu == smp_processor_id())

/*如果在printk運行時,這個CPU發(fā)生崩潰,

確信不能死鎖,10秒1次初始化鎖logbuf_lock和console_sem,留時間

給控制臺打印完全的oops信息*/

zap_locks();

?

local_irq_save(flags);??//存儲本地中斷標(biāo)識

lockdep_off();

spin_lock(&logbuf_lock);

printk_cpu = smp_processor_id(); 

?

/*將輸出信息發(fā)送到臨時緩沖區(qū)printk_buf */

printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);

?

/*拷貝printk_buf數(shù)據(jù)到循環(huán)緩沖區(qū),如果調(diào)用者沒提供合適的日志級別,插入默認值*/

for (p = printk_buf; *p; p++) {

if (log_level_unknown) {

?/* log_level_unknown signals the start of a new line */

if (printk_time) {

int loglev_char;

char tbuf[50], *tp;

unsigned tlen;

unsigned long long t;

unsigned long nanosec_rem;

?

/*在時間輸出之前強制輸出日志級別*/

if (p[0] == '<' && p[1] >='0' &&

p[1] <= '7' && p[2] == '>') {

loglev_char = p[1]; //獲取日志級別字符

p += 3;

printed_len -= 3;

} else {

loglev_char = default_message_loglevel

+ '0';

}

t = printk_clock();//返回當(dāng)前時鐘,以ns為單位

nanosec_rem = do_div(t, 1000000000);

tlen = sprintf(tbuf,

"<%c>[%5lu.%06lu] ",

loglev_char,

(unsigned long)t,

nanosec_rem/1000);//寫入格式化后的日志級別和時間

?

for (tp = tbuf; tp < tbuf + tlen; tp++) 

emit_log_char(*tp); ?//將日志級別和時間字符輸出到循環(huán)緩沖區(qū)

printed_len += tlen;

} else {

if (p[0] != '<' || p[1] < '0' ||

???p[1] > '7' || p[2] != '>') {

emit_log_char('<');

emit_log_char(default_message_loglevel

+ '0'); ?//輸出字符到循環(huán)緩沖區(qū)

emit_log_char('>');

printed_len += 3;

}

}

log_level_unknown = 0;

if (!*p)

break;

}

emit_log_char(*p);//將其他printk_buf數(shù)據(jù)輸出到循環(huán)緩沖區(qū)

if (*p == '/n')

log_level_unknown = 1;

}

?

if (!down_trylock(&console_sem)) {

/*擁有控制臺驅(qū)動程序,降低spinlock并讓release_console_sem()打印字符?*/

console_locked = 1;

printk_cpu = UINT_MAX;

spin_unlock(&logbuf_lock);

?

/*如果CPU準(zhǔn)備好,控制臺就輸出字符。函數(shù)cpu_online檢測CPU是否在線,

函數(shù)have_callable_console()檢測是否

有注冊的控制臺啟動時就可以使用*/

if (cpu_online(smp_processor_id()) || have_callable_console()) {

console_may_schedule = 0;

release_console_sem();

} else {

/*釋放鎖避免刷新緩沖區(qū)*/

console_locked = 0;

up(&console_sem);

}

lockdep_on();

local_irq_restore(flags); //恢復(fù)本地中斷標(biāo)識

} else {

/*如果其他進程擁有這個驅(qū)動程序,本線程降低spinlock,

允許信號量持有者運行并調(diào)用控制臺驅(qū)動程序輸出字符*/

printk_cpu = UINT_MAX;

spin_unlock(&logbuf_lock);

lockdep_on();

local_irq_restore(flags); //恢復(fù)本地中斷標(biāo)識

}

?

preempt_enable();??//開啟搶占機制

return printed_len;

}

函數(shù)release_console_sem()給控制臺系統(tǒng)開鎖,釋放控制臺系統(tǒng)及驅(qū)動程序調(diào)用者持有的信號量。持有信號量時,表示printk?已在緩沖區(qū)存有數(shù)據(jù)。函數(shù)release_console_sem()在釋放信號量之前將這些數(shù)據(jù)送給控制臺顯示。如果后臺進程klogd在等待環(huán)形緩沖 區(qū)裝上數(shù)據(jù),它喚醒klogd進程。

函數(shù)release_console_sem列出如下(在linux26/kernel/printk.c中):

void release_console_sem(void)

{

unsigned long flags;

unsigned long _con_start, _log_end;

unsigned long wake_klogd = 0;

?

for ( ; ; ) {

spin_lock_irqsave(&logbuf_lock, flags);

wake_klogd |= log_start - log_end;

if (con_start == log_end)

break;/*?沒有需要打印的數(shù)據(jù)*/

_con_start = con_start;

_log_end = log_end;

con_start = log_end;/* Flush */

spin_unlock_irqrestore(&logbuf_lock, flags);

?????????//調(diào)用控制臺driver的write函數(shù)寫入到控制臺

call_console_drivers(_con_start, _log_end);

}

console_locked = 0;

console_may_schedule = 0;

up(&console_sem);

spin_unlock_irqrestore(&logbuf_lock, flags);

if (wake_klogd && !oops_in_progress && waitqueue_active(&log_wait))

wake_up_interruptible(&log_wait);//喚醒在等待隊列上的進程

}

函數(shù)_call_console_drivers將緩沖區(qū)中從start到end - 1的數(shù)據(jù)輸出到控制臺進行顯示。在輸出數(shù)據(jù)到控制臺之前,它檢查消息的日志級別。只有日志級別小于控制臺日志級別console_loglevel的消 息,才能交給控制臺驅(qū)動程序進行顯示。

函數(shù)_call_console_drivers列出如下:

static void _call_console_drivers(unsigned long start,

unsigned long end, int msg_log_level)

{

//日志級別小于控制臺日志級別的消息才能輸出到控制臺

if ((msg_log_level < console_loglevel || ignore_loglevel) &&

console_drivers && start != end) {

if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {

/*?調(diào)用控制臺驅(qū)動程序的寫操作函數(shù)?*/

__call_console_drivers(start & LOG_BUF_MASK,?log_buf_len);

__call_console_drivers(0, end & LOG_BUF_MASK);

} else {

__call_console_drivers(start, end);

}

}

}

函數(shù)__call_console_drivers調(diào)用控制臺驅(qū)動程序的寫操作函數(shù)顯示消息。其列出如下:

static void __call_console_drivers(unsigned long start, unsigned long end)

{

struct console *con;

?

for (con = console_drivers; con; con = con->next) {

if ((con->flags & CON_ENABLED) && con->write &&

(cpu_online(smp_processor_id()) ||

(con->flags & CON_ANYTIME)))

con->write(con, &LOG_BUF(start), end - start); //調(diào)用驅(qū)動程序的寫操作函數(shù)

}

}

3? sys_syslog系統(tǒng)調(diào)用

系統(tǒng)調(diào)用sys_syslog根據(jù)參數(shù)type的命令執(zhí)行相應(yīng)的操作。參數(shù)type定義的命令列出如下:

0 --?關(guān)閉日志,當(dāng)前沒實現(xiàn)。
1 --?打開日志,當(dāng)前沒實現(xiàn)。
2 --?從環(huán)形緩沖區(qū)讀取日志消息。
3 --?讀取保留在環(huán)形緩沖區(qū)的所有消息。
4 --?讀取并清除保留在環(huán)形緩沖區(qū)的所有消息。
5 --?清除環(huán)形緩沖區(qū)。
6 --?關(guān)閉printk到控制臺的打印。
7 --?開啟printk到控制臺的打印。
8 --?設(shè)置打印到控制臺的消息的日志級別。
9 --?返回日志緩沖區(qū)中沒讀取的字符數(shù)。
10 --?返回日志緩沖區(qū)的大小。

sys_syslog函數(shù)列出如下(在linux26/kernel/printk.c中):

asmlinkage long sys_syslog(int type, char __user * buf, int len)

{

return do_syslog(type, buf, len);

}

?

int do_syslog(int type, char __user *buf, int len)

{

unsigned long i, j, limit, count;

int do_clear = 0;

char c;

int error = 0;

?

error = security_syslog(type);??//檢查是否調(diào)用這個函數(shù)的權(quán)限

if (error)

return error;

?

switch (type) {

case 0:/*?關(guān)閉日志?*/

break;

case 1:/*?打開日志*/

break;

case 2:/*讀取日志信息*/

error = -EINVAL;

if (!buf || len < 0)

goto out;

error = 0;

if (!len)

goto out;

if (!access_ok(VERIFY_WRITE, buf, len)) { //驗證是否有寫的權(quán)限

error = -EFAULT;

goto out;

}

???????????//當(dāng)log_start - log_end為0時,表示環(huán)形緩沖區(qū)無數(shù)據(jù)可讀,把當(dāng)前進程放入

??????????????????等待隊列l(wèi)og_wait

error = wait_event_interruptible(log_wait,?(log_start - log_end));

if (error)

goto out;

i = 0;

spin_lock_irq(&logbuf_lock);

while (!error && (log_start != log_end) && i < len) {

c = LOG_BUF(log_start); //從環(huán)形緩沖區(qū)得到讀取位置log_start

log_start++;

spin_unlock_irq(&logbuf_lock);

error = __put_user(c,buf); //將c地址的字符傳遞到用戶空間的buf中

buf++;

i++;

cond_resched();??//條件調(diào)度,讓其他進程有運行時間

spin_lock_irq(&logbuf_lock);

}

spin_unlock_irq(&logbuf_lock);

if (!error)

error = i;

break;

case 4:/*?讀/清除上一次內(nèi)核消息*/

do_clear = 1;

/* FALL THRU */

case 3:/*讀取上一次內(nèi)核消息*/

error = -EINVAL;

if (!buf || len < 0) 

goto out;

error = 0;

if (!len)??//讀取長度為0

goto out;

if (!access_ok(VERIFY_WRITE, buf, len)) { //驗證有寫權(quán)限

error = -EFAULT;

goto out;

}

count = len;

if (count > log_buf_len) 

count = log_buf_len;

spin_lock_irq(&logbuf_lock);

if (count > logged_chars) // logged_chars是上次讀/清除以來產(chǎn)生的日志字符數(shù)

count = logged_chars;

if (do_clear)

logged_chars = 0;

limit = log_end; 

/* __put_user()?可以睡眠,當(dāng)__put_user睡眠時,printk()可能覆蓋寫正在

拷貝到用戶空間的消息,因此,這些消息被反方向拷貝,將buf覆蓋部分的數(shù)據(jù)重寫到buf的起始位置*/

for (i = 0; i < count && !error; i++) { //讀取count個字符

j = limit-1-i;

if (j + log_buf_len < log_end)

break;

c = LOG_BUF(j); //從環(huán)形緩沖區(qū)得到讀取位置j

spin_unlock_irq(&logbuf_lock);

 ??//將c位置的字符傳遞到用戶空間的buf中,如果發(fā)生錯誤,將發(fā)生錯誤的c位置給error

error = __put_user(c,&buf[count-1-i]);?

cond_resched();

spin_lock_irq(&logbuf_lock);

}

spin_unlock_irq(&logbuf_lock);

?

if (error)

break;

error = i;

if (i != count) { //表示__put_user沒有拷貝完成

int offset = count-error;

/*?拷貝期間緩沖區(qū)溢出,糾正用戶空間緩沖區(qū)*/

for (i = 0; i < error; i++) {

if (__get_user(c,&buf[i+offset]) ||

????__put_user(c,&buf[i])) { //將覆蓋部分的數(shù)據(jù)

??????????????????????????????????????????????????????????????重寫到buf的起始位置

error = -EFAULT;

break;

}

cond_resched();

}

}

break;

case 5:/*?清除環(huán)形緩沖區(qū)*/

logged_chars = 0;

break;

case 6:/*關(guān)閉向控制臺輸出消息*/

console_loglevel = minimum_console_loglevel;

break;

case 7:/*開啟向控制臺輸出消息*/

console_loglevel = default_console_loglevel;

break;

case 8:/*?設(shè)置打印到控制臺的日志級別*/

error = -EINVAL;

if (len < 1 || len > 8)

goto out;

if (len < minimum_console_loglevel)

len = minimum_console_loglevel;

console_loglevel = len;

error = 0;

break;

case 9:/*?得到日志消息所占緩沖區(qū)的大小*/

error = log_end - log_start;

break;

case 10:/*返回環(huán)形緩沖區(qū)的大小*/

error = log_buf_len;

break;

default:

error = -EINVAL;

break;

}

out:

return error;

}

?

為了更好的管理日志,你可通過修改/etc/syslog.conf來滿足你的需求。man syslog.conf獲得更多關(guān)于syslog.conf的信息。當(dāng)你在編寫內(nèi)核模塊時,應(yīng)該注意一個問題,你不能讓那些消息困擾你,因此定義一些宏來?開關(guān)printk消息是很有必要的,畢竟printk是用來調(diào)試,或者顯示設(shè)備、模塊的一些狀態(tài)。另外內(nèi)核同時也提供了一些接口,?在<linux/kernel.h>中定義,他們是一些內(nèi)聯(lián)函數(shù)。int printk_ratelimit(void)。在打印一條可能被重復(fù)的信息之前,應(yīng)調(diào)用該函數(shù)。如果它返回一個非零值,則可以繼續(xù)打印消息,否則跳過。?printk_ratelimit通過跟蹤發(fā)送到控制臺的消息量,開避免消息的重復(fù)輸出。可以通過修改/proc/sys/kernel /printk_ratelimit來設(shè)置重新打開消息應(yīng)該等待的秒數(shù)。

總結(jié)

以上是生活随笔為你收集整理的linux内核printk调试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。