how does the bootloader pass the kernel command line to the kernel?

Viewed 1289

We can see the kernel command line like this :

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

This command line seems to be what grub has passed to the kernel for booting. How is this command line actually passed to the kernel program? I guess maybe the command line is loaded in memory as a string and a register of the processor (like x1, if arm64 processor) is set to that string address? I'm interested in arm64 case especially.

3 Answers

In the kernel source code init/main.c, the kernel entry point is 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);
[...]

In the preceding lines, there is a call to setup_arch() to get the command line in an architecture dependent way.

For ARM64, setup_arch() is defined in 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;
[...]

Concerning grub, it is detailed here.

The same question sometimes popped up to me. So this is a moment to deep dive.

While x86 uses special protocol for booting Linux, in the case of arm64 a different scheme is used. For communication with kernel, boot loader puts only single address - loaded flatten device tree (FDT) into the x0 register.

Here is excerpt from 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.

Next, during kernel early boot, the OF/FDT module parses the passed device tree and fill in boot_command_line variable.

The details vary based on the bootloader and he head.S assembly language startup module of the specific kernel.

On Arm64, that startup code is in the file arch/arm64/kernel/head.S.

When the startup code is invoked, it assumes that the registers x0 through x3 are boot parameters and saves them in the boot_params array. Later, C code validates that only the first parameter is nonzero. The other three must be zero; evidently, they were historically used.

Thus, there is only one parameter, effectively. What is it? The x0 register points to the FDT (flattened device tree). This device tree blob contains the command line also.

See the code in drivers/of/fdt.c how the command line is retrieved from he DT blob (by looking for a node called "bootargs"), and retained.

Related