当前位置:首页>Linux>Linux下修改了编译传递进去的bootargs却不生效,原来是U-Boot的env在搞鬼

Linux下修改了编译传递进去的bootargs却不生效,原来是U-Boot的env在搞鬼

  • 2026-09-03 14:03:58
Linux下修改了编译传递进去的bootargs却不生效,原来是U-Boot的env在搞鬼
Hello,大家好,我是程序媛MM。

本文约2000字,今天调试AX615长电型号的系统用内核自带的initramfs启动将正式根挂载到内存, 为代替sdk默认的直接将rootfs分区挂载物理盘为squashfs(这个方式会带来ota升级rootfs分区时引起squashfs错误导致升级失败系统成转问题)。调试中的一些问题还是比较经典的,本文先总结关于bootargs编译到uboot失效问题。

关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具。


01PART

— — —

问题的起因

Bug背后

本来以为就是改下project.mak中一个变量赋值的问题——把KERNEL_BOOTARGS拼好,编译烧录,收工。

project.mak
KERNEL_BOOTARGS := "$(OS_MEM) cmmpool=$(CMM_POOL_PARAM) console=ttyS0,115200n8 \  loglevel=8 earlycon=uart8250,mmio32,0x4880000 \  root=/dev/ram0 rw rootfstype=$(ROOTFS_TYPE) init=/linuxrc \  mtdparts=spi3.0:$(FLASH_PARTITIONS) \  initrd=$(ROOTFS_START_ADDR),$(ROOTFS_PARTITION_SIZE) $(CMMRC_PARAM)"

结果内核一起来,initrd= 整段没了,结果自然是内核不能启动挂载文件系统报错。

cmdline
Kernel command line: mem=68M ... rootfstype=squashfs  init=/linuxrc mtdparts=... boot_reason=panic

02PART

— — —

先怀疑变量没赋值成功

开始排查

第一反应:KERNEL_BOOTARGS变量是不是没赋值对?

加打印:

project.mak
$(info # KERNEL_BOOTARGS: $(KERNEL_BOOTARGS))

编译输出:

# KERNEL_BOOTARGS: "mem=68M ... mtdparts=spi3.0:...8M(rootfs)... initrd=0x43c00000,8M "

变量好好的,initrd=0x43c00000,8M 明明白白在里面。

那就奇了怪了。

03PART

不对劲的地方

发现问题

把编译打印和实际内核命令行放一起比,看出问题了:

差异项
编译打印的 KERNEL_BOOTARGS
实际内核命令行
mtdparts 里 rootfs 大小
8M(rootfs)8192K(rootfs)
末尾参数
无
boot_reason=panic

格式不一样,还多了参数。

如果实际命令行是编译时写进 CONFIG_CMDLINE 的,那应该和编译打印一字不差才对。但 8M 变成了 8192K,还凭空冒出来个 boot_reason=panic——这说明实际跑的命令行根本不是编译进去的那份,是U-Boot运行时传进来的。

U-Boot的bootargs 环境变量把内核默认命令行盖掉了。

04PART

— — —

翻U-Boot源码

找源头

顺着这个思路去扒代码,在axera_boot.c的 flash_raw_read 函数里找到了 bootargs的处理:

axera_boot.c
int flash_raw_read(const char *part_name, void *dest){    ...    case STORAGE_TYPE_NOR:       
#if !defined(CONFIG_MTD_SPI_NAND) && defined(CONFIG_SPI_FLASH) bootargs =        env_get("bootargs");       printf("========= bootargs:%s\n", bootargs); if(NULL == bootargs) { bootargs = BOOTARGS_SPINOR; printf("========= BOOTARGS_SPINOR:%s\n", BOOTARGS_SPINOR);#ifdef SUPPPORT_DDR_AUTO_ADJUST if (iram_misc_info->chip_type.chiptype == AX615DV201 || iram_misc_info->chip_type.chiptype == AX615QV201){ bootargs = BOOTARGS_SPINOR_L2; }#endif mtdparts = MTDPARTS_SPINOR; env_set("bootargs", bootargs); env_set("mtdparts", mtdparts); env_save(); } else { mtdparts = strstr(bootargs , "mtdparts"); if (NULL != mtdparts) { mtdparts = strdup(mtdparts); strtok(mtdparts, " "); env_set("mtdparts", mtdparts); free(mtdparts); } else { mtdparts = MTDPARTS_SPINOR; env_set("mtdparts", mtdparts); } } mtdids = env_get("mtdids"); env_set("mtdids", MTDIDS_SPINOR); if (NULL == mtdids) { env_set("mtdids", MTDIDS_SPINOR); } read_len = flash_read_from_nor(part_name, dest);#endif break;    }    return read_len;}

逻辑不复杂:

env里没有 bootargs → 用编译时的 BOOTARGS_SPINOR env 并保存

env 里已经有 bootargs → 直接用现有值,只从中提取 mtdparts,bootargs 本身一个字不改

05PART

— — —

问题出在这里

找到根因

之前某次启动的时候,env 里还没有 bootargs,代码用当时的默认值(有更新后的 root=,但还没有 initrd=)写进了 env,saveenv 持久化到 flash 了。

后来我在源码里加了 initrd=,重新编译烧录——但 env 分区存的还是旧 bootargs。每次启动都走 else 分支,直接用旧值,新编译的 BOOTARGS_SPINOR 根本没机会执行。

我改的是源码里的默认值,实际跑的是 env 里缓存的旧值。这俩不是一个东西。

06PART

— — —

修改方法

清除env中的bootargs数据

进 U-Boot 命令行,清缓存:

env delete bootargssaveenvreset

重启后走 if (NULL == bootargs) 分支,用最新编译的默认值重新写 env。

验证:

Kernel command line: mem=68M ... mtdparts=spi3.0:...8M(rootfs)... initrd=0x43c00000,8M  boot_reason=panic

initrd=0x43c00000,8M 回来了。

这个设计其实有道理:

虽然坑人,但这个逻辑不是乱写的:

出厂第一次启动:env是空的,用编译默认值初始化,保证能起来

后续启动:用户可能在U-Boot命令行里 setenv bootargs 自定义过参数,代码不应该用默认值覆盖用户的修改

问题是:开发者改源码和用户改env,走的是同一条路径。代码区分不了"这个env 是用户手动改的"还是"这个env是上次自动缓存的"。

产品开发阶段这个机制反而害人——我以为改了源码生效了,实际跑的还是几个版本前的缓存,。

07PART

踩过这个坑之后的几条经验

牢记于心

1. 改了 bootargs 相关源码,先清 env 再验证:env delete bootargs; saveenv; reset,别直接看启动日志就下结论

2. 编译打印和实际命令行对一下:如果不一致(格式、参数数量不同),说明有运行时覆盖,优先查 U-Boot env 和设备树 /chosen 节点

3. CONFIG_CMDLINE 不是唯一来源:U-Boot 的 bootargs 环境变量、设备树 bootargs 属性、内核默认命令行三者有优先级,通常 U-Boot 传的最高

4. env 分区是持久化的:重新烧录 kernel/uboot 不会自动清 env,除非擦除了 env 分区或用了 env default -a

08PART

— — —

文末小结

扩展知识

initrd=0x43c00000,8M 里的 8M 后缀,大部分架构用 memparse 解析是支持的。如果遇到大小识别异常,改成字节数 0x800000 更保险。

另外确认一下 0x43c00000 这个地址没和其他内存区域冲突——我们的 mem=68M + cmmpool 从 0x44400000 开始,0x43c00000~0x44400000 这段是空闲的,没问题。但如果后续调整内存布局,这个地址要跟着改。

💡 温馨提示
 这个坑说大不大,但排查路线挺典型的:从"变量没展开"到"运行时覆盖"再到"env 缓存机制",每一步都得靠对比和证据。如果你也同样在AX615上调启动的,少走点弯路。

“谢谢你看到这里”

这里是女程序员的笔记本

 15年+嵌入式软件工程师兼二胎宝妈

分享读书心得、工作经验,自我成长和生活方式。

希望我的文字能对你有所帮助

最新文章

随机文章