找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

6051

积分

1

好友

761

主题
发表于 昨天 15:56 | 查看: 6| 回复: 0

U-Boot的环境变量,跑起来其实有三套逻辑:Bootloader运行时用的、Flash上持久化的、还有Linux用户态 fw_printenv / fw_setenv 自己内置的。调试时最容易踩的坑,是把U-Boot内置的默认环境和fw工具内置的默认环境当成一回事——它们根本是两套各自编译生成的 default_environment 数组。本文基于君正平台(uboot.2013)上实际踩过的坑,将工作机制、冗余备份、常见故障根因整理一遍,加深印象。

一 先搞清环境变量存在哪

1. 存储结构 env_t

环境变量在Flash上是一个二进制结构体,分普通版和冗余备份版(也即主/备份环境变量)两种。

// 非冗余模式
struct env_image_single {
    uint32_t    crc;        /* CRC32:对后续全部data区做校验 */
    char        data[];     /* 变量区:name=value\0name2=value2\0\0,双\0标记结束 */
};

// CONFIG_SYS_REDUNDAND_ENVIRONMENT 开启冗余备份
struct env_image_redundant {
    uint32_t    crc;
    unsigned char   flags;  /* 版本标记:NOR用布尔ACTIVE/OBSOLETE;NAND/UBI用递增序列号 */
    char        data[];
};

三个字段各管一件事:

crc:完整性校验。Flash读出来后重算crc和头部比对,对不上就判定环境损坏。

flags:冗余模式专用,区分主/备份哪个分区是当前有效副本。

data:真正的变量存储区,连续 name=value\0,末尾两个 \0 结束。

存在哪个偏移由 CONFIG_ENV_OFFSETCONFIG_ENV_OFFSET_REDUND 决定。我们平台在这里有个特殊布局:SPI-NAND上env分区嵌套在u-boot的MTD分区内部(0xc0000、0xe0000),不是一个独立MTD分区——这个布局在后面第三节埋了好几个坑。

2. U-Boot内部的两套数据源

U-Boot内部实际有两套数据源,优先级很明确。

第一套:Flash上的持久化环境(优先)。 上电后U-Boot从指定Flash偏移读 env_t 镜像,CRC校验通过就把变量解析到内存哈希表 env_htab,作为运行时环境。setenv 只改内存哈希表,不写Flash;真正落盘是 saveenv,把哈希表导出成完整 env_t 镜像,擦写Flash环境分区。

第二套:编译进 u-boot.bin 的 default_environment(故障兜底)。 编译U-Boot时,include/env_default.h 根据Board配置宏生成静态字符数组,编进 u-boot.bin 的 rodata 只读段:

#ifdef CONFIG_BOOTARGS
    "bootargs=" CONFIG_BOOTARGS "\0"
#endif

只有三种情况会用到它:Flash环境分区读取失败、CRC校验错误、Flash被擦成全0xFF。串口会打一行:

*** Warning - bad CRC, using default environment

这里有个很容易误解的点:CONFIG_BOOTARGS 只是故障兜底值。只要Flash上env的CRC正常,这个宏压根不生效——我们改了宏重新编译,也覆盖不了已经 saveenv 进Flash的变量。

3. 启动加载 env 的完整流程

把上面串起来就是:

  1. env_init():先把 gd->env_addr 指向内置默认环境,gd->env_valid=0
  2. env_relocate():调平台实现 env_relocate_spec()(env_sf.c / env_nand.c / env_sfc_nand.c),从Flash读env镜像到内存buffer,再走 env_import() 做CRC校验;
  3. CRC正常:解析 name=value 构建内存哈希表 env_htabgd->env_valid=1,用Flash里的环境;
  4. CRC失败:调 set_default_env(),导入 u-boot.bin 内置的 default_environment,gd->env_valid=0

之后就是老套路:setenv 改内存哈希,saveenv 导出镜像写Flash。

4. 冗余环境

CONFIG_SYS_REDUNDAND_ENVIRONMENT

开了冗余后,主、备份两份env镜像分别放两个Flash偏移,saveenv 不会覆盖当前在用的那个,总是写对端备份分区。选哪个生效靠 flags:

NOR Flash: flags=1(ACTIVE有效)/ flags=0(OBSOLETE废弃),布尔标记;

NAND / SPI-NAND / UBI: flags 用递增序列号,数值大的算新版本。

上电时两份都读,CRC+flags一起决定用哪份。设计目的是防 saveenv 写Flash中途掉电——两套副本不会同时坏。但别指望它万能:两份都被擦坏,照样回退内置 default_environment。

二 fw_printenv / fw_setenv:Linux用户态的另一个玩家

源码在 u-boot/tools/env,独立编译成可执行文件,跑在Linux用户空间,直接操作MTD设备——读写的和U-Boot是同一块Flash环境分区。头文件 fw_env.h,主逻辑 fw_env.c,命令行入口 fw_env_main.c。

1. 工具一次调用的完整流程

