コンソールログレベル


Rev.3を表示中。最新版はこちら

カーネルログはprink()毎に、リングバッファのlog_buf[]に書き出され、同時にstatic struct console *consoleにリストされているドライバのwriteコールバック関数で出力されます。この時コンソールログで、ログの先頭の<数値>がコンソールログレベルです。

ドライバ毎に初期化で、register_console()をコールすることで、該当するドライバがコンソールドライバとして、consoleに登録されます。登録されたドライバからカーネルログが書き出されることになります。
struct console {
       char    name[16];
       void    (*write)(struct console *, const char *, unsigned);
       int     (*read)(struct console *, char *, unsigned);
       struct tty_driver *(*device)(struct console *, int *);
       void    (*unblank)(void);
       int     (*setup)(struct console *, char *);
       int     (*early_setup)(void);
       short   flags;
       short   index;
       int     cflag;
       void    *data;
       struct   console *next;
};

static struct console *console;
printk()からvprintk()とコールされます。default_message_loglevelはログにコンソールログレベルがない時のログレベルです。log_prefix()でログレベルを取得し、emit_log_char()でログをlog_buf[]に設定し、console_unlock()でコンソールに書き出します。
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;
       size_t plen;
       char special;

       boot_delay_msec();
       printk_delay();

       local_irq_save(flags);
       this_cpu = smp_processor_id();


       if (unlikely(printk_cpu == this_cpu)) {
               if (!oops_in_progress && !lockdep_recursing(current)) {
                       recursion_bug = 1;
                       goto out_restore_irqs;
               }
               zap_locks();
       }

       lockdep_off();
       raw_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);
       }
       printed_len += vscnprintf(printk_buf + printed_len,
                                 sizeof(printk_buf) - printed_len, fmt, args);

       p = printk_buf;

       plen = log_prefix(p, &current_log_level, &special);
       if (plen) {
               p += plen;

               switch (special) {
               case 'c': /* Strip <c> KERN_CONT, continue line */
                       plen = 0;
                       break;
               case 'd': /* Strip <d> KERN_DEFAULT, start new line */
                       plen = 0;
               default:
                       if (!new_text_line) {
                               emit_log_char('\n');
                               new_text_line = 1;
                       }
               }
       }

       for (; *p; p++) {
               if (new_text_line) {
                       new_text_line = 0;

                       if (plen) {
                               int i;

                               for (i = 0; i < plen; i++)
                                       emit_log_char(printk_buf[i]);
                               printed_len += plen;
                       } else {
                               emit_log_char('<');
                               emit_log_char(current_log_level + '0');
                               emit_log_char('>');
                               printed_len += 3;
                       }

                       if (printk_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;
       }

       if (console_trylock_for_printk(this_cpu))
               console_unlock();

       lockdep_on();
out_restore_irqs:
       local_irq_restore(flags);

       return printed_len;
}
ログレベルは0から7までで、2桁以上の数値が設定されていても下位3ビットをログレベルとします。上位の値はファシリティで、syslogd等で使われます。
static size_t log_prefix(const char *p, unsigned int *level, char *special)
{
       unsigned int lev = 0;
       char sp = '\0';
       size_t len;

       if (p[0] != '<' || !p[1])
               return 0;
       if (p[2] == '>') {
               /* usual single digit level number or special char */
               switch (p[1]) {
               case '0' ... '7':
                       lev = p[1] - '0';
                       break;
               case 'c': /* KERN_CONT */
               case 'd': /* KERN_DEFAULT */
                       sp = p[1];
                       break;
               default:
                       return 0;
               }
               len = 3;
       } else {
               char *endp = NULL;

               lev = (simple_strtoul(&p[1], &endp, 10) & 7);
               if (endp == NULL || endp[0] != '>')
                       return 0;
               len = (endp + 1) - p;
       }

       if (sp && !special)
               return 0;

       if (special) {
               *special = sp;
               /* return special char, do not touch level */
               if (sp)
                       return len;
       }

       if (level)
               *level = lev;
       return len;
}
console_unlock()でロック取得し、リングバッファのオーバラップを考慮しながらcall_console_drivers()をコールします。
void console_unlock(void)
{
       unsigned long flags;
       unsigned _con_start, _log_end;
       unsigned wake_klogd = 0, retry = 0;

       if (console_suspended) {
               up(&console_sem);
               return;
       }

       console_may_schedule = 0;

again:
       for ( ; ; ) {
               raw_spin_lock_irqsave(&logbuf_lock, flags);
               wake_klogd |= log_start - log_end;
               if (con_start == log_end)
                       break;                  /* Nothing to print */
               _con_start = con_start;
               _log_end = log_end;
               con_start = log_end;            /* Flush */
               raw_spin_unlock(&logbuf_lock);
               stop_critical_timings();        /* don't trace print latency */
               call_console_drivers(_con_start, _log_end);
               start_critical_timings();
               local_irq_restore(flags);
       }
       console_locked = 0;

       if (unlikely(exclusive_console))
               exclusive_console = NULL;

       raw_spin_unlock(&logbuf_lock);

       up(&console_sem);


       raw_spin_lock(&logbuf_lock);
       if (con_start != log_end)
               retry = 1;
       raw_spin_unlock_irqrestore(&logbuf_lock, flags);

       if (retry && console_trylock())
               goto again;

       if (wake_klogd)
               wake_up_klogd();
}
call_console_drivers()から__call_console_drivers()とコールされます。ログレベルがconsole_loglevelより小さい時、またはログレベルを無視する時、__call_console_drivers()とコールされ、struct console *console->write()でログを書き出します。
static void _call_console_drivers(unsigned start,
                               unsigned 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)) {
                       /* wrapped write */
                       __call_console_drivers(start & LOG_BUF_MASK,
                                               log_buf_len);
                       __call_console_drivers(0, end & LOG_BUF_MASK);
               } else {
                       __call_console_drivers(start, end);
               }
       }
}

