grub参数console=

本文主要分析Linux内核如何处理grub参数中的console=ttyS0,115200n8部分,中间还会穿插一些 include/linux/init.h 的内容。
grub参数中的console=有多种形式,根据Documentation/kernel-parameters.txt 文件,

console= [KNL] Output console device and options.

  • tty Use the virtual console device .
  • ttyS[,options]
  • ttyUSB0[,options]
    Use the specified serial port. The options are of the form "bbbbpnf", where "bbbb" is the baud rate, "p" is parity ("n", "o", or "e"), "n" is number of bits, and "f" is flow control ("r" for RTS or omit it). Default is "9600n8".
  • uart[8250],io,[,options]
  • uart[8250],mmio,[,options]
    Start an early, polled-mode console on the 8250/16550 UART at the specified I/O port or MMIO address, switching to the matching ttyS device later. The options are the same as for ttyS, above.
  • hvc Use the hypervisor console device .
    This is for both Xen and PowerPC hypervisors.

本文主要分析参数值为ttyS,uart[8250],io/mmio,[,options]的情况。

cmdline的"console="参数

内核启动过程中,把grub设置的cmdline保存在init/main.cboot_command_line字符数组中,长度最大为256。
start_kernel 函数在输出过boot_command_line信息后,会对其进行转化,分别调用parse_early_param -> parse_early_options -> parse_args(kernel/params.c) -> do_early_param 。其中,parse_args 函数调用 parse_one 完成具体的操作的各个参数如下:

/* @param = "console"
   @val = "ttyS0,115200n8"或者"uart[8250],io,<addr>[,option]"
   @doing = "early options"
   @params = NULL
   @num_params = 0
   @min_level = 0
   @max_level = 0
   @unknown = do_early_param */
static int parse_one(char *param,
             char *val,
             const char *doing,
             const struct kernel_param *params,
             unsigned num_params,
             s16 min_level,
             s16 max_level,
             int (*handle_unknown)(char *param, char *val,
                     const char *doing))
{
    unsigned int i;
    int err;

    /* Find parameter */
    for (i = 0; i < num_params; i++) {
        if (parameq(param, params[i].name)) {
            if (params[i].level < min_level
                || params[i].level > max_level)
                return 0;
            /* No one handled NULL, so do it here. */
            if (!val &&
                !(params[i].ops->flags & KERNEL_PARAM_FL_NOARG))
                return -EINVAL;
            pr_debug("handling %s with %p\n", param,
                params[i].ops->set);
            mutex_lock(&param_lock);
            err = params[i].ops->set(val, &params[i]);
            mutex_unlock(&param_lock);
            return err;
        }
    }

    if (handle_unknown) {
        pr_debug("doing %s: %s='%s'\n", doing, param, val);
        return handle_unknown(param, val, doing);
    }
    
    pr_debug("Unknown argument '%s'\n", param);
    return -ENOENT;
}

最终,handle_unknown(param, val, doing) -> do_early_param(param, val, doing)

/* @param = "console"
   @val = "uart[8250],io,<addr>[,option]"
   @unused = "early options" */
static int __init do_early_param(char *param, char *val, const char *unused)
{
    const struct obs_kernel_param *p;
    /* __setup_start声明在init/main.c文件中,通过lds文件链接到名为
       .init.setup 的段中,这个段通过include/linux/init.h中的宏
       __setup定义.
       因此,这里会遍历所有通过__setup宏定义的函数,选择设置了early=1
       的obs_kernel_param对象,并且str="earlycon"的函数,
       执行其setup_func */
    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;
}

设置了early标志的struct obs_kernel_param对象通过early_param宏定义,在 include/linux/init.h 中:

 #define early_param(str, fn) 
    __setup_param(str, fn, fn, 1)
#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 }

console=uart[8250],io/mmio,[,options]

serial8250_console定义在 drivers/tty/serial/8250/8250_core.c 中,是基于uart的控制台,初始化函数为serial8250_console_init ,之后调用 include/linux/init.h 中的console_initcall(serial8250_console_init)

这种情况下,do_early_param(param, val, doing) 的参数param="console""val=uart[8250],io/mmio,[,options]"
根据定义在include/linux/serial_core.h 中的

