printk 与syslog

在头文件 <linux/kernel.h> 中定义了 【8种可用的日志级别字符串】
KERN_EMERG    用于紧急事件消息,它们一般是系统崩溃之前提示的消息。
KERN_ALERT    用于需要立即采取动作的情况。
KERN_CRIT     临界状态,通常涉及严重的硬件或软件操作失败。
KERN_ERR      用于报告错误状态;设备驱动程序会经常使用KERN_ERR来报告来自硬件的问题。
KERN_WARNING  对可能出现问题的情况进行警告,这类情况通常不会对系统造成严重问题。
KERN_NOTICE   有必要进行提示的正常情形。许多与安全相关的状况用这个级别进行汇报。
KERN_INFO     提示性信息。很多驱动程序在启动的时候,以这个级别打印出它们找到的硬件信息。
KERN_DEBUG    用于调试信息。

dmesg是从kernel的ring buffer(环缓冲区)中读取信息的.

那什么是ring buffer呢?
    在LINUX中,所有的系统信息(包内核信息)都会传送到ring buffer中.而内核产生的信息由printk()打印出来。系统启动时所看到的信息都是由该函数打印到屏幕中。 printk()打出的信息往往以 <0>...<2>... 这的数字表明消息的重要级别。高于一定的优先级别会打印到屏幕上,否则只会保留在系统的缓冲区中(ring buffer)。
    至于dmesg具体是如何从ring buffer中读取的,大家可以看dmesg.c源代码.很短,比较容易读懂.

     是syslogd这个守护进程根据/etc/syslog.conf,将不同的服务产生的Log记录到不同的文件中.
    LINUX系统启动后,由/etc/init.d/sysklogd先后启动klogd,syslogd两个守护进程。
    其中klogd会通过syslog()系统调用或者读取proc文件系统来从系统缓冲区(ring buffer)中得到由内核printk()发出的信息.而syslogd是通过klogd来读取系统内核信息.

    1> 所有系统信息是输出到ring buffer中去的.dmesg所显示的内容也是从ring buffer中读取的.
    2> LINUX系统中/etc/init.d/sysklogd会启动2个守护进程:Klogd, Syslogd
    3> klogd是负责读取内核信息的,有2种方式:
       syslog()系统调用(这个函数用法比较全,大家去MAN一下看看)直接的对/proc/kmsg进行读取(再这提一下,/proc/kmsg是专门输出内核信息的地方)
    4> Klogd的输出结果会传送给syslogd进行处理,syslogd会根据/etc/syslog.conf的配置把log信息输出到/var/log/下的不同文件中.

注意将printk与syslog接合使用, 用在内核开发方面很不错.

内核用的打印函数printk完全是和stdinstdout无关的,因为一开始到start_kernel函数刚开始进入内核就可以用printk 函数了,而建立stdinstdout是在init函数中实现的。有个问题,这里的代码中,建立stdinstdout如下

 if (open("/dev/null", O_RDWR, 0) < 0)

  printk("Warning: unable to open an initial console./n");

 (void) dup(0);

 (void) dup(0);

问题在于它打开的是/dev/null,而一般pc机上的linux打开的都是/dev/console,而且我把这几行代码删除也没有问题,所以我猜想这里建立stdinstdout并没什么用,肯定在shell中建立了定位到串口的stdinstdout。所以接下来还需要看看busybox的代码吧。

在这里还是主要分析一下printk实现的原理。

static spinlock_t logbuf_lock = SPIN_LOCK_UNLOCKED; //定义logbuf_lock,并初始化为unlock状态

static char log_buf[LOG_BUF_LEN];  //保存日志数据的缓冲区

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

static DECLARE_MUTEX(console_sem); //定义全局互斥信号量console_sem并初始化为1

 

