分类: LINUX
2010-07-31 13:47:46
一 printk 函数
printk函数首先把要打印的信息放到buffer里面,然后调用release_console_sem最后调用到相关驱动的write函数,如果你设定了 CONFIG_CMDLINE="console=ttySL0,19200,那么printk信息就会调用ttySL这个驱动的write函数,也就是从串口输出数据了.在__call_console_drivers里面有一个很重要的变量console_drivers,它决定了调用哪支driver输出printk信息.
printk()
{
..............
//把待输出文字放入buffer
va_start(args, fmt);
printed_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args);
va_end(args);
..............
release_console_sem();
|
----------void release_console_sem(void)
{
.............
_call_console_drivers
|
----------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 driver
下面就是printk如果确定调用哪个driver的write函数输出信息过程,或者说一个console driver选择的过程.
首先看一下linux内核启动代码:
[Main.c]
asmlinkage void __init start_kernel(void)
{
..........
setup_arch(&command_line);
printk("Kernel command line: %s\n", saved_command_line);
parse_options(command_line);
..........
}
parse_options
{
//关键调用
checksetup(line)
|
--------int __init checksetup(char *line)
{
struct kernel_param *p;
if (line == NULL)
return 0;
p = &__setup_start;
do {
int n = strlen(p->str);
if (!strncmp(line,p->str,n)) {
if (p->setup_func(line+n))
return 1;
}
p++;
} while (p < &__setup_end);
return 0;
}
}
setup_arch根据CONFIG_CMDLINE指定的内容设定command_line指针. parse_options会遍历一个kernel_param结构数组,起始于__setup_start, 终止于__setup_end,
struct kernel_param {
const char *str;
int (*setup_func)(char *);
};
此数组里面的数据均来自于__setup(str, fn)这个宏.
[Init.h]
#define __setup(str, fn) \
static char __setup_str_##fn[] __initdata = str; \
static struct kernel_param __setup_##fn __attribute__((unused)) __initsetup = { __setup_str_##fn, fn }
在[printk.c]里面有一个很重要的语句 __setup("console=", console_setup);这句话也就是说当CONFIG_CMDLINE含有"console="字符的话,就调用console_setup函数, 所以在parse_options调用的时候,
就会调用到console_setup函数, console_setup就会记录下来console驱动的name,以及一些选项参数到console_cmdline数组中(如波特率),设置preferred_console参数,这样console driver已经选择好一半了.
console_setup()
{
..............
for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
if (strcmp(console_cmdline[i].name, name) == 0 &&
console_cmdline[i].index == idx) {
preferred_console = i;
return 1;
}
if (i == MAX_CMDLINECONSOLES)
return 1;
preferred_console = i;
c = &console_cmdline[i];
memcpy(c->name, name, sizeof(c->name));
c->options = options;
c->index = idx;
..............
}
选择console driver的另一半来自于register_console(linux启动后也会调用此函数),register_console
最重要的一句话是console->next = console_drivers;这样就完成了选择console driver的全过程.
void register_console(struct console * console)
{
..............
//找到console_driver的过程
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) //此时做了setup的动作.
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; //屏蔽掉其他非console的driver
if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
console->next = console_drivers;
console_drivers = console; //设定console_driver
} else {
console->next = console_drivers->next;
console_drivers->next = console;
}
..............
}
[后记]
bootloader也可以传递参数给Kernel, 原理就是bootloader向一块内存中写入具有特定结构的数据,然后Kernel在调用时分析此内存数据,最后也会放到标准command_line缓存中,像上面一样处理.
[Setup.c]
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
static int __init parse_tag_cmdline(const struct tag *tag)
{
strncpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
default_command_line[COMMAND_LINE_SIZE - 1] = '\0'; //放到default_command_line中
return 0;
}