#define EARLYCON_DECLARE(name, func)
static int __init name##_setup_earlycon(char *buf)
{
    return setup_earlycon(buf, __stringify(name), func);
}
early_param("earlycon", name##_setup_earlycon);

drivers/tty/serial/8250/8250_early.c 中的 EARLYCON_DECLARE(uart8250, early_serial8250_setup);EARLYCON_DECLARE(uart, early_serial8250_setup);

static int __init uart[8250]_setup_earlycon(char *buf)
{
    return setup_earlycon(buf, __stringify(uart[8250]), early_serial8250_setup);
 }
static const char __setup_str_uart[8250]_setup_earlycon[] = "earlycon";
static struct obs_kernel_param __setup_uart[8250]_setup_earlycon = {
        .str = "earlycon",
        .setup_func = uart[8250]_setup_earlycon,
        .early = 1,
    };

do_early_param 函数中的 p->setup_func(val)uart[8250]_setup_earlycon(val) -> setup_earlycon("uart[8250],io/mmio,<addr>[,options]", "uart8250", early_serial8250_setup) ,定义在drivers/tty/serial/earlycon.c

/* @buf = "uart[8250],io/mmio,<addr>[,options]"
   @match = "uart8250"
   @setup = early_8250_setup */
int __init setup_earlycon(char *buf, const char *match,
              int (*setup)(struct earlycon_device *, const char *))
{
    int err;
    size_t len;
    struct uart_port *port = &early_console_dev.port;
    // if条件不满足,不会返回
    if (!buf || !match || !setup)
        return 0;

    len = strlen(match);
    // if条件不满足,不会返回
    if (strncmp(buf, match, len))
        return 0;
    if (buf[len] && (buf[len] != ','))
        return 0;
    //buf指向io/mmio的位置
    buf += len + 1;
    //将buf中的参数信息保存在early_console_dev对象中
    err = parse_options(&early_console_dev, buf);
    /* On parsing error, pass the options buf to the setup function */
    if (!err)
        buf = NULL;

    if (port->mapbase)
        port->membase = earlycon_map(port->mapbase, 64);

    early_console_dev.con->data = &early_console_dev;
    /* setup -> early_serial8250_setup -> init_port */
    err = setup(&early_console_dev, buf);
    if (err < 0)
        return err;
    // set in early_serial8250_setup, is early_serialt8250_write
    if (!early_console_dev.con->write)
        return -ENODEV;
    //控制台信息初始化完成后,调用register_console注册控制台
    register_console(early_console_dev.con);
    return 0;
}

register_console

根据代码中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.

控制台驱动在内核初始化时调用这个函数将控制台输出程序注册到 printk 函数,在控制台驱动初始化前打印内核的输出信息。

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.

内核可能在启动过程的很早阶段(This指的是)输出信息(由于early_printk ),有时会在 setup_arch 函数完成之前,注意使用的内核features,这些features可能还没有初始化。

There are two types of consoles - bootconsoles (early_printk) and "real" consoles (everything which is not a bootconsole) which are handled differently.

  • Any number of bootconsoles can be registered at any time.
  • As soon as a "real" console is registered, all bootconsoles will be unregistered automatically.
  • Once a "real" console is registered, any attempt to register a bootconsoles will be rejected

控制台分为两大类:bootconsoles( early_printk )和真正的控制台(除了bootconsole之外的控制台),按照不同的方式处理。

  • 可以在任何时刻注册任意数量的bootconsoles
  • 所有的bootconsoles都会在注册真正的控制台的同时被自动注销
  • 一旦注册了真正的控制台,任何注册bootconsoles的尝试都会被拒绝

struct console *console_drivers 包含所有的已经注册的控制台驱动,通过 struct console 内的next指针索引。

针对early_console_dev

struct earlycon_device early_console_dev = {
    .con = early_con = {
        .name = "uart",
        .flags = CON_PRINTBUFFER | CON_BOOT,
        .index = -1,
    };
};

执行 register_console 时,假设console_drivers中没有已经注册的驱动,

void register_console(struct console *newcon)
{
    int i;
    unsigned long flags;
    struct console *bcon = NULL;
    struct console_cmdline *c;
    ...
    //if条件满足,但是prefered和selected都是-1
    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) {  //true
        if (newcon->index < 0)  //true
            newcon->index = 0;
        if (newcon->setup == NULL ||   //true
            newcon->setup(newcon, NULL) == 0) {
            newcon->flags |= CON_ENABLED;  //set
            if (newcon->device) {    //false
                newcon->flags |= CON_CONSDEV;
                preferred_console = 0;
            }
        }
    }
    /* 现在newcon = {
               .name = "uart",
               .flags = CON_PRINTBUFFER | CON_BOOT | CON_ENABLED,,
               .index = 0,
          }; */
      /*
     *  See if this console matches one we selected on
     *  the command line.
     */
    for (i = 0, c = console_cmdline;
         i < MAX_CMDLINECONSOLES && c->name[0];
         i++, c++) {
        if (strcmp(c->name, newcon->name) != 0)
            continue;   //控制台名称不匹配
        if (newcon->index >= 0 &&
            newcon->index != c->index)
            continue;   //控制台名称匹配,设备index不匹配
        if (newcon->index < 0)   //false
            newcon->index = c->index;

        if (_braille_register_console(newcon, c))   //false
            return;

        if (newcon->setup &&
            newcon->setup(newcon, console_cmdline[i].options) != 0)
            break;      //false
        /* 根据命令行的控制台信息更新newcon参数 */
        newcon->flags |= CON_ENABLED;
        newcon->index = c->index;
        if (i == selected_console) {    //false
            newcon->flags |= CON_CONSDEV;
            preferred_console = selected_console;
        }
        break;
    }
    ...
     /*
     *  Put this console in the list - keep the
     *  preferred driver at the head of the list.
     */
    console_lock();
    //console_drivers = NULL
    if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {
        newcon->next = console_drivers;
        console_drivers = newcon;
        if (newcon->next)   //false
            newcon->next->flags &= ~CON_CONSDEV;
    } else {
        newcon->next = console_drivers->next;
        console_drivers->next = newcon;
    }
    if (newcon->flags & CON_PRINTBUFFER) {  //true
        /*
         * 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();
    ...
    //这是一个boot console
    pr_info("%sconsole [%s%d] enabled\n",
        (newcon->flags & CON_BOOT) ? "boot" : "" ,
        newcon->name, newcon->index);

}

至此,通过 console=uart[8250],io/mmio,[,options] 设置的控制台已经注册到内核中, 可以正常工作。
正如内核中的注释所说:

Start an early, polled-mode console on the 8250/16550 UART at the specified I/O port or MMIO address, switching to the matching ttyS device later. The options are the same as for ttyS, above.

这种类型的控制台是轮询模式的,之后会切换到对应的 ttyS 设备,并且采用相同的参数信息。

console=ttyS[,options]

这种类型的控制台并不通过early_param 宏定义,没有设置early标志,因此不会在 do_early_param 函数中调用。

console_initcall

console_initcall 宏有两种定义,一种是MODULE下的定义:#define console_initcall(fn) module_init(fn)module_init 的定义为:

#define module_init(initfn) 
    static inline initcall_t __inittest(void)   
    { return initfn; }  
    int init_module(void) __attribute__((alias(#initfn)));

initcall_ttypedef int (*initcall_t)(void); ,于是,console_initcall(serial8250_console_init); 就变成了

static inline int _inittest(void) { return serial8250_console_init; }
int init_module(void) __attribute__((alias("serial8250_console_init"));

__attribute__((alias("serial8250_console_init")) 这个GCC函数属性alias告诉编译器,把serial8250_console_initinit_module 代替。

inbox mode

还有一种是inbox mode(make menuconfig时直接选择某个模块,而不是M),这种情况下

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

console_initcall(serial8250_console_init); ( drivers/tty/serial/8250/8250_core.c )即

static int __initcall_serial8250_console_init __used __section(.con_initcall.init) = serial8250_console_init;

定义了一个名为__initcall_serial8250_console_init 的函数,具有used属性(code must be emitted even if it appears that function is not referenced),放在名为 .con_initcall.init 的段中。
根据lds的内容,这个 .con_initcall.init 段会被链接到 include/linux/init.h__con_initcall_start 指向的地址(也可以查看内核源码目录下的System.map文件,搜索名为 __initcall_serial8250_console_init 函数),然后在 drivers/tty/tty_io.c 中的 console_init 函数调用。
完整的函数调用路径为:start_kernel ( init/main.c ) -> console_init -> __initcall_serial8250_console_init -> serialt8250_console_init

serial8250_console

serial8250_console_init 函数首先调用 serial8250_isa_init_ports 根据 arch/x86/include/asm/serial.h 中的SERIAL_PORT_DFNS信息对struct uart_8250_port serial8250_ports 结构体对象进行初始化,然后再调用 kernel/printk/printk.c -> register_console(&serial8250_console) 注册8250控制台到内核。

static struct console serial8250_console = {
    .name       = "ttyS",
    .write      = serial8250_console_write,
    .device     = uart_console_device,
    .setup      = serial8250_console_setup,
    .early_setup    = serial8250_console_early_setup,
    .flags      = CON_PRINTBUFFER | CON_ANYTIME,
    .index      = -1,
    .data       = &serial8250_reg,
};

由于serial8250_console之前没有注册过,也不具有CON_BOOT标志,因此会直接执行

void register_console(struct console *newcon)
{
    int i;
    unsigned long flags;
    struct console *bcon = NULL;
    struct console_cmdline *c;
    ...
    /* 如果有console=uart参数,此时console_drivers已经包含uart类型的
       控制台,而且是一个bootconsole,下列两个if条件都会满足 */
    if (console_drivers && console_drivers->flags & CON_BOOT)
        bcon = console_drivers;

    if (preferred_console < 0 || bcon || !console_drivers)
        preferred_console = selected_console;
    // serial8250_console_early_setup
    if (newcon->early_setup)
        newcon->early_setup();

newcon->early_setup() -> serial8250_console_early_setup -> serial8250_find_port_for_earlycon

int serial8250_find_port_for_earlycon(void)
{   
    /* 如果设置了uart控制台,这里的early_device就是
       struct earlycon_device early_console_dev,
      在函数early_serial8250_serup里设置,否则就是NULL */
    struct earlycon_device *device = early_device;
    
    /* 如果early_device为NULL,这里port就为NULL,
       在之后的代码语句会直接返回-ENODEV, 否则就根据
       early_device寻找用于控制台的串口信息 */
    struct uart_port *port = device ? &device->port : NULL;
    int line;
    int ret;
    
    if (!port || (!port->membase && !port->iobase))
        return -ENODEV;
        
    /* 查找和serial8250_ports中保存的串口信息相匹配的
       串口line,匹配包括串口的iotype和io地址 */
    line = serial8250_find_port(port);
    if (line < 0)
        return -ENODEV;
    /* 如果console_cmdline数组中已有匹配的uart8250控制台
       信息,将其更新为ttySn信息 */
    ret = update_console_cmdline("uart", 8250,
                 "ttyS", line, device->options);
    /* 如果console_cmdline数组中已有匹配的uart控制台信息
       将其更新为ttySn信息 */
    if (ret < 0)
        ret = update_console_cmdline("uart", 0,
                     "ttyS", line, device->options);

    return ret;
}

int update_console_cmdline(char *name, int idx, char *name_new, int idx_new, char *options)
{
    struct console_cmdline *c;
    int i;
    /* console_cmdline是struct console_cmdline对象数组,
       保存系统中所有的根据cmdline信息构建的控制台信息,
       即grub参数console=,最多为8 */
    for (i = 0, c = console_cmdline;
         i < MAX_CMDLINECONSOLES && c->name[0];
         i++, c++)
        /* 如果name和index匹配,就用新的name替换旧的name,
           并设置option,更新index信息,返回其在console_cmdline
           的数组下标。 */ 
        if (strcmp(c->name, name) == 0 && c->index == idx) {
            strlcpy(c->name, name_new, sizeof(c->name));
            c->name[sizeof(c->name) - 1] = 0;
            c->options = options;
            c->index = idx_new;
            return i;
        }
    /* not found */
    return -1;
}

newcon->early_setup() 返回之后, register_console 函数会继续执行,

    if (preferred_console < 0) {    //假设为true
        if (newcon->index < 0)  //true
            newcon->index = 0;
        if (newcon->setup == NULL ||    //false
            /* serial8250_console_setup 
               newcon = serial8250_console */
            newcon->setup(newcon, NULL) == 0) { //true
            newcon->flags |= CON_ENABLED;
            if (newcon->device) { //true
                newcon->flags |= CON_CONSDEV;
                preferred_console = 0;
            }
        }
    }

serial8250_console_setup 会调用 uart_parse_optionsuart_set_options 函数先把传入的串口参数信息转化,然后设置到串口信息中。这里,由于串口的参数为NULL,uart_set_options 函数会直接采用默认值9600n8,无flow control设置串口信息。
如果newcon和通过cmdline选择的控制台匹配,register_console 根据cmdline的控制台信息设置newcon:

    /* 如果console_cmdline中包含和newcon匹配的控制台,
       用控制台的参数信息重新配置newcon 
       但是这样有一个问题,如果上面的代码preferred<0成立
       会设置newcon->index=0;
       假设console=ttyS1,则console_cmdline的index不会和
       newcon->index相等,就无法根据console_cmdline的信息
       设置控制台参数 */
    for (i = 0, c = console_cmdline;
         i < MAX_CMDLINECONSOLES && c->name[0];
         i++, c++) {
        if (strcmp(c->name, newcon->name) != 0)
            continue;
        if (newcon->index >= 0 &&
            newcon->index != c->index)
            continue;
        if (newcon->index < 0)
            newcon->index = c->index;

        if (_braille_register_console(newcon, c))
            return;

        if (newcon->setup &&
            newcon->setup(newcon, console_cmdline[i].options) != 0)
            break;
        newcon->flags |= CON_ENABLED;
        newcon->index = c->index;
        if (i == selected_console) {
            newcon->flags |= CON_CONSDEV;
            preferred_console = selected_console;
        }
        break;
    }
    ...
    //如果newcon是非bootconsole,释放所有已经注册的bootconsoles
    if (bcon &&
        ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
        !keep_bootcon) {
        /* We need to iterate through all boot consoles, to make
         * sure we print everything out, before we unregister them.
         */
        for_each_console(bcon)
            if (bcon->flags & CON_BOOT)
                unregister_console(bcon);
    }
 

preferred_console

下面,追踪一下变量selected_consolepreferred_console,这两个都是定义在 kernel/printk/printk.c 中的静态变量。
preferred_console只有在函数 register_console 中才会修改,而且赋值语句的右值都是selected_consoleselected_console的赋值都在 __add_preferred_console 函数。
函数的逆调用路径为:__add_preferred_console <- console_setup <- obsolete_checksetupinit/main.c ) <- unknown_bootoption <- parse_args <- start_kernel
先看 start_kernel 函数:

    ...
    pr_notice("Kernel command line: %s\n", boot_command_line);
    // console=uart 在这个函数处理,创建earlycon
    parse_early_param();
    // console=ttyS<n> 在这个函数处理,添加preferred console?
    after_dashes = parse_args("Booting kernel",
                  static_command_line, __start___param,
                  __stop___param - __start___param,
                  -1, -1, &unknown_bootoption);
    ...
    //serial8250_conosole_init调用,根据命令行参数更新控制台信息
    console_init();
    ...

查看System.map文件, __start___param__stop___param 之间包含所有grub参数,查看lds文件, __start___param__stop___param 指向名为 __param 的段。
只有 include/linux/moduleparam.h 中用到了这个段:

#define __module_param_call(prefix, name, ops, arg, perm, level)    
    /* Default value instead of permissions? */         
    static const char __param_str_##name[] = prefix #name; 
    static struct kernel_param __moduleparam_const __param_##name   
    __used                              
    __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) 
    = { __param_str_##name, ops, VERIFY_OCTAL_PERMISSIONS(perm),    
        level, { arg } }