asmlinkage int printk(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;

 

 if (oops_in_progress)  // default : oops_in_progress = 0

 { //oops_in_progress指示进程发生错误,只有在panic()函数中才等于1

  //所以一般情况下下两句都不运行

  /* If a crash is occurring, make sure we can't deadlock */

  spin_lock_init(&logbuf_lock); //初始化logbuf_lock

  /* And make sure that we print immediately */

  init_MUTEX(&console_sem); //初始化console_sem为互斥的信号量,初值为1

 }

 

 /* This stops the holder of console_sem just where we want him */

 spin_lock_irqsave(&logbuf_lock, flags);

 //一般spin_lock在单cpu中无效的,所以spin_lock_irqsave真正的作用是关中断 和保存状态寄存器。

 /* Emit the output into the temporary buffer */

 va_start(args, fmt);

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

 //先把数据格式化到printk_buf中去

 va_end(args);

 

 /*

  * Copy the output into log_buf.  If the caller didn't provide

  * appropriate log level tags, we insert them here

  */

 //emit_log_char 把字符存入log_buf中等待被发送,具体的参见下面的分析

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

  if (log_level_unknown) {

   if (p[0] != '<' || p[1] < '0' || p[1] > '7' || p[2] != '>') {

    emit_log_char('<');

    emit_log_char(default_message_loglevel + '0');

    emit_log_char('>');

   }

 //如果没有提供<1>类似的日志级别,则在此加上<4>

 //我这里的default_message_loglevel=4

   log_level_unknown = 0;

  }

  emit_log_char(*p);

  if (*p == '/n') //每一行前面都需要加<4>之类的日志级别

   log_level_unknown = 1;

 }

 

 if (!arch_consoles_callable()) // unexecute

 {

  /* 控制台是否可调用,一般下面的不会被执行

   * On some architectures, the consoles are not usable

   * on secondary CPUs early in the boot process.

   */

  spin_unlock_irqrestore(&logbuf_lock, flags);

  goto out;

 }

 if (!down_trylock(&console_sem)) //lock ok

 {

  /* down_trylock获取信号量,lock则返回0,否则立即返回非0

   * We own the drivers.  We can drop the spinlock and let

   * release_console_sem() print the text

   */

  spin_unlock_irqrestore(&logbuf_lock, flags);

  console_may_schedule = 0;

  release_console_sem(); //在这个函数中把数据发送到串口并释放console_sem

 } else {

  /*

   * Someone else owns the drivers.  We drop the spinlock, which

   * allows the semaphore holder to proceed and to call the

   * console drivers with the output which we just produced.

   */

  spin_unlock_irqrestore(&logbuf_lock, flags);

 }

out:

 return printed_len;

}

 

static unsigned long log_start;  /* Index into log_buf: next char to be read by syslog() */

static unsigned long con_start;  /* Index into log_buf: next char to be sent to consoles */

static unsigned long log_end;  /* Index into log_buf: most-recently-written-char + 1 */

static unsigned long logged_chars; /* Number of chars produced since last read+clear operation */

static void emit_log_char(char c)

{

 LOG_BUF(log_end) = c;  //把字符c存到log_buf缓冲区中,缓冲区满了就会覆盖开始的数据

 log_end++;   //

 if (log_end - log_start > LOG_BUF_LEN) //log_start指示syslog读取的开始

  log_start = log_end - LOG_BUF_LEN;//缓冲区满了会把开始的指针向前推

 if (log_end - con_start > LOG_BUF_LEN) //con_start指示控制台读取的开始

  con_start = log_end - LOG_BUF_LEN;

 if (logged_chars < LOG_BUF_LEN)

  logged_chars++;

}

 

void release_console_sem(void)

{

 unsigned long flags;

 unsigned long _con_start, _log_end;

 unsigned long must_wake_klogd = 0;

 

 for ( ; ; ) {

  spin_lock_irqsave(&logbuf_lock, flags);//关中断和保存flag

  must_wake_klogd |= log_start - log_end; //唤醒klogd标志

  if (con_start == log_end)

   break;   /* Nothing to print */

  _con_start = con_start;

  _log_end = log_end;

  con_start = log_end;  /* Flush , con_start向前移用了,可见缓冲区是循环使用的 */

  spin_unlock_irqrestore(&logbuf_lock, flags);

  call_console_drivers(_con_start, _log_end);//在这个函数中发送数据,见下面的分析

 }

 console_may_schedule = 0; //指示数据发送时是否能进行任务调度,在使用串口控制台时没用

 up(&console_sem); //释放信号量

 spin_unlock_irqrestore(&logbuf_lock, flags);

 if (must_wake_klogd && !oops_in_progress)

  wake_up_interruptible(&log_wait);

}

 

 

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

