linux kernel中的cmdline的详细介绍

本文详细介绍了在Linux内核中添加cmdline的四种方法,包括在设备树源文件(DTS)、BoardConfig配置文件、U-Boot源代码以及Android Makefile中添加。此外,还深入探讨了U-Boot如何将cmdline统一存放于扁平设备树(FDT)中,以及Linux内核如何从FDT中解析cmdline,并在启动过程中使用这一信息。


★★★ 友情链接 : 个人博客导读首页—点击此处 ★★★

1、向linux kernel添加cmdline的四种方式

在linux启动时候,串口log中会打印cmdline

[    0.000000] c0 0 (swapper) Kernel command line: earlycon androidboot.selinux=permissive uart_dma keep_dbgclk_on clk_ignore_unused initrd=0xd0000000,38711808 rw crash_page=0x8f040000 initrd=/recoveryrc boot_reason=0x2000 ota_status=0x1001

在linux启动完成后,通过 cat /proc/cmdline也是可以看到cmdline. 那么cmdline是如何添加的呢?

(1)、 在dts中的bootargs中添加
/ {
    model = "yyyyyyy";
    compatible = "yyyyyyy", "xxxxxxxx";

    chosen {
        /*
         * initrd parameters not set in dts file since the ramdisk.img size
         * need to check in uboot, and the initrd load address and size will
         * set in uboot stage.
         */
        bootargs = "earlycon androidboot.selinux=permissive uart_dma keep_dbgclk_on clk_ignore_unused";
        stdout-path = "serial0:115200";
    };
......
}
(2)、在BoardConfig中添加

vim device/xxx/xxx_evb/BoardConfigCommon.mk

BOARD_KERNEL_CMDLINE += androidboot.selinux=enforcing androidboot.hardware=xxxx_phone androidboot.dtbo_idx=0
(3)、在uboot中添加

vim u-boot/common/cmd_bootm.c

append_bootargs("recovery=1");

sprintf(dm_buf,"init=/init skip_initramfs rootwait root=/dev/dm-0 dm=\"system none ro,0 1 android-verity /dev/mmcblk0p%d\"",ret);
append_bootargs((const char *)dm_buf);
(4)、在android的Makefile中添加

vim build/core/Makefile

INTERNAL_KERNEL_CMDLINE := $(strip $(BOARD_KERNEL_CMDLINE) buildvariant=$(TARGET_BUILD_VARIANT) $(VERITY_KEYID))
ifdef INTERNAL_KERNEL_CMDLINE
INTERNAL_BOOTIMAGE_ARGS += --cmdline "$(INTERNAL_KERNEL_CMDLINE)"
endif

2、在uboot中,将cmdline统一放置在FDT中

以后再写,哈哈哈哈

3、在kernel中,从FDT中解析处cmdline并使用

(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)
(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);
 }

在start_kernel()打印了cmdline.
asmlinkage __visible void __init start_kernel(void)
{

pr_notice(“Kernel command line: %s\n”, boot_command_line);

}

