内核参数cmdline/command_line怎么传递到内核?

参考: 

linux kernel中的cmdline的详细介绍_kernel command line-CSDN博客

cmdline(二):uboot cmdline怎么传?&&cmdline kernel怎么用?-CSDN博客

linux kernel的启动参数是怎么拿到的-以arm64为例 - 半山随笔 - 博客园 (cnblogs.com)

Kernel怎么解析这个传过来的参数呢?

(1)、跳转linux kernel之前-准备cmdline
在跳转linux kernel之前(如uboot中),将cmdline数据放到了FDT中,然后将FDT的地址写入到了X0中。然后再跳转linux kernel.

请看kernel-4.14/Documentation/arm64/booting.txt

Before jumping into the kernel, the following conditions must be met:

- Quiesce all DMA capable devices so that memory does not get
  corrupted by bogus network packets or disk data.  This will save
  you many hours of debug.

- Primary CPU general-purpose register settings
  x0 = physical address of device tree blob (dtb) in system RAM.
  x1 = 0 (reserved for future use)
  x2 = 0 (reserved for future use)
  x3 = 0 (reserved for future use)

arm64即时采用uefi也依然会通过fdt传递内核参数:

/sys/firmware/fdt

/dts-v1/;
// magic:        0xd00dfeed
// totalsize:        0x29d (669)
// off_dt_struct:    0x38
// off_dt_strings:    0x1c8
// off_mem_rsvmap:    0x28
// version:        17
// last_comp_version:    17
// boot_cpuid_phys:    0x0
// size_dt_strings:    0xd5
// size_dt_struct:    0x190

/ {
    #size-cells = <0x00000002>;
    #address-cells = <0x00000002>;
    chosen {
        linux,uefi-mmap-desc-ver = <0x00000001>;
        linux,uefi-mmap-desc-size = <0x00000030>;
        linux,uefi-mmap-size = <0x00000f90>;
        linux,uefi-mmap-start = <0x00000000 0xf67b1018>;
        linux,uefi-system-table = <0x00000000 0xfbfe0018>;
        bootargs = "BOOT_IMAGE=/vmlinuz-5.15.0-g8223071764d2-dirty root=UUID=e50cdacd-1591-485d-8b81-f6c611309734 ro video=VGA-1:640x480-32@60me cgroup_disable=files apparmor=0 crashkernel=1024M,high smmu.bypassdev=0x1000:0x17 smmu.bypassdev=0x1000:0x15 console=tty0";
        linux,initrd-end = <0x00000000 0xef09a6e3>;
        linux,initrd-start = <0x00000000 0xedc1b000>;
    };
};

uefi将内核当成efi文件执行入口: 


efi_pe_entry:
{
    cmdline_ptr = efi_convert_cmdline(image, &cmdline_size); //获取cmdline 启动参数
 
    efi_info("Booting Linux Kernel...\n");

    si = setup_graphics();

    status = handle_kernel_image(&image_addr, &image_size,
                                     &reserve_addr,
                                     &reserve_size,
                                     image);                 //重新将内核镜像移动位置,地址保存在image_addr

    
    status = allocate_new_fdt_and_exit_boot(handle, &fdt_addr,
                                                initrd_addr, initrd_size,
                                                cmdline_ptr, fdt_addr, fdt_size);
            
            
            |->update_fdt((void *)fdt_addr, fdt_size,
                            (void *)*new_fdt_addr, MAX_FDT_SIZE, cmdline_ptr,
                            initrd_addr, initrd_size);
                            
                |->fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
                                     strlen(cmdline_ptr) + 1); 
                                     //最终更新fdt 供内核使用
    
    efi_enter_kernel(image_addr, fdt_addr, fdt_totalsize((void *)fdt_addr));  //重新进入内核arch/arm64/kernel/efi-entry.S 汇编
}

SYM_CODE_START(efi_enter_kernel)
        /*
         * efi_pe_entry() will have copied the kernel image if necessary and we
         * end up here with device tree address in x1 and the kernel entry
         * point stored in x0. Save those values in registers which are
         * callee preserved.
         */
        ldr     w2, =primary_entry_offset
        add     x19, x0, x2             // relocated Image entrypoint
        mov     x20, x1                 // DTB address
...
        /* Jump to kernel entry point */
        mov     x0, x20
        mov     x1, xzr
        mov     x2, xzr
        mov     x3, xzr
        br      x19       //跳入内核,此时x0寄存器保存fdt的地址
 
SYM_CODE_END(efi_enter_kernel)

