Linux核心中printk與記錄層級

來源:互聯網
上載者:User

     函數printk的使用方法和printf相似,用於核心列印訊息。printk根據記錄層級(loglevel)對訊息進行分類。記錄層級用宏定義,記錄層級宏展開為一個字串,在編譯時間由前置處理器將它和訊息文本拼接成一個字串,因此printk 函數中記錄層級宏和格式字串間不能有逗號。

   下面是兩個printk的例子,一個用於列印調試資訊,另一個用於列印臨界條件資訊。

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>"
#defineKERN_ALERT"<1>"
#defineKERN_CRIT"<2>"
#defineKERN_ERR"<3>"
#defineKERN_WARNING"<4>"
#defineKERN_NOTICE"<5>"
#defineKERN_INFO"<6>"
#defineKERN_DEBUG"<7>"

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中):


#define DEFAULT_MESSAGE_LOGLEVEL 4

核心可把訊息列印到當前控制台上,可以指定控制台為字元模式的終端或印表機等。預設情況下,“控制台”就是當前的虛擬終端。

為了更好地控制不同層級的資訊顯示在控制台上,核心設定了控制台的記錄層級console_loglevel。printk記錄層級的作用是列印一定層級的訊息,與之類似,控制台只顯示一定層級的訊息。

當記錄層級小於console_loglevel時,訊息才能顯示出來。控制台相應的記錄層級定義如下:


#define MINIMUM_CONSOLE_LOGLEVEL  1   
#define DEFAULT_CONSOLE_LOGLEVEL  7

int console_printk[4] = {
DEFAULT_CONSOLE_LOGLEVEL,

DEFAULT_MESSAGE_LOGLEVEL,

MINIMUM_CONSOLE_LOGLEVEL,
DEFAULT_CONSOLE_LOGLEVEL,
};

如果系統運行了klogd和syslogd,則無論console_loglevel為何值,核心訊息都將追加到/var/log/messages中。如果klogd沒有運行,訊息不會傳遞到使用者空間,只能查看/proc/kmsg。

變數console_loglevel的初始值是DEFAULT_CONSOLE_LOGLEVEL,可以通過sys_syslog系統調用進行修改。調用klogd時可以指定-c開關選項來修改這個變數。如果要修改它的當前值,必須先殺掉klogd,再加-c選項重新啟動它。

註:#ps -e 查看所有進程PID,然後KILL。
通過讀寫/proc/sys/kernel/printk檔案可讀取和修改控制台的記錄層級。查看這個檔案的方法如下:

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

上面顯示的4個資料分別對應控制台記錄層級、預設的訊息記錄層級、最低的控制台記錄層級和預設的控制台記錄層級。

可用下面的命令設定當前記錄層級:

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

printk列印訊息機制

在核心中,函數printk將訊息列印到環形緩衝區__log_buf中,並將訊息傳給控制台進行顯示。控制台驅動程式根據控制台的記錄層級顯示日誌訊息。

應用程式通過系統調用sys_syslog管理環形緩衝區__log_buf,它可以讀取資料、清除緩衝區、設定記錄層級、開/關控制台等。

當系統調用sys_syslog從環形緩衝區__log_buf讀取資料時,如果緩衝區沒有資料,系統調用sys_syslog所在進程將被加入到等待隊列log_wait中進行等待。當printk將資料列印到緩衝區後,將喚醒系統調用sys_syslog所在進程從緩衝區中讀取資料。等待隊列 log_wait定義如下:

DECLARE_WAIT_QUEUE_HEAD(log_wait);//等待隊列log_wait

環形緩衝區__log_buf在使用之前就是已定義好的全域變數,緩衝區的長度為1 << CONFIG_LOG_ BUF_SHIFT。變數CONFIG_LOG_BUF_SHIFT在核心編譯時間由設定檔定義,對於i386平台,其值定義如下(在 linux26/arch/i386/defconfig中):

CONFIG_LOG_BUF_SHIFT=18

在核心編譯時間,編譯器根據設定檔的設定,產生如下的宏定義:

#define CONFIG_LOG_BUF_SHIFT 18

環形緩衝區__log_buf定義如下(在linux26/kernel/printk.c中):