{

 unsigned long cur_index, start_print;

 static int msg_level = -1;

 

 if (((long)(start - end)) > 0)

  BUG();

 

 cur_index = start;

 start_print = start;

 while (cur_index != end) {

  if ( msg_level < 0 &&

   ((end - cur_index) > 2) &&

   LOG_BUF(cur_index + 0) == '<' &&

   LOG_BUF(cur_index + 1) >= '0' &&

   LOG_BUF(cur_index + 1) <= '7' &&

   LOG_BUF(cur_index + 2) == '>')

  {

   msg_level = LOG_BUF(cur_index + 1) - '0';

   cur_index += 3;

   start_print = cur_index;

  } //去除每行开头的类似<4>的日志级别,把它赋给msg_level

  while (cur_index != end) {

   char c = LOG_BUF(cur_index);

   cur_index++;

   if (c == '/n') {

    if (msg_level < 0) {

     /*

      * printk() has already given us loglevel tags in

      * the buffer.  This code is here in case the

      * log buffer has wrapped right round and scribbled

      * on those tags

      */

     msg_level = default_message_loglevel;

    }

    _call_console_drivers(start_print, cur_index, msg_level);

    //发送一行数据

    msg_level = -1;

    start_print = cur_index;

    break;

   }

  }

 }

 _call_console_drivers(start_print, end, msg_level); //发送剩余的数据

}

 

 

struct console *console_drivers; //全局的console类型的结构体

 

static void _call_console_drivers(unsigned long start, unsigned long end, int msg_log_level)

{

 //如果msg_log_level < console_loglevel 并且 console_drivers存在 并且 start != end

 if (msg_log_level < console_loglevel && console_drivers && start != end) {

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

   /* wrapped write */

   //缓冲区循环使用就会出现(start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)

   //的清况,于是就分两部分来发送.

   //__call_console_drivers才是真正的发送函数

   __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 long start, unsigned long end)

{

 struct console *con;

 

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

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

   con->write(con, &LOG_BUF(start), end - start);

 } //调用console_drivers->write来把数据发送出去

}

 

接下来理解一下console_drivers这个结构体指针

struct console

{

 char name[8];

 void (*write)(struct console *, const char *, unsigned);

 int (*read)(struct console *, const char *, unsigned);

 kdev_t (*device)(struct console *);

 int (*wait_key)(struct console *);

 void (*unblank)(void);

 int (*setup)(struct console *, char *);

 short flags;

 short index;

 int cflag;

 struct  console *next;

};

 

而开始console_drivers这个指针是NULL,在什么时候被赋值的呢,原本以为应该在用printk以前就被初始化了,其实不然,它是在start_kernel函数中调用的console_init()函数中被初始化的.最好的办法还是看看代码.

void __init console_init(void)

