本文主要分析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.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,[,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";
stat