开发板:tiny4412ADK+S700 4GB Flash
主机:Wind7 64位
虚拟机:Vmware+Ubuntu12_04
u-boot:U-Boot 2010.12
Linux内核版本:linux-3.0.31
Android版本:android-4.1.2
下面要分析的是内核Log打印的几个阶段
自解压阶段
内核启动阶段
内核启动完全以后
shell终端下
在这个阶段内核log打印可以调用printk和printascii,同时printk又分为两个阶段,从刚才的分析中知道,printk最终调用的是有register_console注册的console_drivers的write函数,在tiny4412平台上调用register_console的地方有两处,第一处是在arch/arm/kernel/early_printk.c中,另一处就是在串口驱动注册中,具体是在driver/tty/serial/samsung.c中,下面我们开始分析。
printascii的实现:
首先printascii需要配置内核才能使用:
make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
Kernel hacking
--- Kernel low-level debugging functions
这样就可以使用printascii了:
在printk中会调用printascii,
#ifdef CONFIG_DEBUG_LL
printascii(printk_buf);
#endif
print_asciii使用汇编实现的,在文件arch/arm/kernel/debug.S中:
.macro addruart_current, rx, tmp1, tmp2
addruart tmp1, tmp2
mrc p15, 0, rx, c1, c0
tst rx, #1
moveq rx, tmp1
movne rx, tmp2
.endm
ENTRY(printascii)
addruart_current r3, r1, r2
b 2f
1: waituart r2, r3
senduart r1, r3
busyuart r2, r3
teq r1, #'n'
moveq r1, #'r'
beq 1b
2: teq r0, #0
ldrneb r1, [r0], #1
teqne r1, #0
bne 1b
mov pc, lr
ENDPROC(printascii)
其中 addruart 是在文件arch/arm/mach-exynos/include/mach/debug-macro.S中实现的,waituart、senduart以及busyuart是在arch/arm/plat-samsung/include/plat/debug-macro.S中实现的,大家可以参考这两个文件理解具体实现过程。
early_printk中调用register_console
tiny4412使用的内核默认是没有开启early_printk的,即log_buf中内容只有等driver/tty/serial下的串口驱动注册完成后才能输出到串口终端,在此之前调用printk的内容都缓存到log_buf中了,如果想提前使用的话,需要使能early_printk,下面说明一下如何使能early_printk。
make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
Kernel hacking
-- Kernel low-level debugging functions
--Early printk
要使能early_printk首先必须使能Kernel low-level debugging functions,因为early_printk最终也是使用printascii实现的,这样arch/arm/kernel/early_printk.c就会参加编译
在early_printk.c中:
static int __init setup_early_printk(char *buf)
{
printk("%s entern", __func__);
register_console(&early_console);
return 0;
}
early_param("earlyprintk", setup_early_printk);
虽然内核配置了,但是要让内核调用setup_early_printk还必须在u-boot给内核传参的时候给bootargs在加上一个参数”earlyprintk”,如下:
set bootargs ‘console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70 earlyprintk’
那么内核是如何处理的呢?
在文件include/linux/init.h中:
#define __setup_param(str, unique_id, fn, early)
static const char __setup_str_##unique_id[] __initconst
__aligned(1) = str;
static struct obs_kernel_param __setup_##unique_id
__used __section(.init.setup)
__attribute__((aligned((sizeof(long)))))
= { __setup_str_##unique_id, fn, early }
#define early_param(str, fn)
__setup_param(str, fn, fn, 1)
将early_param("earlyprintk", setup_early_printk);展开后:
static const char __setup_str_setup_early_printk[] __initconst __aligned(1) = "earlyprintk";
static struct obs_kernel_param __setup_setup_early_printk __used __section(.init.setup)
__attribute__((aligned((sizeof(long))))) =
{ __setup_str_setup_early_printk, setup_early_printk, 1 }
即上面的这个结构体被链接到了”.init.setup”段,在arch/arm/kernel/vmlinux.lds中:
. = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
将所有的.init.setup都链接到了__setup_start和__setup_end之间
在内核启动的时候:
start_kernel
--- setup_arch(&command_line);
//这个函数的目的是获得u-boot传给内核的参数(bootargs),并将参数存放在command_line中,然后解析command_line,并调用相关的函数处理--- parse_early_param() (arch/arm/kernel/setup.c)
--- strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
--- parse_early_options(tmp_cmdline);
parse_early_options用于解析tmp_cmdline
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, do_early_param);
}
在文件kernel/params.c中:
int parse_args(const char *name,
char *args,
const struct kernel_param *params,
unsigned num,
int (*unknown)(char *param, char *val))
{
char *param, *val;
/* Chew leading spaces */
args = skip_spaces(args); // 跳过args开头的空格
while (*args) {
int ret;
int irq_was_disabled;
/*
如:cmdline是“console=ttySAC0,115200n8 androidboot.console=ttySAC0”
执行next_arg后:
params=”console”, val=” ttySAC0,115200n8” args=” androidboot.console=ttySAC0”
*/
args = next_arg(args, ¶m, &val); // 获得下一个参数的位置,
irq_was_disabled = irqs_disabled();
/*
parse_one所做的主要就是将params和val传递给unknown处理,这里unknown就是do_early_param,所以下面分析do_early_param
*/
ret = parse_one(param, val, params, num, unknown);
…
}
}
/* All parsed OK. */
return 0;
}
static int __init do_early_param(char *param, char *val)
{
const struct obs_kernel_param *p;
/*
还记得early_param("earlyprintk", setup_early_printk)展开后的结果吗?
其中early为1,str是 “earlyprintk”,setup_func就是setup_early_printk
*/
for (p = __setup_start; p < __setup_end; p++) {
if ((p->early && strcmp(param, p->str) == 0) ||
(strcmp(param, "console") == 0 &&
strcmp(p->str, "earlycon") == 0)
) {
if (p->setup_func(val) != 0)
…
}
}
return 0;
}
下面我们就分析setup_early_printk:
static int __init setup_early_printk(char *buf)
{
register_console(&early_console);
return 0;
}
结构体early_console 的定义如下:
static struct console early_console = {
.name = "earlycon",
.write = early_console_write,
.flags = CON_PRINTBUFFER | CON_BOOT,
.index = -1,
};
看一下early_console_write干了什么:
static void early_console_write(struct console *con, const char *s, unsigned n)
{
early_write(s, n);
}
static void early_write(const char *s, unsigned n)
{
while (n-- > 0) {
if (*s == 'n')
printch('r');
printch(*s);
s++;
}
}
可以看到,它调用的是printch,它在文件arch/arm/kernel/debug.S中实现:
ENTRY(printascii)
addruart_current r3, r1, r2
b 2f
1: waituart r2, r3
senduart r1, r3
busyuart r2, r3
teq r1, #'n'
moveq r1, #'r'
beq 1b
2: teq r0, #0
ldrneb r1, [r0], #1
teqne r1, #0
bne 1b
mov pc, lr
ENDPROC(printascii)
ENTRY(printch)
addruart_current r3, r1, r2
mov r1, r0
mov r0, #0
b 1b
ENDPROC(printch)
这样当driver/tty/serial下的驱动尚未注册时,printk就已经可以使用了,它最终调用的是early_console_write输出到串口终端的
下面我们分析一个函数register_console
/*
这段话最好看一下
* The console driver calls this routine during kernel initialization
* to register the console printing procedure with printk() and to
* print any messages that were printed by the kernel before the
* console driver was initialized.
*
* This can happen pretty early during the boot process (because of
* early_printk) - sometimes before setup_arch() completes - be careful
* of what kernel features are used - they may not be initialised yet.
*
* There are two types of consoles - bootconsoles (early_printk) and
* "real" consoles (everything which is not a bootconsole) which are