引导加载程序如何将内核命令行传递给内核?
how does the bootloader pass the kernel command line to the kernel?
我们可以看到这样的内核命令行:
ckim@chan-ubuntu:~/$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.15.0-122-generic root=UUID=99c66a0a-39c1-451c-9f72-ad1576aafb41 ro quiet splash
这个命令行似乎是grub传递给内核用于引导的。这个命令行实际上是如何传递给内核程序的?我想也许命令行作为字符串加载到内存中,并且处理器的寄存器(如 x1,如果是 arm64 处理器)设置为该字符串地址?我对 arm64 的情况特别感兴趣。
在内核源代码init/main.c中,内核入口点是start_kernel() :
[...]
/* Untouched command line saved by arch-specific code. */
char __initdata boot_command_line[COMMAND_LINE_SIZE];
[...]
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them.
*/
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
early_security_init();
setup_arch(&command_line);
[...]
pr_notice("Kernel command line: %s\n", boot_command_line);
[...]
在前面的几行中,调用了 setup_arch() 以依赖于体系结构的方式获取命令行。
对于ARM64,setup_arch()定义在arch/arm64/kernel/setup.c:
void __init setup_arch(char **cmdline_p)
{
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
*cmdline_p = boot_command_line;
[...]
关于grub,详细here。
同样的问题有时会突然出现在我面前。所以这是深入研究的时刻。
而 x86 使用 special protocol for booting Linux, in the case of arm64 a different scheme is used。对于与内核的通信,引导加载程序仅将单个地址 - 加载 flatten device tree (FDT) 放入 x0
寄存器。
这里摘录自Linux and the Device Tree, Runtime configuration:
In most cases, a DT will be the sole method of communicating data from
firmware to the kernel, so also gets used to pass in runtime and
configuration data like the kernel parameters string and the location
of an initrd image.
Most of this data is contained in the /chosen node, and when booting
Linux it will look something like this:
chosen {
bootargs = "console=ttyS0,115200 loglevel=8";
initrd-start = <0xc8000000>;
initrd-end = <0xc8200000>;
};
Here is another example of DT.
接下来,在内核早期启动期间,OF/FDT模块解析传递的设备树和fill in boot_command_line
variable .
具体细节因bootloader和他head.S
特定内核的汇编语言启动模块而异。
在 Arm64 上,该启动代码在文件 arch/arm64/kernel/head.S
.
中
当调用启动代码时,它假定寄存器x0
到x3
是启动参数并将它们保存在boot_params
数组中。后来,C 代码验证只有第一个参数是非零的。其他三个必须为零;显然,它们在历史上被使用过。
因此,实际上只有一个参数。它是什么? x0
寄存器指向 FDT(扁平设备树)。此设备树 blob 还包含命令行。
查看 drivers/of/fdt.c
中的代码如何从 DT blob 中检索命令行(通过查找名为 "bootargs"
的节点)并保留。
我们可以看到这样的内核命令行:
ckim@chan-ubuntu:~/$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.15.0-122-generic root=UUID=99c66a0a-39c1-451c-9f72-ad1576aafb41 ro quiet splash
这个命令行似乎是grub传递给内核用于引导的。这个命令行实际上是如何传递给内核程序的?我想也许命令行作为字符串加载到内存中,并且处理器的寄存器(如 x1,如果是 arm64 处理器)设置为该字符串地址?我对 arm64 的情况特别感兴趣。
在内核源代码init/main.c中,内核入口点是start_kernel() :
[...]
/* Untouched command line saved by arch-specific code. */
char __initdata boot_command_line[COMMAND_LINE_SIZE];
[...]
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them.
*/
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
early_security_init();
setup_arch(&command_line);
[...]
pr_notice("Kernel command line: %s\n", boot_command_line);
[...]
在前面的几行中,调用了 setup_arch() 以依赖于体系结构的方式获取命令行。
对于ARM64,setup_arch()定义在arch/arm64/kernel/setup.c:
void __init setup_arch(char **cmdline_p)
{
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
*cmdline_p = boot_command_line;
[...]
关于grub,详细here。
同样的问题有时会突然出现在我面前。所以这是深入研究的时刻。
而 x86 使用 special protocol for booting Linux, in the case of arm64 a different scheme is used。对于与内核的通信,引导加载程序仅将单个地址 - 加载 flatten device tree (FDT) 放入 x0
寄存器。
这里摘录自Linux and the Device Tree, Runtime configuration:
In most cases, a DT will be the sole method of communicating data from firmware to the kernel, so also gets used to pass in runtime and configuration data like the kernel parameters string and the location of an initrd image.
Most of this data is contained in the /chosen node, and when booting Linux it will look something like this:
chosen { bootargs = "console=ttyS0,115200 loglevel=8"; initrd-start = <0xc8000000>; initrd-end = <0xc8200000>; };
Here is another example of DT.
接下来,在内核早期启动期间,OF/FDT模块解析传递的设备树和fill in boot_command_line
variable .
具体细节因bootloader和他head.S
特定内核的汇编语言启动模块而异。
在 Arm64 上,该启动代码在文件 arch/arm64/kernel/head.S
.
当调用启动代码时,它假定寄存器x0
到x3
是启动参数并将它们保存在boot_params
数组中。后来,C 代码验证只有第一个参数是非零的。其他三个必须为零;显然,它们在历史上被使用过。
因此,实际上只有一个参数。它是什么? x0
寄存器指向 FDT(扁平设备树)。此设备树 blob 还包含命令行。
查看 drivers/of/fdt.c
中的代码如何从 DT blob 中检索命令行(通过查找名为 "bootargs"
的节点)并保留。