所有的kernel parameters(Documentation/kernel-parameters )都直接或者间接通过这个宏定义,但是其中没有与“console”匹配的参数,所以 parse_one 函数还是会执行 handle_unknown ,即 unknown_bootoption -> obsolete_checksetup
在执行 obsolete_checksetup 前,unknown_bootoption 会先调用repair_env_string , 将参数 paramvalue 拼接成 "param=value"的形式,之后将其作为参数传递给 obsolete_checksetup 函数。
在解析 obsolete_checksetup 函数之前,先说明 include/linux/init.h 中的 __setup 宏:

#define __setup(str, fn)    
    __setup_param(str, fn, fn, 0)

early_param宏类似,, setup 宏也通过 __setup_param 宏定义,只是传入的early参数为0。因此, kernel/printk/printk.c 中的 __setup("console=", console_setup); ,即

static const char __setup__str_console_setup[] = "console=";
static struct obs_kernel_param __setup_console_setup = {
    __setup_str_console_setup => "console=", 
    console_setup,
    0
};

这样,在执行 obsolete_checksetup 函数时,

// @line="console=ttyS0,115200n8"
static int __init obsolete_checksetup(char *line)
{
    const struct obs_kernel_param *p;
    int had_early_param = 0;

    /* __setup_start声明在init/main.c中,
      根据lds文件,__setup_start指向section(.init.setup)
      也可以根据System.map文件,查看__setup_start -
      __setup_end 之间的所有函数.
      这里,会遍历所有定义在section(.init.setup)中的函数 */
    p = __setup_start;
    do {
        int n = strlen(p->str);
        /* 这里,定义在kernel/printk/printk.c中的struct 
           obs_kernel_param __setup_console_setup对象的
           str成员就能够通过检查 */
        if (parameqn(line, p->str, n)) {    //true
            if (p->early) {     //false
                /* Already done in parse_early_param?
                 * (Needs exact match on param part).
                 * Keep iterating, as we can have early
                 * params and __setups of same names 8( */
                if (line[n] == '\0' || line[n] == '=')
                    had_early_param = 1;
            } else if (!p->setup_func) {    //false
                pr_warn("Parameter %s is obsolete, ignored\n",
                    p->str);
                return 1;
            /* 调用console_setup函数,将param对应的value作为参数 */
            } else if (p->setup_func(line + n))
                return 1;
        }
        p++;
    } while (p < __setup_end);

    return had_early_param;
}

