printk() を読んでみる
printk() って?
-> カーネル空間にメッセージを出力する。syslog ( /var/log/messages ) やシリアルコンソールにメッセージを出力できる。
早速 kernel/printk.c を見てみよう。
/**
* printk - print a kernel message
* @fmt: format string
*
* This is printk(). It can be called from any context. We want it to work.
*
* We try to grab the console_sem. If we succeed, it's easy - we log the output and
* call the console drivers. If we fail to get the semaphore we place the output
* into the log buffer and return. The current holder of the console_sem will
* notice the new output in release_console_sem() and will send it to the
* consoles before releasing the semaphore.
*
* One effect of this deferred printing is that code which calls printk() and
* then changes console_loglevel may break. This is because console_loglevel
* is inspected when the actual printing occurs.
*
* See also:
* printf(3)
*
* See the vsnprintf() documentation for format string extensions over C99.
*/
asmlinkage int printk(const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
r = vprintk(fmt, args);
va_end(args);
return r;
}
冒頭の “This is printk(). It can be called from any context. We want it to work.” は「printk() はどのコンテキストからも実行できる、つまり割り込みハンドラでも使える」 ということを言っている。 printk(const char *fmt, …) の中身を見ると、vprintk() を呼んでいるだけのことがわかる。
asmlinkage int vprintk(const char *fmt, va_list args)
{
int printed_len = 0;
int current_log_level = default_message_loglevel;
unsigned long flags;
int this_cpu;
char *p;
boot_delay_msec();
printk_delay();
preempt_disable();
/* This stops the holder of console_sem just where we want him */
raw_local_irq_save(flags);
this_cpu = smp_processor_id();
boot_delay_msec() と printk_delay() はメイン処理の前に待ちを入れるかの判断をするだけ。 preempt_disable() はカーネル・プリエンプションを無効にする。 最後の行で preempt_enable() で最有効化をしている。これは、printk() の実行中にプリエンプションが発生すると、処理が正しく動かないため。 raw_local_irq_save() は、printk() を実行している CPU からの割り込みを禁止し、他の処理に割り込ませないようにする。 smp_processor_id() は CPU の番号を取得。(コア単位)
/*
* Ouch, printk recursed into itself!
*/
if (unlikely(printk_cpu == this_cpu)) {
/*
* If a crash is occurring during printk() on this CPU,
* then try to get the crash message out but make sure
* we can't deadlock. Otherwise just return to avoid the
* recursion and return - but flag the recursion so that
* it can be printed at the next appropriate moment:
*/
if (!oops_in_progress) {
recursion_bug = 1;
goto out_restore_irqs;
}
zap_locks();
}
lockdep_off();
spin_lock(&logbuf_lock);
printk_cpu = this_cpu;
if (recursion_bug) {
recursion_bug = 0;
strcpy(printk_buf, recursion_bug_msg);
printed_len = strlen(recursion_bug_msg);
}
unlikely() で CPU 番号のチェックが入っている。
printk() の実行中にカーネルパニックが発生し、その過程で printk() が呼び出されると printk() の無限ループになる。それを防ぐのがこの CPU 番号のチェック。
recursion_bug_msg を printk_bug に格納している。
/* Emit the output into the temporary buffer */
printed_len += vscnprintf(printk_buf + printed_len,
sizeof(printk_buf) - printed_len, fmt, args);
vscnprintf() では printk_buf[] というバッファに printk() に渡された引数をフォーマットしてコピーしている。
/* kernel.h */
extern int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
__attribute__ ((format (printf, 3, 0)));
以降の処理は printk_buf に対して行われる。
p = printk_buf;
/* Do we have a loglevel in the string? */
if (p[0] == '<') {
unsigned char c = p[1];
if (c && p[2] == '>') {
switch (c) {
case '0' ... '7': /* loglevel */
current_log_level = c - '0';
/* Fallthrough - make sure we're on a new line */
case 'd': /* KERN_DEFAULT */
if (!new_text_line) {
emit_log_char('\n');
new_text_line = 1;
}
/* Fallthrough - skip the loglevel */
case 'c': /* KERN_CONT */
p += 3;
break;
}
}
}
バッファの二番目の要素から LogLevel を判別している。
/*
* Copy the output into log_buf. If the caller didn't provide
* appropriate log level tags, we insert them here
*/
for ( ; *p; p++) {
if (new_text_line) {
/* Always output the token */
emit_log_char('<');
emit_log_char(current_log_level + '0');
emit_log_char('>');
printed_len += 3;
new_text_line = 0;
if (printk_time) {
/* Follow the token with the time */
char tbuf[50], *tp;
unsigned tlen;
unsigned long long t;
unsigned long nanosec_rem;
t = cpu_clock(printk_cpu);
nanosec_rem = do_div(t, 1000000000);
tlen = sprintf(tbuf, "[%5lu.%06lu] ",
(unsigned long) t,
nanosec_rem / 1000);
for (tp = tbuf; tp < tbuf + tlen; tp++)
emit_log_char(*tp);
printed_len += tlen;
}
if (!*p)
break;
}
emit_log_char(*p);
if (*p == '\n')
new_text_line = 1;
}
print_buf の内容を一文字ずつ解析し、emit_log_char() で log_buf[] に送っている。 log_buf[] は char log_buf[VMM_LOG_LEN] で定義されるリングバッファでこの大きさはカーネルコンフィグレーションで変更可能。
結局 printk() は log_buf[] ( カーネル内のバッファ ) に格納されるだけ。
dmesg でこのバッファ内容は読み取ることができ、また、klogd というデーモンが定期的に内容を吸い上げ syslog に書き出す。
kluged デーモンの吸い上げが遅れるとリングバッファが一周し、一部のデータが出力されない場合もある。( printk() の使用頻度が多いなど )