static void __call_console_drivers(unsigned start, unsigned end)
{
       struct console *con;

       for_each_console(con) {
               if (exclusive_console && con != exclusive_console)
                       continue;
               if ((con->flags & CON_ENABLED) && con->write &&
                               (cpu_online(smp_processor_id()) ||
                               (con->flags & CON_ANYTIME)))
                       con->write(con, &LOG_BUF(start), end - start);
       }
}

補足

ログ値は以下のようにマクロ定義されています。端末ドライバ実装で、<0>はKERN_EMERGで全ての端末に出力させる事は理にかなっているわけです。
#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                 */

コンソールログレベルの設定は、syslogシステムコールで行います。
SYSLOG_ACTION_CONSOLE_OFFでconsole_loglevel = minimum_console_loglevel。minimum_console_loglevel以下のログレベルをprintk()で出力できません。

SYSLOG_ACTION_CONSOLE_ONでconsole_loglevel = saved_console_loglevelとし、ログレベルを復帰します。

SYSLOG_ACTION_CONSOLE_LEVELでconsole_loglevelを設定します。
int do_syslog(int type, char __user *buf, int len, bool from_file)
{
  :
  :
       switch (type) {

       case SYSLOG_ACTION_CONSOLE_OFF:
               if (saved_console_loglevel == -1)
                       saved_console_loglevel = console_loglevel;
               console_loglevel = minimum_console_loglevel;
               break;
       case SYSLOG_ACTION_CONSOLE_ON:
               if (saved_console_loglevel != -1) {
                       console_loglevel = saved_console_loglevel;
                       saved_console_loglevel = -1;
               }
               break;
       case SYSLOG_ACTION_CONSOLE_LEVEL:
               error = -EINVAL;
               if (len < 1 || len > 8)
                       goto out;
               if (len < minimum_console_loglevel)
                       len = minimum_console_loglevel;
               console_loglevel = len;
               saved_console_loglevel = -1;
               error = 0;
               break;
       default:
               error = -EINVAL;
               break;
       }
out:
       return error;
}

検証サンプル

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/console.h>
#include <linux/printk.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("y.kitamura");

static int mod_init(void)
{
   int     i;
   struct console *con;

   for_each_console(con) {
       printk("<0>%s\n", con->name);
   }
   for (i = 0; i < 10; i++) {
       printk("<%d>log level:%d\n", i, i);
   }
   return -1;
}

module_init(mod_init);
コンソール画面からinsmodした結果です。ssh等の端末からinsmodすると、/dev/ptsがregister_console()されていないので、ログレベル0のみ表示されます。(コンソールには表示)

ログレベル0の表示は、カーネルとして実装されていなく、ドライバのwriteコール内での実装のようです。

<8><9>は単にログとなっています。
[root@localhost lkm]# cat /proc/sys/kernel/printk
4       4       1       7

[root@localhost lkm]# tty
/dev/tty1

[root@localhost lkm]# insmod consoledrv.ko
[ 3610.934390] tty
[ 3610.934729] log level:0
[ 3610.934926] log level:1
[ 3610.935067] log level:2
[ 3610.935475] log level:3

[root@localhost lkm]# dmesg
   :
[ 3610.934390] tty
[ 3610.934729] log level:0
[ 3610.934926] log level:1
[ 3610.935067] log level:2
[ 3610.935475] log level:3
[ 3610.937111] log level:4
[ 3610.937118] log level:5
[ 3610.937122] log level:6
[ 3610.937126] log level:7
[ 3610.937131] <8>log level:8
[ 3610.937135] <9>log level:9

最終更新 2014/06/15 17:26:36 - north
(2011/07/25 19:46:40 作成)


検索

アクセス数
3586196
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。