<think>嗯,用户想了解如何在Linux中设置boot_command_line的值,可能通过修改/proc/cmdline或者引导参数。我之前已经回答过关于如何将bootargs写入/proc/cmdline的问题,但用户现在更深入,涉及到boot_command_line的赋值,这可能涉及内核层面的修改。首先,我需要确认boot_command_line是什么。在内核启动过程中,boot_command_line是存储启动参数的全局变量,通常在setup_arch函数中初始化,来自引导程序传递的参数。用户可能想直接修改这个变量,或者了解如何影响它的值。用户提到通过/proc/cmdline或其他方法配置。之前的回答已经指出/proc/cmdline是只读的,无法直接修改,必须通过引导加载程序。但用户现在的问题可能更偏向于内核开发,比如在内核源码中直接设置boot_command_line,或者在运行时动态修改,这可能涉及到内核模块或补丁。接下来,我需要考虑正确的途径。修改boot_command_line通常在内核启动前,通过引导参数设置。如果用户想在内核运行时修改,可能需要重新构建内核或使用内核模块,但这可能有风险,也不推荐。另外,用户的问题可能涉及到内核开发或调试,比如在编译内核时预设参数,或者通过设备树传递参数。需要分情况讨论:编译时设置、引导加载程序设置、运行时修改的可能性。 还要注意,boot_command_line在内核初始化后就不再改变,因此运行时修改可能无效,除非在内核启动的早期阶段。需要查阅内核源码,确认相关流程,比如在setup_arch中如何获取参数。 可能需要引导用户通过修改内核启动参数的正确方法,比如U-Boot或GRUB,而不是直接操作/proc/cmdline。同时,如果用户确实需要在内核代码中预设参数,可以修改内核源码中的default_command_line,并重新编译。需要区分用户的使用场景:是嵌入式开发需要固化参数,还是普通用户想临时修改启动参数。根据不同的场景提供不同的方法,并强调直接修改boot_command_line的风险和复杂性。最后,生成相关问题,引导用户进一步了解启动参数的相关知识,比如内核参数传递机制、设备树的作用等。</think>### 如何在 Linux 中设置 `boot_command_line` 的值? #### 核心结论 **`boot_command_line` 是内核启动时初始化的全局变量**,存储内核启动参数(如 U-Boot/GRUB 传递的 `bootargs`)。其值在系统启动时由引导加载程序决定,**无法在运行时直接修改**[^1][^3]。若需自定义,需通过以下方法: 1. **修改引导加载程序参数**(如 U-Boot 的 `bootargs` 或 GRUB 的 `GRUB_CMDLINE_LINUX`) 2. **在内核源码中预设默认值**(需重新编译内核) 3. **通过设备树(Device Tree)传递参数**(嵌入式系统适用) --- #### 一、`boot_command_line` 的初始化机制 `boot_command_line` 在内核启动流程中由 `setup_arch()` 函数初始化,其值来源于引导加载程序传递的原始参数。例如,在 ARM 架构中: ```c // arch/arm/kernel/setup.c void __init setup_arch(char **cmdline_p) { strlcpy(boot_command_line, CONFIG_CMDLINE, COMMAND_LINE_SIZE); // ... } ``` 若内核配置项 `CONFIG_CMDLINE` 定义了默认参数,它将覆盖引导加载程序的参数[^1]。 --- #### 二、设置 `boot_command_line` 的方法 ##### 方法 1:通过引导加载程序设置(推荐) - **U-Boot** ```bash setenv bootargs 'console=ttyS0 root=/dev/mmcblk0p2 rw' # 直接定义参数 saveenv bootelf 0x82000000 ``` - **GRUB** 修改 `/etc/default/grub` 中的 `GRUB_CMDLINE_LINUX`,运行 `update-grub` 后重启生效[^1][^3]。 ##### 方法 2:在内核源码中预设默认值 1. **修改内核配置** 在 `.config` 文件中设置 `CONFIG_CMDLINE`: ```bash CONFIG_CMDLINE="console=ttyS0 root=/dev/mmcblk0p2 rw" ``` 2. **重新编译内核** ```bash make oldconfig && make -j$(nproc) ``` 此方法会将参数硬编码到内核中[^1]。 ##### 方法 3:通过设备树传递参数 在设备树文件中添加 `chosen` 节点: ```dts chosen { bootargs = "console=ttyS0 root=/dev/mmcblk0p2 rw"; }; ``` 内核启动时会优先读取此参数[^2]。 --- #### 三、验证 `boot_command_line` 的值 1. **查看 `/proc/cmdline`** ```bash cat /proc/cmdline # 输出与 boot_command_line 一致 ``` 2. **内核日志检查** ```bash dmesg | grep "Command line" # 显示实际加载的参数 ``` --- #### 四、运行时动态修改的限制 - **内核符号重定向(仅调试)** 可通过动态修改符号表(需关闭 `CONFIG_STRICT_KERNEL_RWX`)强行覆盖 `boot_command_line` 的值: ```c // 内核模块示例 #include <linux/init.h> #include <linux/module.h> extern char *boot_command_line; static int __init demo_init(void) { strncpy(boot_command_line, "new_args", COMMAND_LINE_SIZE); return 0; } module_init(demo_init); ``` **注意**:此方法可能导致系统不稳定,且修改后参数不会影响已初始化的子系统[^4]。 --- ### 相关问题 1. 如何通过设备树动态覆盖 U-Boot 传递的 `bootargs`? 2. 内核编译时 `CONFIG_CMDLINE` 与设备树参数的优先级如何?[^1][^2] 3. 为什么修改 `/proc/cmdline` 无法影响已运行的内核?[^3] 4. 如何调试因 `boot_command_line` 参数错误导致的设备初始化失败? 5. 内核启动后能否通过 `/sys` 接口修改 `boot_command_line` 的派生参数?[^4] [^1]: 内核启动参数初始化流程与 `CONFIG_CMDLINE` 的作用 [^2]: 设备树对启动参数的传递机制 [^3]: `/proc/cmdline` 的只读属性与内核运行时数据保护 [^4]: 内核符号重定向与运行时内存保护机制
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Arm精选

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

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

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

打赏作者

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

抵扣说明:

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

余额充值