本文约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
不对劲的地方
发现问题
把编译打印和实际内核命令行放一起比,看出问题了:
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=panicinitrd=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年+嵌入式软件工程师兼二胎宝妈
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助