开机过程中的内核打印
作者: zjujoe 转载请注明出处
Email:zjujoe@yahoo.com
BLOG:http://blog.csdn.net/zjujoe
前言
嵌入式开发中, 通常使用串口输出调试信息,了解运行状态。 内核启动过程中,在不同阶段会通过不同的方式将调试信息输出到串口。 (注:以下内容针对 arm-linux.)
解压缩阶段
解压缩阶段内核会输出:
Uncompressing Linux................................ done, booting the kernel.
查找内核, 会发现如上输出是在如下语句中打印的:
312 putstr("Uncompressing Linux...");
313 gunzip();
314 putstr(" done, booting the kernel/n");
putstr 会调用 uncompress.h 中的 putc 来完成任务。
通常, 嵌入式开发中,我们假定 bootloader 已经进行了串口初始化, 所以我们只需要对串口进行写操作就可以了。比如:
18static void putc(char c)
19{
20 volatile u32 *uart = (volatile void *) DAVINCI_UART0_BASE;
21
22 while (!(uart[UART_LSR] & UART_LSR_THRE))
23 barrier();
24 uart[UART_TX] = c;
25}
实现函数,就可以看到串口输出了。当然, 如果您是完美主义者, 还应该实现:
27static inline void flush(void)
28{
29 volatile u32 *uart = (volatile void *) DAVINCI_UART0_BASE;
30 while (!(uart[UART_LSR] & UART_LSR_THRE))
31 barrier();
32}
Image 启动早期
这里的“早期”定义为串口驱动初始化之前。 这里又分为两个阶段:汇编阶段与 C 语言阶段。
汇编阶段
在启动正常后一般不需要在此阶段输出信息。但是,如果系统还没有正常启动, 则需要在汇编里向串口输出一些信息。
内核提供了函数:
116ENTRY(printascii)
为了使用它,我们需要实现:
addruart 获得调试串口的地址
senduart 发送一个字节
waituart 等待串口可用
三个汇编函数。
可以参考某一平台来实现, 比如:
http://lxr.linux.no/linux+v2.6.27/arch/arm/mach-pxa/include/mach/debug-macro.S#L16
pxa 的实现表明, 由于 uart 通常是8250 兼容的, 所以很可能我们只需要实现:
addruart, 提供一下串口基地址,即可。
另外, 值得注意的是:汇编代码会经历实地址模式与虚拟地址模式两个阶段。 而上面的打印函数是可以同时用于两种模式的。
C 语言阶段
C 语言代码从 start_kernel 开始, 可以看到,内核很快就会调用 printk:
printk(KERN_NOTICE);
然而在 arm 平台上, 如上的打印要等 console_init()函数执行后,才会输出到串口。
如果有人有兴趣, 可以为arm平台实现一个 patch, 使得在打印内核 notice时就能够向串口输出。 当然, 对于 BSP 开发, 目前的实现基本够用, 因为驱动程序初始化时间较晚,在 console_init 之后。
我们看一下该函数的实现:
3644/*
3645 * Initialize the console device. This is called *early*, so
3646 * we can't necessarily depend on lots of kernel help here.
3647 * Just do some early initializations, and do the complex setup
3648 * later.
3649 */
3650void __init console_init(void)
3651{
3654 /* Setup the default TTY line discipline. */
3657 /*
3658 * set up the console device so that later boot sequences can
3659 * inform about problems etc..
3660 */
3661 call = __con_initcall_start;
3662 while (call < __con_initcall_end) {
3665 }
3666}
原来该函数会调用 __con_initcall_start 段里的函数。而串口驱动正是在此注册了一个这样的函数, 而提供了 early printk 功能。
比如, http://lxr.linux.no/linux+v2.6.27/drivers/serial/8250.c 中,就实现了改功能:
2645static int __init serial8250_console_init(void)
2646{
2647 if (nr_uarts > UART_NR)
2648 nr_uarts = UART_NR;
2649
2650 serial8250_isa_init_ports();
2651 register_console(&serial8250_console);
2652 return 0;
2653}
2654console_initcall(serial8250_console_init);
本质上,就是提前注册串口终端,而不是在串口驱动初始化时才注册。
Image 启动后期
有了 前面实现的 early print, 内核打印就没有问题了, 如果没有实现early print, 则在串口驱动初始化时,会注册一个console 驱动, 从而实现内核打印。
当然, 前提是我们需要在串口驱动中实现并注册 static struct console 。
具体细节可以参考样例驱动。 这里就不深究了。
内核打印机制浅析
内核函数调用 printk 后, printk 将 打印信息发送到一个 log_buffer. 然后调用如下函数, 试图进行后续处理。
如果已经注册了终端,则会调用终端的输出函数。
另外,终端注册函数也会调用 release_console_sem, 将此前所有的信息一次性打印到 console.
984 /**
985 * release_console_sem - unlock the console system
986 *
987 * Releases the semaphore which the caller holds on the console system
988 * and the console driver list.
989 *
990 * While the semaphore was held, console output may have been buffered
991 * by printk(). If this is the case, release_console_sem() emits
992 * the output prior to releasing the semaphore.
993 *
994 * If there is output waiting for klogd, we wake it up.
995 *
996 * release_console_sem() may be called from any context.
997 */
998 void release_console_sem(void)
999 {
1000 unsigned long flags;
1001 unsigned _con_start, _log_end;
1002 unsigned wake_klogd = 0;
1003
1004 if (console_suspended) {
1005 up(&console_sem);
1006 return;
1007 }
1008
1009 console_may_schedule = 0;
1010
1011 for ( ; ; ) {
1012 spin_lock_irqsave(&logbuf_lock, flags);
1013 wake_klogd |= log_start - log_end;
1014 if (con_start == log_end)
1015 break; /* Nothing to print */
1016 _con_start = con_start;
1017 _log_end = log_end;
1018 con_start = log_end; /* Flush */
1019 spin_unlock(&logbuf_lock);
1020 call_console_drivers(_con_start, _log_end);
1021 local_irq_restore(flags);
1022 }
1023 console_locked = 0;
1024 up(&console_sem);
1025 spin_unlock_irqrestore(&logbuf_lock, flags);
1026 if (wake_klogd)
1027 wake_up_klogd();
1028 }
1029 EXPORT_SYMBOL(release_console_sem);