分享

走读printk代码

 智慧书仓 2013-03-04
 
在我们书写内核代码的时候通常会使用printk,这里我们看下printk是如何和uart关联起来的。关于uart的相关的硬件知识不介绍了,so easy。我们这里只是走读下printk的代码,看看和uart的驱动的关联。

printk---函数实现在kernel/printk.c文件中。接下来的很多的函数都是在这个文件中。

这是一个神奇的函数哦,参数中的“...”可以让你参数输入的比较随意了。在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。
  1. asmlinkage int printk(const char *fmt, ...)  
  2. {  
  3.     va_list args;  
  4.     int r;  
  5.   
  6.   
  7. #ifdef CONFIG_KGDB_KDB   
  8.     if (unlikely(kdb_trap_printk)) {  
  9.         va_start(args, fmt);  
  10.         r = vkdb_printf(fmt, args);  
  11.         va_end(args);  
  12.         return r;  
  13.     }  
  14. #endif   
  15.     va_start(args, fmt);  
  16.     _trace_kernel_printk(_RET_IP_);  
  17.     r = vprintk(fmt, args);  
  18.     va_end(args);  
  19.   
  20.   
  21.     return r;  
  22. }  
这里就是对数据的规整,最终走到了vprintk这个函数中。
  1. asmlinkage int vprintk(const char *fmt, va_list args)  
  2. {  
  3. ........................  
  4.     spin_lock(&logbuf_lock); //申请到对log buffer的锁,锁的使用对防止并发有很好的作用   
  5. ........................  
  6.     /* 这里会看到我们的输入的log level的处理 */  
  7.     if (p[0] == '<') {  
  8.         unsigned char c = p[1];  
  9.         if (c && p[2] == '>') {  
  10.             switch (c) {  
  11.             case '0' ... '7'/* loglevel */  
  12.                 current_log_level = c - '0';  
  13.             /* Fallthrough - make sure we're on a new line */  
  14.             case 'd'/* KERN_DEFAULT */  
  15.                 if (!new_text_line) {  
  16.                     emit_log_char('\n');  
  17.                     new_text_line = 1;  
  18.                 }  
  19.             /* Fallthrough - skip the loglevel */  
  20.             case 'c'/* KERN_CONT */  
  21.                 p += 3;  
  22.                 break;  
  23.             }  
  24.         }  
  25.     }  
  26.   
  27.   
  28.     /* 
  29.      * 在这里就把所要输出的内容copy to log_buf。然后判断是否需要打印log tag,和时间戳。 
  30.      */  
  31.     for ( ; *p; p++) {  
  32.         if (new_text_line) {  
  33.             /* Always output the token */  
  34.             emit_log_char('<');  
  35.             emit_log_char(current_log_level + '0');  
  36.             emit_log_char('>');  
  37.             printed_len += 3;  
  38.             new_text_line = 0;  
  39.   
  40.   
  41.             if (printk_time) {  
  42.                 /* Follow the token with the time */  
  43.                 char tbuf[50], *tp;  
  44.                 unsigned tlen;  
  45.                 unsigned long long t;  
  46.                 unsigned long nanosec_rem;  
  47.   
  48.   
  49.                 t = cpu_clock(printk_cpu);  
  50.                 nanosec_rem = do_div(t, 1000000000);  
  51.                 tlen = sprintf(tbuf, "[%5lu.%06lu] ",  
  52.                         (unsigned long) t,  
  53.                         nanosec_rem / 1000);  
  54.   
  55.   
  56.                 for (tp = tbuf; tp < tbuf + tlen; tp++)  
  57.                     emit_log_char(*tp);  
  58.                 printed_len += tlen;  
  59.             }  
  60.   
  61.   
  62.             if (!*p)  
  63.                 break;  
  64.         }  
  65.   
  66.   
  67.         emit_log_char(*p);  
  68.         if (*p == '\n')  
  69.             new_text_line = 1;  
  70.     }  
  71.   
  72.   
  73.     /* 
  74.      *取得console的使用标志并且要立即释放掉,释放函数会做输出的动作 
  75.      * 
  76.      * 在前面看到的spin_lock(&logbuf_lock),在函数acquire_console_semaphore_for_printk函数中也会得到释放 
  77.      *重申:对于锁一定要记得去释放 
  78.      */  
  79.     if (acquire_console_semaphore_for_printk(this_cpu))  
  80.         release_console_sem();  
  81.   
  82.   
  83. ............................  
  84. }  