(2)、kernel启动-解析cmdline
linux kernel从stext开始启动,整个流程大概就是读取X0(FDT地址)保存到X21中,又将X21保存到__fdt_pointer全局变量中
然后再将__fdt_pointer解析处cmdline数据到boot_command_line全局变量中。

	/*
	 * The following callee saved general purpose registers are used on the
	 * primary lowlevel boot path:
	 *
	 *  Register   Scope                      Purpose
	 *  x21        stext() .. start_kernel()  FDT pointer passed at boot in x0
	 *  x23        stext() .. start_kernel()  physical misalignment/KASLR offset
	 *  x28        __create_page_tables()     callee preserved temp register
	 *  x19/x20    __primary_switch()         callee preserved temp registers
	 */
ENTRY(stext)
	bl	preserve_boot_args
	bl	el2_setup			// Drop to EL1, w0=cpu_boot_mode
	adrp	x23, __PHYS_OFFSET
	and	x23, x23, MIN_KIMG_ALIGN - 1	// KASLR offset, defaults to 0
	bl	set_cpu_boot_mode_flag
	bl	__create_page_tables
	/*
	 * The following calls CPU setup code, see arch/arm64/mm/proc.S for
	 * details.
	 * On return, the CPU will be ready for the MMU to be turned on and
	 * the TCR will have been set.
	 */
	bl	__cpu_setup			// initialise processor
	b	__primary_switch
ENDPROC(stext)

这里调用了:

  • preserve_boot_args
  • __primary_switch

在preserve_boot_args将X0(fdt地址)暂时先保存到了X21中

preserve_boot_args:
	mov	x21, x0				// x21=FDT

	adr_l	x0, boot_args			// record the contents of
	stp	x21, x1, [x0]			// x0 .. x3 at kernel entry
	stp	x2, x3, [x0, #16]

	dmb	sy				// needed before dc ivac with
						// MMU off

	mov	x1, #0x20			// 4 x 8 bytes
	b	__inval_dcache_area		// tail call
ENDPROC(preserve_boot_args)



__primary_switch调用了__primary_switched
__primary_switch:
#ifdef CONFIG_RANDOMIZE_BASE
	mov	x19, x0				// preserve new SCTLR_EL1 value
	mrs	x20, sctlr_el1			// preserve old SCTLR_EL1 value
#endif

	bl	__enable_mmu
#ifdef CONFIG_RELOCATABLE
	bl	__relocate_kernel
#ifdef CONFIG_RANDOMIZE_BASE
	ldr	x8, =__primary_switched
	adrp	x0, __PHYS_OFFSET
	blr	x8

__primary_switched将X21(fdt地址)保存到了__fdt_pointer全局变量中

__primary_switched:
	adrp	x4, init_thread_union
	add	sp, x4, #THREAD_SIZE
	adr_l	x5, init_task
	msr	sp_el0, x5			// Save thread_info

	adr_l	x8, vectors			// load VBAR_EL1 with virtual
	msr	vbar_el1, x8			// vector table address
	isb

	stp	xzr, x30, [sp, #-16]!
	mov	x29, sp

	str_l	x21, __fdt_pointer, x5		// Save FDT pointer

	ldr_l	x4, kimage_vaddr		// Save the offset between
	sub	x4, x4, x0			// the kernel virtual and
	str_l	x4, kimage_voffset, x5		// physical mappings

	// Clear BSS
	adr_l	x0, __bss_start
	mov	x1, xzr
	adr_l	x2, __bss_stop
	sub	x2, x2, x0
	bl	__pi_memset
	dsb	ishst				// Make zero page visible to PTW

在setup_arch()的时候,调用setup_machine_fdt将fdt解析到了boot_command_line全局变量中

void __init setup_arch(char **cmdline_p)
{
	pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id());
......
	*cmdline_p = boot_command_line;
......
	setup_machine_fdt(__fdt_pointer);
......
}

setup_machine_fdt()—>early_init_dt_scan()—>early_init_dt_scan_nodes()

通过调用将fdt解析到了boot_command_line中,of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line)

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
	void *dt_virt = fixmap_remap_fdt(dt_phys);
	const char *name;

	if (!dt_virt || !early_init_dt_scan(dt_virt)) {
		pr_crit("\n"
			"Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"
			"The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
			"\nPlease check your bootloader.",
			&dt_phys, dt_virt);

		while (true)
			cpu_relax();
	}

	name = of_flat_dt_get_machine_name();
	if (!name)
		return;
	/* backward-compatibility for third-party applications */
	machine_desc_set(name);

	pr_info("Machine model: %s\n", name);
	dump_stack_set_arch_desc("%s (DT)", name);
}



 bool __init early_init_dt_scan(void *params)
 {
 	bool status;
 
 	status = early_init_dt_verify(params);
 	if (!status)
 		return false;
 
 	early_init_dt_scan_nodes();
 	return true;
 }

 void __init early_init_dt_scan_nodes(void)
 {
 	/* Retrieve various information from the /chosen node */
 	of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); //这个函数里获取
 
 	/* Initialize {size,address}-cells info */
 	of_scan_flat_dt(early_init_dt_scan_root, NULL);
 
 	/* Setup memory, calling early_init_dt_add_memory_arch */
 	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
 }

 early_init_dt_check_for_initrd:获取initrd的地址信息。


