我的分析是基于Linux4.15.1

1.看看kernel是如何调用到console初始化函数的:

   分两条线:

    a.start_kernel  -->  console_init   --> call = __con_initcall_start  

       去调用放在__con_initcall_start和__con_initcall_end之间的所有函数,看看这个区间的有哪些函数.

__con_initcall_start = .;
    *(.con_initcall.init)
__con_initcall_end = .;

    b.start_kernel  -->  rest_init  -->  kernel_init  -->  kernel_init_freeable  --> do_basic_setup  -->  do_initcalls .

       do_initcalls()将按顺序从由__initcall_start开始,到__initcall_end结束的section中以函数指针的形式取出这些编译到内核的驱动模块中初始化函数起始地址,来依次完成相应的初始化.

2.看看跟单板芯片相关的函数是如何放到__initcall这个section的。

   在drivers/tty/serial/samsung.c里面有如下定义:console_initcall(s3c24xx_serial_console_init);,去include/linux/init.h里面查看console_initcall的定义:

#define console_initcall(fn) \
static initcall_t __initcall_##fn \
__used __section(.con_initcall.init) = fn

因此在执行console_init的时候,s3c24xx_serial_console_init也会被调用,进而执行register_console(&s3c24xx_serial_console);,结构体s3c24xx_serial_console的定义如下,

static struct console s3c24xx_serial_console = {
    .name        = S3C24XX_SERIAL_NAME,
    .device        = uart_console_device,
    .flags        = CON_PRINTBUFFER,
    .index        = -1,
    .write        = s3c24xx_serial_console_write,
    .setup        = s3c24xx_serial_console_setup,
    .data        = &s3c24xx_uart_drv,
};

相当于将此结构体注册进内核,注意这里的名称:#define S3C24XX_SERIAL_NAME "ttySAC",是不是很眼熟,没错,这个就是我们在bootargs里面设置的console的名称。

这里也有一个write函数,就是通过这个函数将打印信息送到串口上的。

在register_console的时候,还会调用每个console的setup函数,对应于s3c2440的话,就是s3c24xx_serial_console_setup函数了。

    if (!has_preferred) {
        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;
                has_preferred = true;
            }
        }
    }

console就这样注册进内核了,如果细看注册过程,其实是将此console的信息记录到static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];这个数组里面了,也由此可知内核最多允许8个console。

3.再看看我们传进去的console是如何跟上面注册的相关联起来的。

   上面的代码是内核自动会运行的,所以可以看出,内核在初始化的时候注册了一个名为ttySAC*的console,里面还有write的函数直接操作串口,一般这个console就是ttySAC0了,这就是为什么我们在uboot里面传参数的时候console=ttySAC0的原因,就是为了跟内核里面注册的console名称匹配,每一款芯片可能这个名字都不一样,要自己看源码。

接下来就看我们传进去的console=ttySAC0是如何跟内核自动注册的console关联起来的。

在内核里面搜索"console="看看哪里解析了我们传进来的参数:

可以搜到在kernel/printk/printk.c里面有如下定义:

__setup("console=", console_setup);

当uboot通过"console="传入参数时,内核里面是用__setup宏申明的方法来处理的,前提是要名称匹配,也就是说,内核是通过调用console_setup来处理uboot传入的“console=”这个命令行的,也就是在console_cmdline[MAX_CMDLINECONSOLES]这个数组里面查看是否有相同的名称的console:

    for (i = 0, c = console_cmdline;
         i < MAX_CMDLINECONSOLES && c->name[0];
         i++, c++) {
        if (strcmp(c->name, name) == 0 && c->index == idx) {
            if (!brl_options)
                preferred_console = i;
            return 0;
        }
    }

如果找到名称匹配的,则将其记录在preferred_console这个变量里,关于console_setup这个函数的分析,网上很多,我这里就不细讲了。

这样用户指定的console就用起来了。

梳理一遍:

用户指定console    -->    在内核注册的console里面有这个设备   -->  于是通过名字建立了关系。