那么下面我们看看release_console_sem是如何实现输出的。
release_console_sem的作用是解锁console系统
  1. void release_console_sem(void)  
  2. {  
  3. //先判断了下是否系统需要休眠,需要休眠则离开   
  4.       
  5.     for ( ; ; ) {  
  6.         spin_lock_irqsave(&logbuf_lock, flags);  
  7.         wake_klogd |= log_start - log_end;  
  8.         if (con_start == log_end)  
  9.             break;          /* Nothing to print */  
  10.         _con_start = con_start;  
  11.         _log_end = log_end;  
  12.         con_start = log_end;        /* Flush */  
  13.         spin_unlock(&logbuf_lock);  
  14.         stop_critical_timings();    /* don't trace print latency */  
  15.         //在把数据拿出来之后,这里终于看到了调用串口驱动的接口   
  16.         call_console_drivers(_con_start, _log_end);  
  17.         start_critical_timings();  
  18.         local_irq_restore(flags);  
  19.     }  
  20.     console_locked = 0;  
  21.     up(&console_sem);  
  22.     spin_unlock_irqrestore(&logbuf_lock, flags);  
  23.     if (wake_klogd)  
  24.         wake_up_klogd();  
  25. }  
  26. EXPORT_SYMBOL(release_console_sem);  
  27. so,从上面的函数我们去看看call_console_drivers函数中到底有何门道。
    1. /*  
    2.  * 调用console的驱动,首先要先将log_buf[start]到log_buf[end-1]的数据拿出来  
    3.  *在这个过程中,要把console的标志一直保持住  
    4.  */  
    5. static void call_console_drivers(unsigned start, unsigned end)  
    6. {  
    7.     unsigned cur_index, start_print;  
    8.     static int msg_level = -1;  
    9.   
    10.   
    11.     BUG_ON(((int)(start - end)) > 0);  
    12.   
    13.   
    14.     cur_index = start;  
    15.     startstart_print = start;  
    16.     while (cur_index != end) {  
    17.         if (msg_level < 0 && ((end - cur_index) > 2) &&  
    18.                 LOG_BUF(cur_index + 0) == '<' &&  
    19.                 LOG_BUF(cur_index + 1) >= '0' &&  
    20.                 LOG_BUF(cur_index + 1) <= '7' &&  
    21.                 LOG_BUF(cur_index + 2) == '>') {  
    22.             msg_level = LOG_BUF(cur_index + 1) - '0';  
    23.             cur_index += 3;  
    24.             start_print = cur_index;  
    25.         }  
    26.         while (cur_index != end) {  
    27.             char c = LOG_BUF(cur_index);  
    28.   
    29.   
    30.             cur_index++;  
    31.             if (c == '\n') {  
    32.                 if (msg_level < 0) {  
    33.                     /*  
    34.                      * printk() has already given us loglevel tags in  
    35.                      * the buffer.  This code is here in case the  
    36.                      * log buffer has wrapped right round and scribbled  
    37.                      * on those tags  
    38.                      */  
    39.                     msg_level = default_message_loglevel;  
    40.                 }  
    41.                 _call_console_drivers(start_print, cur_index, msg_level);  
    42.                 msg_level = -1;  
    43.                 start_print = cur_index;  
    44.                 break;  
    45.             }  
    46.         }  
    47.     }  
    48.     _call_console_drivers(start_print, end, msg_level);  
    49. }  
    这里还没用看到,但是看到更加深入的接口_call_console_drivers。这个函数里面一探究竟吧。
    1. static void _call_console_drivers(unsigned start,  
    2.                 unsigned end, int msg_log_level)  
    3. {  
    4.     if ((msg_log_level < console_loglevel || ignore_loglevel) &&  
    5.             console_drivers && start != end) {  
    6.         if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {  
    7.             /* wrapped write */  
    8.             __call_console_drivers(start & LOG_BUF_MASK,  
    9.                         log_buf_len);  
    10.             __call_console_drivers(0, end & LOG_BUF_MASK);  
    11.         } else {  
    12.             __call_console_drivers(start, end);  
    13.         }  
    14.     }  
    15. }  
    紧接着的__call_console_drivers是不是呢?
    1. static void __call_console_drivers(unsigned start, unsigned end)  
    2. {  
    3.     struct console *con;  
    4.   
    5.   
    6.     for_each_console(con) {  
    7.         if ((con->flags & CON_ENABLED) && con->write &&  
    8.                 (cpu_online(smp_processor_id()) ||  
    9.                 (con->flags & CON_ANYTIME)))  
    10.             con->write(con, &LOG_BUF(start), end - start);  
    11.     }  
    12. }  
    你以为躲起来就找不到你了吗?没有用的,你是那样拉风的函数,不管在什么地方,就好象漆黑中的萤火虫一样,那样的鲜明,那样的出众,你那忧郁的眼神,唏嘘的胡碴子,神乎奇迹的刀法,还有那杯DRY  MARTINE,都深深的迷住了我。不过虽然你是这样的出色,但是行有行规,我也要剖析你的内心了con->write。


    到了这里,我们先暂时忍耐下,先看看这个拉轰的结构体
    1. struct console {  
    2.     char    name[16];  
    3.     void    (*write)(struct console *, const char *, unsigned);  
    4.     int (*read)(struct console *, char *, unsigned);  
    5.     struct tty_driver *(*device)(struct console *, int *);  
    6.     void    (*unblank)(void);  
    7.     int (*setup)(struct console *, char *);  
    8.     int (*early_setup)(void);  
    9.     short   flags;  
    10.     short   index;  
    11.     int cflag;  
    12.     void    *data;  
    13.     struct   console *next;  
    14. };  
    哈哈,你一定也注意到了,这里有write,是的,这就是在上面con->write中调用的。
    在你的UART的驱动中你一定看到了register_console(&arch-xxx_uart_console);这个函数了,里面的write就是注册在此让printk使用的。write中的实现就是UART的那些小case了,这里不多说了。
    当然这里还有这个console_initcall(arch—xxx_uart_console_init);将你的uart加入起来的了。

    你可能在看arch-xxx_uart_console这个结构体定义的时候注意到没用read函数?这是什么原因呢?
    在LDD中有这个答案,“当tty驱动程序接收到数据后,它将负责把从硬件获得的任何数据传递给tty核心,而不使用传统的read函数。tty核心将缓冲数据直接接到来自用户的请求。由于tty核心已经提供了缓冲逻辑,因此没用必要为每个tty驱动程序实现他们自己的缓冲区逻辑。”。当然这段话是教材这么说的,后续在看的时候会把这部分源码在展开来读读看。

    当然printk不一定非得是串口输出,这里只是一个例子,今天就先到这里了,下面会把如何指定printk的输出给看看,这里先说下在__call_console_drivers()函数中的for_each_console()是个关键。见http://www./Linux/2011-09/43676.htm

 

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多