#define __LOG_BUF_LEN(1 << CONFIG_LOG_BUF_SHIFT) //定義環形緩衝區的長度,i386平台為 
static char __log_buf[__LOG_BUF_LEN]; //printk的環形緩衝區
static char *log_buf = __log_buf;
static int log_buf_len = __LOG_BUF_LEN;

static DEFINE_SPINLOCK(logbuf_lock);

通過宏定義LOG_BUF,緩衝區__log_buf具備了環形緩衝區的操作行為。宏定義LOG_BUF得到緩衝區指定位置序號的字元,位置序號超過緩衝區長度時,通過與長度掩碼LOG_BUF_MASK進行邏輯與操作,位置序號迴圈回到環形緩衝區中的位置。

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

#define LOG_BUF_MASK (log_buf_len-1)
#define LOG_BUF(idx)  (log_buf[(idx) & LOG_BUF_MASK])

為了指明環形緩衝區__log_buf字元讀取位置,定義了下面的位置變數:

static unsigned long log_start;
static unsigned long con_start;
static unsigned long log_end;
static unsigned long logged_chars;

任何地方的核心調用都可以調用函數printk列印調試、安全、提示和錯誤訊息。函數printk嘗試得到控制台訊號量(console_sem),如果得到,就將資訊輸出到環形緩衝區__log_buf中,然後函數release_console_sem()在釋放訊號量之前把環形緩衝區中的訊息送到控制台,調用控制台驅動程式顯示列印的資訊。如果沒得到訊號量,就只將資訊輸出到環形緩衝區後返回。函數printk的調用層次1所示。

圖1 函數printk的調用層次圖

函數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(); //關閉核心搶佔
if (unlikely(oops_in_progress) && printk_cpu == smp_processor_id())

zap_locks();

local_irq_save(flags); //儲存本地中斷標識
lockdep_off();
spin_lock(&logbuf_lock);
printk_cpu = smp_processor_id(); 


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


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

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();//返回當前時鐘,以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);  //將記錄層級和時間字元輸出到迴圈緩衝區
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');  //輸出字元到迴圈緩衝區
emit_log_char('>');
printed_len += 3;
}
}
log_level_unknown = 0;
if (!*p)
break;
}
emit_log_char(*p);//將其他printk_buf資料輸出到迴圈緩衝區
if (*p == '/n')
log_level_unknown = 1;
}

if (!down_trylock(&console_sem)) {

console_locked = 1;
printk_cpu = UINT_MAX;
spin_unlock(&logbuf_lock);


if (cpu_online(smp_processor_id()) || have_callable_console()) {
console_may_schedule = 0;
release_console_sem();
} else {

console_locked = 0;
up(&console_sem);
}
lockdep_on();
local_irq_restore(flags); //恢複本地中斷標識
} else {

printk_cpu = UINT_MAX;
spin_unlock(&logbuf_lock);
lockdep_on();
local_irq_restore(flags); //恢複本地中斷標識
}

preempt_enable(); //開啟搶佔機制
return printed_len;
}

函數release_console_sem()給控制台系統開鎖,釋放控制台系統及驅動程式調用者持有的訊號量。持有訊號量時,表示printk 已在緩衝區存有資料。函數release_console_sem()在釋放訊號量之前將這些資料送給控制台顯示。如果後台進程klogd在等待環形緩衝區裝上資料,它喚醒klogd進程。

函數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;
_con_start = con_start;
_log_end = log_end;
con_start = log_end;
spin_unlock_irqrestore(&logbuf_lock, flags);
//調用控制台driver的write函數寫入到控制台
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);//喚醒在等待隊列上的進程
}

函數_call_console_drivers將緩衝區中從start到end - 1的資料輸出到控制台進行顯示。在輸出資料到控制台之前,它檢查訊息的記錄層級。只有記錄層級小於控制台記錄層級console_loglevel的訊息,才能交給控制台驅動程式進行顯示。

函數_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)) {

__call_console_drivers(start & LOG_BUF_MASK, log_buf_len);
__call_console_drivers(0, end & LOG_BUF_MASK);
} else {
__call_console_drivers(start, end);
}
}
}

函數__call_console_drivers調用控制台驅動程式的寫操作函數顯示訊息。其列出如下:

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); //調用驅動程式的寫操作函數
}
}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.