1.system.map映射表
参考之前写的一篇博客 点击打开链接
2.从内核第二阶段启动分析控制台console注册流程
路径: linux-3.10.101/init/main.c
asmlinkage void __init start_kernel(void)
{
//......
pr_notice("Kernel command line: %s\n", boot_command_line);
parse_early_param(); //解析控制台参数
parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, -1, -1, &unknown_bootoption);
//......
console_init(); //控制台初始化
//......
}
3. parse_early_param()
路径:linux-3.10.101/init/main.c/start_kernel()
void __init parse_early_param(void)
{
static __initdata int done = 0;
static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];
if (done)
return;
/* All fall through to do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
done = 1;
}
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, 0, 0, do_early_param);
}
static int __init do_early_param(char *param, char *val, const char *unused)
{
const struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) {
if ((p->early && parameq(param, p->str)) ||
(strcmp(param, "console") == 0 &&
strcmp(p->str, "earlycon") == 0)
) {
if (p->setup_func(val) != 0)
pr_warn("Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
分析:这里主要了解__setup_start和__setup_end的意义,可以参考之前写的一篇博客“linux _setup” 击打开链接,这里不在赘述!所以for循环遍历system.map表中__setup_start、__setup_end区间内映射表,
c04373e0 T __setup_start
c04373ec t __setup_init_setup
c04373f8 t __setup_loglevel
c0437404 t __setup_quiet_kernel
//......
c0437560 t __setup_console_setup //注意这里
//......
c0437788 T __setup_end
从而找到__setup_console_setup对应的结构体内容,最终调用console_setup函数:
- struct obs_kernel_param {
- const char *str=“console”
- int (*setup_func)(char *)=console_setup
- int early=0
- };
static int __init console_setup(char *str)
{
char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */
char *s, *options, *brl_options = NULL;
int idx;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
if (!memcmp(str, "brl,", 4)) {
brl_options = "";
str += 4;
} else if (!memcmp(str, "brl=", 4)) {
brl_options = str + 4;
str = strchr(brl_options, ',');
if (!str) {
printk(KERN_ERR "need port name after brl=\n");
return 1;
}
*(str++) = 0;
}
#endif
/*
* Decode str into name, index, options.
*/
if (str[0] >= '0' && str[0] <= '9') {
strcpy(buf, "ttyS");
strncpy(buf + 4, str, sizeof(buf) - 5);
} else {
strncpy(buf, str, sizeof(buf) - 1);
}
buf[sizeof(buf) - 1] = 0;
if ((options = strchr(str, ',')) != NULL)
*(options++) = 0;
#ifdef __sparc__
if (!strcmp(str, "ttya"))
strcpy(buf, "ttyS0");
if (!strcmp(str, "ttyb"))
strcpy(buf, "ttyS1");
#endif
for (s = buf; *s; s++)
if ((*s >= '0' && *s <= '9') || *s == ',')
break;
idx = simple_strtoul(s, NULL, 10);
*s = 0;
__add_preferred_console(buf, idx, options, brl_options); //下面我们继续分析该函数
console_set_on_cmdline = 1;
return 1;
}
__setup("console=", console_setup);
static int __add_preferred_console(char *name, int idx, char *options,
char *brl_options)
{
struct console_cmdline *c;
int i;
/*
* See if this tty is not yet registered, and
* if we have a slot free.
*/
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) {
if (!brl_options)
selected_console = i;
return 0;
}
if (i == MAX_CMDLINECONSOLES)
return -E2BIG;
if (!brl_options)
selected_console = i;
c = &console_cmdline[i]; //注意这里,最终将控制台内容加入到该全局变量结构体中
strlcpy(c->name, name, sizeof(c->name));
c->options = options;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
c->brl_options = brl_options;
#endif
c->index = idx;
return 0;
}
start_kernel-->parse_args-->parse_early_options-->parse_args-->do_early_param-->console_setup-->__add_preferred_console-->console_cmdline[i],在该函数内部,将控制台参数保存到到了全局变量结构体console_cmdline[i]中,接下来分析控制台的注册流程。
4.console_init
void __init console_init(void)
{
initcall_t *call;
/* Setup the default TTY line discipline. */
tty_ldisc_begin(); //设置默认线路规程
/*
* set up the console device so that later boot sequences can
* inform about problems etc..
*/
call = __con_initcall_start; //通过查找system.map,这里会调用console_initcall(con_init);函数进行ttyS0控制台初始化
while (call < __con_initcall_end) {
(*call)();
call++;
}
}
根据之前写的一篇博客“linux _setup” 击打开链接,很容易确定__con_initcall_start在system.map的位置,内容如下:
c0437ad0 T __con_initcall_start
c0437ad0 t __initcall_con_init
c0437ad0 T __initcall_end
c0437ad4 t __initcall_nuc970serial_console_init //ttyS0控制台
c0437ad8 T __con_initcall_end
在映射表中有__initcall_con_init和__initcall_nuc970serial_console_init两个控制台程序。
__initcall_con_init有点不好找,我是通过内核启动时打印的报文找到的,报文内容如下:
......
Console: colour dummy device 80x30//linux-3.10.x/drivers/tty/vt/vt.c line=2905
console [ttyS0] enabled//linux-3.10.x/kernel/prink.c line=2387
......
路径:linux-3.10.x/drivers/tty/vt/vt.c
static int __init con_init(void)
{
const char *display_desc = NULL;
struct vc_data *vc;
unsigned int currcons = 0, i;
console_lock();
if (conswitchp)
display_desc = conswitchp->con_startup();
if (!display_desc) {
fg_console = 0;
console_unlock();
return 0;
}
for (i = 0; i < MAX_NR_CON_DRIVER; i++) {
struct con_driver *con_driver = ®istered_con_driver[i];
if (con_driver->con == NULL) {
con_driver->con = conswitchp;
con_driver->desc = display_desc;
con_driver->flag = CON_DRIVER_FLAG_INIT;
con_driver->first = 0;
con_driver->last = MAX_NR_CONSOLES - 1;
break;
}
}
for (i = 0; i < MAX_NR_CONSOLES; i++)
con_driver_map[i] = conswitchp;
if (blankinterval) {
blank_state = blank_normal_wait;
mod_timer(&console_timer, jiffies + (blankinterval * HZ));
}
for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) {
vc_cons[currcons].d = vc = kzalloc(sizeof(struct vc_data), GFP_NOWAIT);
INIT_WORK(&vc_cons[currcons].SAK_work, vc_SAK);
tty_port_init(&vc->port);
visual_init(vc, currcons, 1);
vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT);
vc_init(vc, vc->vc_rows, vc->vc_cols,
currcons || !vc->vc_sw->con_save_screen);
}
currcons = fg_console = 0;
master_display_fg = vc = vc_cons[currcons].d;
set_origin(vc);
save_screen(vc);
gotoxy(vc, vc->vc_x, vc->vc_y);
csi_J(vc, 0);
update_screen(vc);
pr_info("Console: %s %s %dx%d\n",
vc->vc_can_do_color ? "colour" : "mono",
display_desc, vc->vc_cols, vc->vc_rows);
printable = 1;
console_unlock();
#ifdef CONFIG_VT_CONSOLE
register_console(&vt_console_driver); //这里很重要,内核启动时打印的信息“Console: colour dummy device 80x30”, “console [ttyS0] enabled”就在里面
#endif
return 0;
}
console_initcall(con_init);
static struct console vt_console_driver = {
.name = "tty",
.write = vt_console_print,
.device = vt_console_device,
.unblank = unblank_screen,
.flags = CON_PRINTBUFFER,
.index = -1,
};
#endif
return 0;
}
console_initcall(con_init);
static struct console vt_console_driver = {
.name = "tty",
.write = vt_console_print,
.device = vt_console_device,
.unblank = unblank_screen,
.flags = CON_PRINTBUFFER,
.index = -1,
};
路径:linux-3.10.101/drivers/tty/serial/nuc970_serial.c
static int __init nuc970serial_console_init(void)
{
nuc970serial_init_ports();
register_console(&nuc970serial_console);
return 0;
}
console_initcall(nuc970serial_console_init);
static struct console nuc970serial_console = {
.name = "ttyS",
.write = nuc970serial_console_write,
.device = uart_console_device,
.setup = nuc970serial_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &nuc970serial_reg,
};
con_init和nuc970serial_console_init最终都会调用register_console注册控制台,我们继续分析register_console,源码如下:
void register_console(struct console *newcon)
{
int i;
unsigned long flags;
struct console *bcon = NULL;
/*
* before we register a new CON_BOOT console, make sure we don't
* already have a valid console
*/
if (console_drivers && newcon->flags & CON_BOOT) {
/* find the last or real console */
for_each_console(bcon) {
if (!(bcon->flags & CON_BOOT)) {
printk(KERN_INFO "Too late to register bootconsole %s%d\n",
newcon->name, newcon->index);
return;
}
}
}
if (console_drivers && console_drivers->flags & CON_BOOT)
bcon = console_drivers;
if (preferred_console < 0 || bcon || !console_drivers)
preferred_console = selected_console;
if (newcon->early_setup)
newcon->early_setup();
/*
* See if we want to use this console driver. If we
* didn't select a console we take the first one
* that registers here.
*/
if (preferred_console < 0) {
if (newcon->index < 0)
newcon->index = 0;
if (newcon->setup == NULL ||
newcon->setup(newcon, NULL) == 0) {
newcon->flags |= CON_ENABLED;
if (newcon->device) {
newcon->flags |= CON_CONSDEV;
preferred_console = 0;
}
}
}
/*
*add by CL 20171129 23:16
*下面的console_cmdline[i]变量是在static int __init console_setup(char *str)函数内部增加的,
*而console_setup函数是通过__setup("console=", console_setup);添加到system.map表中,至于ttyS0
*设备是什么时候通过console_setup添加到console_cmdline[i]中,关于添加是在start_kernel->parse_early_param()->parse_early_options(tmp_cmdline);
*里操作的。
*/
/*
* See if this console matches one we selected on
* the command line.
*/
for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];
i++) {
if (strcmp(console_cmdline[i].name, newcon->name) != 0) //�Ƚϵ���ttyS0 ttyUSB0,�����ں���������������������̨
continue;
if (newcon->index >= 0 &&
newcon->index != console_cmdline[i].index)
continue;
if (newcon->index < 0)
newcon->index = console_cmdline[i].index;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
if (console_cmdline[i].brl_options) {
newcon->flags |= CON_BRL;
braille_register_console(newcon,
console_cmdline[i].index,
console_cmdline[i].options,
console_cmdline[i].brl_options);
return;
}
#endif
if (newcon->setup &&
newcon->setup(newcon, console_cmdline[i].options) != 0)
break;
newcon->flags |= CON_ENABLED;
newcon->index = console_cmdline[i].index;
if (i == selected_console) {
newcon->flags |= CON_CONSDEV;
preferred_console = selected_console;
}
break;
}
if (!(newcon->flags & CON_ENABLED))
return;
/*
* If we have a bootconsole, and are switching to a real console,
* don't print everything out again, since when the boot console, and
* the real console are the same physical device, it's annoying to
* see the beginning boot messages twice
*/
if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))
newcon->flags &= ~CON_PRINTBUFFER;
/*
* Put this console in the list - keep the
* preferred driver at the head of the list.
*/
console_lock();
if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {
newcon->next = console_drivers;
console_drivers = newcon;
if (newcon->next)
newcon->next->flags &= ~CON_CONSDEV;
} else {
newcon->next = console_drivers->next;
console_drivers->next = newcon;
}
if (newcon->flags & CON_PRINTBUFFER) {
/*
* console_unlock(); will print out the buffered messages
* for us.
*/
raw_spin_lock_irqsave(&logbuf_lock, flags);
console_seq = syslog_seq;
console_idx = syslog_idx;
console_prev = syslog_prev;
raw_spin_unlock_irqrestore(&logbuf_lock, flags);
/*
* We're about to replay the log buffer. Only do this to the
* just-registered console to avoid excessive message spam to
* the already-registered consoles.
*/
exclusive_console = newcon;
}
console_unlock();
console_sysfs_notify();
/*
* By unregistering the bootconsoles after we enable the real console
* we get the "console xxx enabled" message on all the consoles -
* boot consoles, real consoles, etc - this is to ensure that end
* users know there might be something in the kernel's log buffer that
* went to the bootconsole (that they do not see on the real console)
*/
if (bcon &&
((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
!keep_bootcon) {
/* we need to iterate through twice, to make sure we print
* everything out, before we unregister the console(s)
*/
printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",
newcon->name, newcon->index);
for_each_console(bcon)
if (bcon->flags & CON_BOOT)
unregister_console(bcon);
} else {
printk(KERN_INFO "%sconsole [%s%d] enabled\n",
(newcon->flags & CON_BOOT) ? "boot" : "" ,
newcon->name, newcon->index);
}
}
EXPORT_SYMBOL(register_console);
到这里控制台就完成了注册。
5.总结
通过第2点将控制台相应的真是串口如ttyS0\ttyUSB0的信息存储到全局结构体console_cmdline[]中。
通过第4点注册控制台,其实就是从注册的控制台设备(.name = "ttyS",.name = "tty",)与全局结构体console_cmdline中的内容比较,成功就是创建该设备作为控制台
本文最需了解的是__setup_start、 __setup_end,__con_initcall_start、 __con_initcall_end在system.map的意义、功能,同时需清楚__setup("console=", console_setup)、 console_initcall(con_init)、console_initcall(nuc970serial_console_init);
理解这几个函数的意义对于内核的架构设计还是很有意义的!