static void __init early_init_dt_check_for_initrd(unsigned long node)
{
	u64 start, end;
	int len;
	const __be32 *prop;

	pr_debug("Looking for initrd properties... ");

	prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);  //获取initrd加载在内存的地址
	if (!prop)
		return;
	start = of_read_number(prop, len/4);

	prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
	if (!prop)
		return;
	end = of_read_number(prop, len/4);

	__early_init_dt_declare_initrd(start, end);
	phys_initrd_start = start;
	phys_initrd_size = end - start;

	pr_debug("initrd_start=0x%llx  initrd_end=0x%llx\n",
		 (unsigned long long)start, (unsigned long long)end);
}
//保存initrd的物理地址到虚拟地址initrd_start
void __init arm64_memblock_init(void)
{
...
		initrd_start = __phys_to_virt(phys_initrd_start);
		initrd_end = initrd_start + phys_initrd_size;
...
}


使用init/initramfs.c
static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
{
....
    err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
....
}

在start_kernel()打印了cmdline. 

asmlinkage __visible void __init start_kernel(void)
{
…
    pr_notice(“Kernel command line: %s\n”, boot_command_line);
…
}
 

内核如何知道的initrd加载在内存的物理地址?

efi_pe_entry函数arm64内核efi格式入口函数efi_pe_entry_efi stub: using dtb from configuration table-CSDN博客

实际上在efi_load_initrd中并不会加载initrd,而是由grub加载?

因为在efi_pe_entry中返回的initrd_start,initrd_size都为0,但是status是EFI_SUCCESS:

status = efi_load_initrd(image, &initrd_addr, &initrd_size,
                                         ULONG_MAX, max_addr)

本以为在update_fdt中会更新,但是根据 前面的函数early_init_dt_check_for_initrd,解析fdt的方式在这里直接解析,能够获取到已经设置了。

efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
                                   efi_system_table_t *sys_table_arg)
{
。。。。
在函数一开始打印地址
        int node;
        node = fdt_subnode_offset(fdt_addr, 0, "chosen");
                const __be32 *prop,*prop1 = NULL;
        int len;
        u64 start,end;
        prop = fdt_getprop(fdt_addr,node,"linux,initrd-start",&len);
        if(prop) {
                start = of_read_number(prop, len/4);
                efi_info("0 fdt_getprop initrd_start %llx\n",start);
        }
        prop1 = fdt_getprop(fdt_addr,node,"linux,initrd-end",&len);
        if(prop1) {
                end = of_read_number(prop1, len/4);
                efi_info("0 fdt_getprop initrd_end %llx\n",end);
        }

...
然后打印efi_load_initrd的返回地址信息。
       if (!efi_noinitrd) {
                max_addr = efi_get_max_initrd_addr(image_addr);
                status = efi_load_initrd(image, &initrd_addr, &initrd_size,
                                         ULONG_MAX, max_addr);
                efi_info("efi_load_initrd at %llx size %llx \n",initrd_addr,initrd_size);
                if (status != EFI_SUCCESS)
                        efi_err("Failed to load initrd!\n");
        }

。。。。
}



结果显示,一开始就有地址,后面efi_load_initrd反而为空。说明已经加载了,不需要再加载了。 

Loading openEuler (5.15.0-g8223071764d2-dirty) 22.03 (LTS-SP1)
Loading initial ramdisk ...
EFI stub: Booting Linux Kernel... 
EFI stub: EFI_RNG_PROTOCOL unavailable
??I stub: Using DTB from configuration table ?
EFI stub: 0 fdt_getprop initrd_start edc1b000  //initrd-start
EFI stub: 0 fdt_getprop initrd_end ef09a6e3   //initrd-end
EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path status 14 l
oad_size 0 load_addr 0    //efi_load_initrd 返回的地址为0
EFI stub: Loaded initrd from command line option status 0 load_size 0 load_addr 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

古井无波 2024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值