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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM