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
标签:kernel,efi,为例,boot,cmdline,fdt,line,arm64 From: https://www.cnblogs.com/banshanjushi/p/17770875.html