因此,接下来会调用 console_setup ( kernel/printk/printk.c )函数:

/* @str="ttyS0,115200n8" */
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;

    if (_braille_console_setup(&str, &brl_options)) //false
        return 1;

    /*
     * Decode str into name, index, options.
     */
    if (str[0] >= '0' && str[0] <= '9') {
        strcpy(buf, "ttyS");
        strncpy(buf + 4, str, sizeof(buf) - 5);
    } else {
        /* sizeof(buf)=12
           buf="ttyS0,11520" */
        strncpy(buf, str, sizeof(buf) - 1);
    }
    buf[sizeof(buf) - 1] = 0;
    if ((options = strchr(str, ',')) != NULL)
        //str="ttyS0" "115200"
        *(options++) = 0;
    for (s = buf; *s; s++)
        if ((*s >= '0' && *s <= '9') || *s == ',')
            break;
    idx = simple_strtoul(s, NULL, 10);
    *s = 0;

    /* buf = ttyS, idx = 0, options = 115200n8 */
    __add_preferred_console(buf, idx, options, brl_options);
    console_set_on_cmdline = 1;
    return 1;
}

接下来调用 __add_preferred_console 函数,

/* @name="ttyS"
   @idx=0
   @options="115200n8"
   @brl_options=NULL  */
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, c = console_cmdline;
         i < MAX_CMDLINECONSOLES && c->name[0];
         i++, c++) {
         //如果console_cmdline中包含匹配的控制台,设置selected_console
        if (strcmp(c->name, name) == 0 && c->index == idx) {
            if (!brl_options)
                selected_console = i;
            return 0;
        }
    }
    if (i == MAX_CMDLINECONSOLES)
        return -E2BIG;
    //console_cmdline中没有匹配的控制台,但是有空闲的slot
    if (!brl_options)
        selected_console = i;
    strlcpy(c->name, name, sizeof(c->name));
    c->options = options;
    braille_set_options(c, brl_options);

    c->index = idx;
    return 0;
}

console_cmdline变量定义在 kernel/printk/printk.c 中的static struct console_cmdline对象的数组,最多包含8个元素,只有 __add_preferred_console 函数会对其进行修改操作。
因此,对于kernel param "console=uart[8250],io/mmio,[,options]" ,执行 register_console 函数时,selected_console=-1;对于 "console=ttyS[,options]" ,执行 __add_preferred_console 函数时,console_cmdline变量中没有元素,因此会把新增的控制台保存在数组中的一个元素,执行 register_consoleselected_console=0

posted @ 2019-07-05 15:30  glob  阅读(2661)  评论(0编辑  收藏  举报