fw_setenv/fw_printenv命令行
->拿排他锁 /var/lock/fw_printenv.lock,防止多进程并发写Flash
->fw_env_open()
->parse_config():读fw_env.h编译宏或fw_env.config,拿到MTD设备、env偏移、大小、擦除块大小
->读Flash主、备份env镜像到内存buffer,做CRC校验
->按CRC+flags选当前有效镜像
->CRC全挂:加载fw工具自己内置的default_environment数组
->fw_printenv():解析内存镜像打印环境变量
->fw_setenv():在RAM镜像里做增删改(fw_env_write只动内存,不写Flash)
->fw_env_close():重算CRC,走MTD接口擦写Flash环境分区

2. 最容易踩坑:fw工具也有一份独立的 default_environment

这是整个体系里最容易绕晕的地方。fw_printenv / fw_setenv 是独立编译的ELF程序,编译 tools/env 的时候同样会 include board 头文件,生成一份属于工具自己的 default_environment 静态数组,编进 fw_printenv 二进制的 rodata 段。

对象 default_environment放哪 默认环境被触发时打什么
U-Boot Bootloader u-boot.bin *** Warning - bad CRC, using default environment
fw_printenv 工具 /usr/bin/fw_printenv Warning: The environment is empty ... loading default environment Success!

两份默认环境完全相互独立。Flash上env分区CRC校验失败时,fw_printenv 不会去翻 u-boot.bin,直接输出工具自己内置的那份;U-Boot上电才会读 u-boot.bin 里的那份。如果编译fw工具时的Board宏和编译U-Boot bin时不一致,两套默认环境内容就对不上。

这次在项目中我就栽过这个坑:迭代改过 CONFIG_BOOTARGS 里的 mem 内存参数,U-Boot更新了,文件系统里的 fw_printenv 还是旧版(没有重新编译过)。结果Flash env一坏,fw工具打印出旧的 mem=224M,U-Boot上电实际跑的是新配置。当时在U-Boot源码里怎么搜都搜不到 mem=224M——这字符串根本不在 u-boot.bin 里,结果分析了一圈居然是躺在 fw_printenv 工具二进制里的:

# 在fw_printenv二进制里找旧字符串,确认它在工具内部
strings /usr/bin/fw_printenv | grep "mem=224M"
# u-boot.bin里不会有这个旧字符串
strings u-boot.bin | grep "mem=224M"

记住一句话:Linux下 fw_printenv 打出来的“默认环境”,不代表板子上电后U-Boot真实的默认环境。 U-Boot真实默认环境,以U-Boot串口控制台输出为准。

3. fw工具与U-Boot的配置对齐是硬要求

fw_env.h 里的编译宏必须和U-Boot配置一一对齐:

对应
DEVICE1_NAME / DEVICE2_NAME MTD设备节点
DEVICE1_OFFSET / DEVICE2_OFFSET env主、备份Flash偏移
ENV1_SIZE / ENV2_SIZE 环境分区大小
DEVICE1_ESIZE Flash擦除块大小
HAVE_REDUND 冗余开关,对应CONFIG_SYS_REDUNDAND_ENVIRONMENT

这些对不齐,fw工具读写偏移就错位,直接把U-Boot环境分区写坏。

4. 变量校验机制

fw_env.c 里同样实现了和U-Boot本体同源的 env_flags 逻辑,支持变量只读、禁止删除、禁止覆盖这类访问权限控制,保证Bootloader和Linux用户态的约束一致。

5. 两种编译配置模式

编译期硬编码(我们项目用的):fw_env.h 里写死MTD、offset、size宏;

运行时配置文件:定义 CONFIG_FILE="/system/etc/fw_env.config",工具启动时读文本配置文件。

三 SPI-NAND平台踩坑复盘

先交代分区布局,前面768K存放uboot,紧接着128K存放主env,最后128K存放备份env:

mtd0: 1M(uboot),0 ~ 0xFFFFF
├─ uboot程序:0 ~ 0xc0000
├─ 主env分区:0xc0000 ~ 0xdFFFF
└─ 备份env分区:0xe0000 ~ 0xFFFFF

注意env分区是嵌套在uboot这个 mtd0 分区内部的,不是独立分区——后面几个坑都跟这个布局有关。

现象1:flashcp烧了uboot,环境变量全丢

flashcp u-boot.bin /dev/mtd0

flashcp 的做法是先把整个MTD分区整体擦除,再写镜像。我们的 u-boot.bin 只有455K,不会溢出到env区,但 flashcp 把整个1M的 mtd0 都擦成了0xFF,嵌套在末尾的主/备份env分区连坐,全被擦了,Flash上env镜像的CRC自然就挂了。

不重启时在Linux里跑 fw_printenv,打印的是fw工具内置的旧版默认环境(旧 mem=224M);一重启,U-Boot上电打出 *** Warning - bad CRC, using default environment,加载 u-boot.bin 里新版 default_environment。但此时Flash env分区还是全0xFF,只有进U-Boot执行一次 saveenv,把默认环境真正写进Flash,之后再跑 fw_printenv 才是对的。