{

 /* Setup the default TTY line discipline. */

 memset(ldiscs, 0, sizeof(ldiscs));

 (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

 

 /*

  * Set up the standard termios.  Individual tty drivers may

  * deviate from this; this is used as a template.

  */

 memset(&tty_std_termios, 0, sizeof(struct termios));

 memcpy(tty_std_termios.c_cc, INIT_C_CC, NCCS);

 tty_std_termios.c_iflag = ICRNL | IXON;

 tty_std_termios.c_oflag = OPOST | ONLCR;

#ifdef CONFIG_MIZI //CONFIG_MIZI=1

 tty_std_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL;

#else

 tty_std_termios.c_cflag = B38400 | CS8 | CREAD | HUPCL;

#endif

 tty_std_termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |

  ECHOCTL | ECHOKE | IEXTEN;

 

 /*

  * set up the console device so that later boot sequences can

  * inform about problems etc..

  */

#ifdef CONFIG_VT //如果不是串口控制台就定义这个虚拟控制台

 con_init(); //于是输入是键盘输出是显示器

#endif

#ifdef CONFIG_AU1000_SERIAL_CONSOLE

 au1000_serial_console_init();

#endif

#ifdef CONFIG_SERIAL_CONSOLE

#if (defined(CONFIG_8xx) || defined(CONFIG_8260))

 console_8xx_init();

#elif defined(CONFIG_MAC_SERIAL) && defined(CONFIG_SERIAL)

 if (_machine == _MACH_Pmac)

   mac_scc_console_init();

 else

  serial_console_init();

#elif defined(CONFIG_MAC_SERIAL)

  mac_scc_console_init();

#elif defined(CONFIG_PARISC)

 pdc_console_init();

#elif defined(CONFIG_SERIAL)

 serial_console_init();

#endif /* CONFIG_8xx */

#ifdef CONFIG_SGI_SERIAL

 sgi_serial_console_init();

#endif

#if defined(CONFIG_MVME162_SCC) || defined(CONFIG_BVME6000_SCC) || defined(CONFIG_MVME147_SCC)

 vme_scc_console_init();

#endif

#if defined(CONFIG_SERIAL167)

 serial167_console_init();

#endif

#if defined(CONFIG_SH_SCI)

 sci_console_init();

#endif

#endif

#ifdef CONFIG_TN3270_CONSOLE

 tub3270_con_init();

#endif

#ifdef CONFIG_TN3215

 con3215_init();

#endif

#ifdef CONFIG_HWC

        hwc_console_init();

#endif

#ifdef CONFIG_STDIO_CONSOLE

 stdio_console_init();

#endif

#ifdef CONFIG_SERIAL_CORE_CONSOLE   //  CONFIG_SERIAL_CORE_CONSOLE=1

 uart_console_init(); //这里唯一一个被运行的函数

#endif

#ifdef CONFIG_ARC_CONSOLE

 arc_console_init();

#endif

#ifdef CONFIG_SERIAL_TX3912_CONSOLE

 tx3912_console_init();

#endif

}

 

void __init uart_console_init(void)

{

#ifdef CONFIG_SERIAL_AMBA_CONSOLE

 ambauart_console_init();

#endif

#ifdef CONFIG_SERIAL_ANAKIN_CONSOLE

 anakin_console_init();

#endif

#ifdef CONFIG_SERIAL_CLPS711X_CONSOLE

 clps711xuart_console_init();

#endif

#ifdef CONFIG_SERIAL_21285_CONSOLE

 rs285_console_init();

#endif

#ifdef CONFIG_SERIAL_SA1100_CONSOLE

 sa1100_rs_console_init();

#endif

#ifdef CONFIG_SERIAL_8250_CONSOLE

 serial8250_console_init();

#endif

#ifdef CONFIG_SERIAL_UART00_CONSOLE

 uart00_console_init();

#endif

#ifdef CONFIG_SERIAL_S 3C 2400_CONSOLE

 s 3c 2400_console_init();

#endif

#ifdef CONFIG_SERIAL_S 3C 2410_CONSOLE

 s 3c 2410_console_init();  //这个函数被运行

#endif

}

 

void __init s 3c 2410_console_init(void)

{

 register_console(&s 3c 2410_cons); //调用注册控制台的函数

}

 

static struct console s 3c 2410_cons = {

 name:  "ttyS",

 write:  s 3c 2410_console_write,

 device:  s 3c 2410_console_device,

 wait_key: s 3c 2410_console_wait_key,

 setup:  s 3c 2410_console_setup,

 flags:  CON_PRINTBUFFER,

 index:  -1,

}; //这个就是console_drivers所指向的结构了

 

void register_console(struct console * console)

{ //该函数就是把console_drivers这个全局指针指向console结构体了,而且在注册完后

 //会把存在缓冲区中的都发送出去,所以在注册console以前调用的printk并不发送数据

 //而只是把数据存到缓冲区里,注册了以后才能被马上发送.多个控制台被注册的话就会

 //形成一个链表结构,都能发送数据.

 int     i;

 unsigned long flags;

 

 /*

  * See if we want to use this console driver. If we

  * didn't select a console we take the first one

  * that registers here.

  */

 if (preferred_console < 0) {

  if (console->index < 0)

   console->index = 0;

  if (console->setup == NULL ||

      console->setup(console, NULL) == 0) {

   console->flags |= CON_ENABLED | CON_CONSDEV;

   preferred_console = 0;

  }

 }

 

 /*

  * See if this console matches one we selected on

  * the command line.

  */

 for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) {

  if (strcmp(console_cmdline[i].name, console->name) != 0)

   continue;

  if (console->index >= 0 &&

      console->index != console_cmdline[i].index)

   continue;

  if (console->index < 0)

   console->index = console_cmdline[i].index;

  if (console->setup &&

      console->setup(console, console_cmdline[i].options) != 0)

   break;

  console->flags |= CON_ENABLED;

  console->index = console_cmdline[i].index;

  if (i == preferred_console)

   console->flags |= CON_CONSDEV;

  break;

 }

 

 if (!(console->flags & CON_ENABLED))

  return;

 

 /*

  * Put this console in the list - keep the

  * preferred driver at the head of the list.

  */

 acquire_console_sem();

 if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {

  console->next = console_drivers;

  console_drivers = console;

 } else {

  console->next = console_drivers->next;

  console_drivers->next = console;

 }

 if (console->flags & CON_PRINTBUFFER) {

  /*

   * release_cosole_sem() will print out the buffered messages for us.

   */

  spin_lock_irqsave(&logbuf_lock, flags);

  con_start = log_start;

  spin_unlock_irqrestore(&logbuf_lock, flags);

 }

 release_console_sem();

}


你可能感兴趣的:(linux,struct,Semaphore,buffer,output,安全相关)