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
cmdline的"console="参数
内核启动过程中,把grub设置的cmdline保存在init/main.c 的boot_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(¶m_lock);
err = params[i].ops->set(val, ¶ms[i]);
mutex_unlock(¶m_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,
根据定义在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,
正如内核中的注释所说:
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_t
即typedef 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_init
用init_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_options
和 uart_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_console和preferred_console,这两个都是定义在 kernel/printk/printk.c 中的静态变量。
preferred_console只有在函数 register_console
中才会修改,而且赋值语句的右值都是selected_console;selected_console的赋值都在 __add_preferred_console
函数。
函数的逆调用路径为:__add_preferred_console
<- console_setup
<- obsolete_checksetup
(init/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
, 将参数 param
和 value
拼接成 "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,register_console
函数时,selected_console=-1;对于 "console=ttyS__add_preferred_console
函数时,console_cmdline变量中没有元素,因此会把新增的控制台保存在数组中的一个元素,执行 register_console
时selected_console=0 。
作者:glob
出处:http://www.cnblogs.com/adera/
欢迎访问我的个人博客:https://blog.globs.site/
本文版权归作者和博客园共有,转载请注明出处。