对比一下:nandwrite 只写镜像文件的实际长度,不擦分区剩余块,也就不会碰后面嵌套的env分区。

现象2:fw_printenv和U-Boot上电的默认值对不上

根因不复杂: 文件系统里 fw_printenv 版本旧了,编译它的时候Board宏没跟着更新,工具内置的 default_environment 还是项目早期配置。解决也直接:编译 tools/env 时用和U-Boot完全一致的Board配置宏,重新出 fw_printenv,保证两份默认环境内容一致。

现象3:改了CONFIG_BOOTARGS,擦掉env分区,bootargs没变

先确认 CONFIG_BOOTARGS 确实被定义(检查条件编译)。然后记住它只是故障兜底:Flash env CRC正常时完全不生效。另外要分清两个 bootargs:U-Boot环境变量里的 bootargs,和dtb chosen节点里的 bootargs。U-Boot环境变量里存在 bootargs 时,会覆盖dtb chosen节点;没有的话,内核直接使用dtb的 bootargs。

四 关键调试排查手段

几个常用手段:

判断Flash env是否损坏:U-Boot串口打 *** Warning - bad CRC, using default environment;Linux下 fw_printenv 打 Warning: The environment is empty ... loading default environment Success!

区分两套 default_environment:Linux下 strings /usr/bin/fw_printenv | grep xxx 看fw工具内置的;U-Boot串口 printenv bootargsmd ${env_addr} dump内存看U-Boot内置的。这里涉及的源码解析思路,在 技术文档 板块里有大量同类案例可以参考。

直接看Flash原始二进制,确认env镜像状态

dd if=/dev/mtd0 of=env_raw.bin skip=$((0xc0000/4096)) bs=4096 count=32
hexdump env_raw.bin

全0xFF说明env分区被擦过。

烧uboot又不想动嵌套的env分区,就只擦uboot程序区域,别碰后面:

flash_erase /dev/mtd0 0 0xc0000
nandwrite /dev/mtd0 u-boot.bin

含嵌套env的MTD分区不建议用 flashcp。

五 踩坑心得

踩完坑之后,最值得做的就这几条:

  1. env分区尽量独立出来,别嵌套在uboot的MTD分区里。CONFIG_ENV_OFFSET 挪到uboot分区后面,MTDPARTS里加独立env分区,从根上避免烧uboot连坐擦掉环境变量。fw_env.h 里的偏移要同步改。
mtdparts=nand:1M(uboot),128K(env),3M(kernel),20M(root),-(appfs)
  1. fw_printenv / fw_setenv 必须跟着U-Boot同步编译发布。 tools/env 编译时用和u-boot完全相同的board配置,保证fw工具和 u-boot.bin 内置的两份默认环境一致。不然Flash env一坏,两边输出对不上,纯纯增加调试成本。这类构建链路上的同步问题,在 技术文档 的避坑指南分类里也经常被提起。

  2. 不要把 fw_printenv 输出的默认值当U-Boot运行时的基准。 故障状态下以U-Boot串口控制台 printenv 为准。

  3. 量产烧写时,Flash全擦除后让U-Boot执行一次 saveenv,把默认环境固化进env分区,避免每次上电都走 bad-CRC 恢复分支。

六 文末小结

把这几个关键点再钉一下:

正常运行时,U-Boot用Flash上的持久化env;只有Flash损坏(读失败/CRC错/全0xFF)才回退到 u-boot.bin 内置的 default_environment,CONFIG_BOOTARGS 只是兜底值。

fw_printenv/fw_setenvU-Boot 读写的是同一份Flash env分区,但fw工具另有一份独立编译内置的 default_environment。Flash env坏了它输出的是工具自带的默认,不等于U-Boot上电跑的那个默认

我们SPI-NAND这个平台,env嵌套在uboot的MTD分区里,flashcp 一擦就把env连坐擦掉,这个布局新项目开发时最好改成独立env分区。

改了 bootargs 这类默认配置,u-boot.bin 和 fw_printenv 要同步编译更新,否则两份默认环境不一致,调试时很容易误判。这类底层机制与用户态工具的配合问题,在 云栈社区 的嵌入式讨论中也不少见,有兴趣可以进去翻翻同行的踩坑记录。

两条故障信号,背下来直接对上号:

U-Boot串口:*** Warning - bad CRC, using default environment
->Bootloader用u-boot.bin内置默认

fw_printenv:Warning: The environment is empty ... loading default environment Success!
->Linux工具用fw_printenv二进制内置默认

就先记这么多,后面再踩到新坑再补。




上一篇:PacketSender开源网络发包工具实战:TCP私有协议调试与模拟请求
下一篇:嵌入式Linux USB热插拔升级进程假死:手动正常自动退出的环境变量排查
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-10 16:01 , Processed in 1.455723 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表