printk 实现分析

原创 2007年10月07日 19:38:00

一直都不清楚是怎么被定位到串口的,所以也非常想搞明白,因为以后可能把标准输入输出还原到键盘和显示器上去,所以决心自己再读一读源码了。
不过内核用的打印函数printk完全是和stdin或stdout无关的,因为一开始到start_kernel函数刚开始进入内核就可以用printk函数了,而建立stdin和stdout是在init函数中实现的。有个问题,在我这里的代码中,建立stdin和stdout如下
 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,而且我把这几行代码删除也没有问题,所以我猜想这里建立stdin和stdout并没什么用,肯定在shell中建立了定位到串口的stdin和stdout。所以接下来还需要看看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_S3C2400_CONSOLE
 s3c2400_console_init();
#endif
#ifdef CONFIG_SERIAL_S3C2410_CONSOLE
 s3c2410_console_init();  //这个函数被运行
#endif
}

void __init s3c2410_console_init(void)
{
 register_console(&s3c2410_cons); //调用注册控制台的函数
}

static struct console s3c2410_cons = {
 name:  "ttyS",
 write:  s3c2410_console_write,
 device:  s3c2410_console_device,
 wait_key: s3c2410_console_wait_key,
 setup:  s3c2410_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();
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

printk实现分析(源码)

一直都不清楚是怎么被定位到串口的,所以也非常想搞明白,因为以后可能把标准输入输出还原到键盘和显示器上去,所以决心自己再读一读源码了。 不 过内核用的打印函数printk完全是和stdin或s...

ARM 架构 dump_stack 实现分析(3.0 printk %pS选项实现)

上篇提到了函数: void dump_backtrace_entry(unsigned long where, unsigned long from, unsigned long frame) {...

printk实现分析

printk实现分析

内核printk的实现分析

在这里还是主要分析一下printk实现的原理。 static spinlock_t logbuf_lock = SPIN_LOCK_UNLOCKED; //定义logbuf_lock,并初始...

printk实现分析

由于前两天在看netconsole的源码实现中,发现其跟printk的实现机制相关,加之之前一直是很普通的使用printk,从不清楚printk到底是怎样工作的,因此就趁这个机会把printk的实现代...

[console] early printk实现流程

本文以ARM为例一、功能说明printk的log输出是由console实现(会在其他文章中说明)。由于在kernel刚启动的过程中,还没有为串口等设备等注册console(在device probe阶...

D:LINUX内核层PRINTK实现原理

 (最终调用上面注册的CONSOLE来输出调试信息) LINUX内核源码版本:linux-3.0.86   Linux内核层printk函数用于输出内核调试信息。Printk->vprint...

关于printk的分析

前面转了一篇文章,但一直没弄清楚为何printk(linux_banner)会留在log_buf里,今天仔细跟了一下,记录如下,另外还要看一下dmesg...:printk->vprintk在函数vp...

Printk的loglevel和日志记录分析

http://blog.csdn.net/brfeng/archive/2008/08/05/2772502.aspx     Printk的loglevel和日志记录分析 (分析版本是ubu...
  • gowyz
  • gowyz
  • 2011-10-22 10:17
  • 714
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)