在我们书写内核代码的时候通常会使用printk,这里我们看下printk是如何和uart关联起来的。关于uart的相关的硬件知识不介绍了,so easy。我们这里只是走读下printk的代码,看看和uart的驱动的关联。printk---函数实现在kernel/printk.c文件中。接下来的很多的函数都是在这个文件中。这是一个神奇的函数哦,参数中的“...”可以让你参数输入的比较随意了。在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。- asmlinkage int printk(const char *fmt, ...)
- {
- va_list args;
- int r;
-
-
- #ifdef CONFIG_KGDB_KDB
- if (unlikely(kdb_trap_printk)) {
- va_start(args, fmt);
- r = vkdb_printf(fmt, args);
- va_end(args);
- return r;
- }
- #endif
- va_start(args, fmt);
- _trace_kernel_printk(_RET_IP_);
- r = vprintk(fmt, args);
- va_end(args);
-
-
- return r;
- }
这里就是对数据的规整,最终走到了vprintk这个函数中。- asmlinkage int vprintk(const char *fmt, va_list args)
- {
- ........................
- spin_lock(&logbuf_lock);
- ........................
-
- if (p[0] == '<') {
- unsigned char c = p[1];
- if (c && p[2] == '>') {
- switch (c) {
- case '0' ... '7':
- current_log_level = c - '0';
-
- case 'd':
- if (!new_text_line) {
- emit_log_char('\n');
- new_text_line = 1;
- }
-
- case 'c':
- p += 3;
- break;
- }
- }
- }
-
-
-
-
-
- for ( ; *p; p++) {
- if (new_text_line) {
-
- emit_log_char('<');
- emit_log_char(current_log_level + '0');
- emit_log_char('>');
- printed_len += 3;
- new_text_line = 0;
-
-
- 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 (acquire_console_semaphore_for_printk(this_cpu))
- release_console_sem();
-
-
- ............................
- }
那么下面我们看看release_console_sem是如何实现输出的。release_console_sem的作用是解锁console系统- void release_console_sem(void)
- {
-
-
- for ( ; ; ) {
- spin_lock_irqsave(&logbuf_lock, flags);
- wake_klogd |= log_start - log_end;
- if (con_start == log_end)
- break;
- _con_start = con_start;
- _log_end = log_end;
- con_start = log_end;
- spin_unlock(&logbuf_lock);
- stop_critical_timings();
-
- call_console_drivers(_con_start, _log_end);
- start_critical_timings();
- local_irq_restore(flags);
- }
- console_locked = 0;
- up(&console_sem);
- spin_unlock_irqrestore(&logbuf_lock, flags);
- if (wake_klogd)
- wake_up_klogd();
- }
- EXPORT_SYMBOL(release_console_sem);
- so,从上面的函数我们去看看call_console_drivers函数中到底有何门道。
- /*
- * 调用console的驱动,首先要先将log_buf[start]到log_buf[end-1]的数据拿出来
- *在这个过程中,要把console的标志一直保持住
- */
- static void call_console_drivers(unsigned start, unsigned end)
- {
- unsigned cur_index, start_print;
- static int msg_level = -1;
-
-
- BUG_ON(((int)(start - end)) > 0);
-
-
- cur_index = start;
- startstart_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;
- }
- 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);
- }
这里还没用看到,但是看到更加深入的接口_call_console_drivers。这个函数里面一探究竟吧。- 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)) {
-
- __call_console_drivers(start & LOG_BUF_MASK,
- log_buf_len);
- __call_console_drivers(0, end & LOG_BUF_MASK);
- } else {
- __call_console_drivers(start, end);
- }
- }
- }
紧接着的__call_console_drivers是不是呢?- static void __call_console_drivers(unsigned start, unsigned end)
- {
- struct console *con;
-
-
- for_each_console(con) {
- if ((con->flags & CON_ENABLED) && con->write &&
- (cpu_online(smp_processor_id()) ||
- (con->flags & CON_ANYTIME)))
- con->write(con, &LOG_BUF(start), end - start);
- }
- }
你以为躲起来就找不到你了吗?没有用的,你是那样拉风的函数,不管在什么地方,就好象漆黑中的萤火虫一样,那样的鲜明,那样的出众,你那忧郁的眼神,唏嘘的胡碴子,神乎奇迹的刀法,还有那杯DRY MARTINE,都深深的迷住了我。不过虽然你是这样的出色,但是行有行规,我也要剖析你的内心了con->write。
到了这里,我们先暂时忍耐下,先看看这个拉轰的结构体- 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;
- };
哈哈,你一定也注意到了,这里有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。
|