linux kernel的启动参数是怎么拿到的-以arm64为例

发布时间 2023-10-17 22:33:31作者: 半山随笔

linux kernel拿到启动参数一定是在boot阶段,那就必须从start_kernel找起。

asmlinkage __visible __init __no_sanitize_address __noreturn __no_stack_protector
void start_kernel(void)
{
       。。。
        setup_arch(&command_line);

setup_arch的参数里有command_line,这个就是拿参数用的。看看他是怎么拿到的。

void __init __no_sanitize_address setup_arch(char **cmdline_p)
{
        setup_initial_init_mm(_stext, _etext, _edata, _end);

        *cmdline_p = boot_command_line;

很简单的一句赋值语句就完成了。那boot_command_line又是在哪里赋值的?

继续搜索发现在drivers/of/fdt.c中有

rc = early_init_dt_scan_chosen(boot_command_line);

 这就明白了,原来启动参数是从fdt的chosen里面拿到的。但是这就完了吗?

如果kernel是从fdt启动的那基本到这里就算清楚了,但是如果kernel根本就没有收到fdt呢?这是有可能的,当kernel从efi启动,可以只传给kernel acpi表而不传fdt。请看下面这段代码:

void __init acpi_boot_table_init(void)
{
	/*
	 * Enable ACPI instead of device tree unless
	 * - ACPI has been disabled explicitly (acpi=off), or
	 * - the device tree is not empty (it has more than just a /chosen node,
	 *   and a /hypervisor node when running on Xen)
	 *   and ACPI has not been [force] enabled (acpi=on|force)
	 */
	if (param_acpi_off ||
	    (!param_acpi_on && !param_acpi_force && !dt_is_stub()))
		goto done;

 也即是说如果我们的机器要从acpi启动,那么有可能我们只有一个空的fdt。如果是个空的,启动参数又从哪里找呢?

不知大家有没有发现,在grub将机器的控制权交给kernel的时候,在那些看起来井井有条的kernel log出现之前,我们经常会看到这样的的输出:

EFI stub: Booting Linux Kernel...
EFI stub: EFI_RNG_PROTOCOL unavailable
EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path
EFI stub: Generating empty DTB
EFI stub: Exiting boot services...

这些带着EFI标志的输出是什么东西,又是从哪里来的呢?

grep一下就能看到这些东西都是从kernel efi的driver里面来的。那为啥一个driver运行会先于kernel主体的初始化?简单来讲这是因为efi boot的需要。为了从efi启动kernel,为了能够使用efi的boot service和runtime service,在主体代码初始化之前需要先伪装成efi应用做一些初始化工作。这里我们只关系那句Generating empty DTB。

efi_status_t allocate_new_fdt_and_exit_boot(void *handle,
                                            efi_loaded_image_t *image,
                                            unsigned long *new_fdt_addr,
                                            char *cmdline_ptr)
...
if (!fdt_addr)
                efi_info("Generating empty DTB\n");

        efi_info("Exiting boot services...\n");

       ...
        status = update_fdt((void *)fdt_addr, fdt_size,
                            (void *)*new_fdt_addr, MAX_FDT_SIZE, cmdline_ptr);

 传入的cmdline_ptr指向了启动参数。看看update_fdt干了啥:

static efi_status_t update_fdt(void *orig_fdt, unsigned long orig_fdt_size,
                               void *fdt, int new_fdt_size, char *cmdline_ptr)
{

...
        node = fdt_subnode_offset(fdt, 0, "chosen");
        if (node < 0) {
                node = fdt_add_subnode(fdt, 0, "chosen");
                if (node < 0) {
                        /* 'node' is an error code when negative: */
                        status = node;
                        goto fdt_set_fail;
                }
        }

        if (cmdline_ptr != NULL && strlen(cmdline_ptr) > 0) {
                status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
                                     strlen(cmdline_ptr) + 1);
                if (status)
                        goto fdt_set_fail;
        }

  这里就是bootarg重建的代码了。那现在就剩下一个问题,cmdline_ptr是哪里获得的?

efi_pe_entry->efi_stub_common->efi_boot_kernel->allocate_new_fdt_and_exit_boot->update_fdt

efi_status_t efi_handle_cmdline(efi_loaded_image_t *image, char **cmdline_ptr)
{
...
 /*
         * Get the command line from EFI, using the LOADED_IMAGE
         * protocol. We are going to copy the command line into the
         * device tree, so this can be allocated anywhere.
         */
        cmdline = efi_convert_cmdline(image, &cmdline_size);
/*
 * Convert the unicode UEFI command line to ASCII to pass to kernel.
 * Size of memory allocated return in *cmd_line_len.
 * Returns NULL on error.
 */
char *efi_convert_cmdline(efi_loaded_image_t *image, int *cmd_line_len)
{
        const efi_char16_t *options = efi_table_attr(image, load_options);
...
        snprintf((char *)cmdline_addr, options_bytes, "%.*ls",
                 options_bytes - 1, options);

          *cmd_line_len = options_bytes;
        return (char *)cmdline_addr;
}

 到这里已经清楚了,参数是通过efi_table_attr(image, load_options)拿到的。

#define efi_table_attr(inst, attr)	(inst)->attr

 也就是从image_handle里面拿到load_options参数。这个参数的赋值就要从uefi代码中去找了。

这里要提醒一下的是,这个参数中的参数是utf-16的宽字符格式,所以要经过一些处理才能正确解析。

好了这次arm64上kernel 参数的获取就分析完了。 

以上kernel code依据v6.4-rc6