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

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,这个就是拿参数用的。看看他是怎么拿到的。

1
2
3
4
5
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中有

1
rc = early_init_dt_scan_chosen(boot_command_line);

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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出现之前,我们经常会看到这样的的输出:

1
2
3
4
5
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
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干了啥:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

1
2
3
4
5
6
7
8
9
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
 * 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)拿到的。

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

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

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

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

以上kernel code依据v6.4-rc6

  

 

posted on   半山随笔  阅读(976)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示