printk 實現分析

來源:互聯網
上載者:User

一直都不清楚是怎麼被定位到串口的,所以也非常想搞明白,因為以後可能把標準輸入輸出還原到鍵盤和顯示器上去,所以決心自己再讀一讀源碼了。
不過核心用的列印函數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();
}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.