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

5915

积分

0

好友

738

主题
发表于 昨天 15:58 | 查看: 8| 回复: 0

我们产品需要临时增加一个后门紧急升级功能,包含 uboot 升级、内核 bootargs 环境变量设置,再加上常规 OTA 升级。整个流程由 USB 热插拔自动触发,U 盘插上后自动挂载并执行脚本完成升级。

昨天已经搞定了临时 OTA 升级程序,今天继续调试 USB 脚本触发升级的完整链路。这个功能看着简单,拆下来需求其实不少:要检查版本号做升级过滤,升级时要有特殊标志灯闪烁,升级成功后指示灯常亮提醒产线人员,还要防止反复升级,周末还得拷机压测。

工作环境

  • 硬件平台:Ingenic SoC(MIPS 架构)
  • 操作系统:嵌入式 Linux
  • Shell 环境:/bin/sh(dash)
  • 升级程序:usbUpgrade
  • 触发方式:USB 热插拔自动挂载执行脚本
  • 可用命令:基本 Busybox 工具集,scriptscreen 这些高级命令都被裁剪了

开发的大致工作流程如下:

USB插入 → 自动挂载 → 触发脚本A → 调用脚本B → 检测升级文件 → 升级uboot(并设置bootargs) → 执行ota升级程序做常规ota升级

调试过程中遇到了几个问题:

  1. uboot 的环境变量会概率恢复默认值;
  2. USB 热插拔识别太快,应用程序还没运行起来,版本号文件没生成,导致脚本不能继续执行;
  3. 指示灯亮了又被应用程序关掉,没达到预期效果;
  4. 手动运行脚本一切正常,但 USB 自动挂载启动脚本运行后,升级进程进入"假死"状态。

前三个问题排查完,手动运行脚本也确实顺利完成了预期功能。结果实际测试时,出现了让人抓狂的现象 4。以前也遇到过类似的情况,可能进入进程所在路径再运行就能解决,但这次没这么简单:

在串口终端手动敲 ./advancedsettings.sh&,升级一切正常;但走热插拔脚本自动调用 advancedsettings.sh& 后,usbUpgrade 进程刚起来就退出,一个升级动作都不执行。

排查:从"程序没问题"到"环境不对"

第一步,先确认程序本身。串口手动执行,升级成功。结论:advancedsettings.shusbUpgrade 本身没问题。

第二步,给脚本 B 加调试。在 ota_upgrade() 里加判断,2 秒后检查进程还在不在:

ota_upgrade()
{
    cd "$USBMOUNTPOUNT"
    echo "Starting usbUpgrade..."
    ./usbUpgrade > /tmp/usbUpgrade.log 2>&1 &
    UPGRADE_PID=$!

    sleep 2
    if kill -0 $UPGRADE_PID 2>/dev/null; then
        echo "usbUpgrade is running"
    else
        echo "usbUpgrade exited immediately"
    fi
}

结果:

Starting usbUpgrade...
usbUpgrade exited immediately

再看进程和串口打印,是空的,连个报错都没留下——进程起来了,但什么都没干就退了。

=== Process check after 2s ===
 1242 root      3380 S    grep -E (usbUpgrade|PID1|PID2)
WARNING: nohup process 1207 exited
=== Last 30 lines of  ===
=== End of log ===

第三步,手动运行与自动运行对比。最诡异的是,串口手动跑同一个脚本 advancedsettings.sh 是成功的,只有热插拔自动触发时才出问题:

执行方式 命令 结果
串口手动 /tmp/mnt/usb/advancedsettings.sh 成功
热插拔自动 USB 插入自动触发 失败

程序和脚本都没问题,那剩下的变量只有一样——执行环境。

环境对比差异性

把两种方式下的环境变量都打出来看了看。

手动执行时(串口终端登录)继承的是一套完整的 shell 环境:

$ env
TERM=xterm
HOME=/
USER=root
SHELL=/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/app/bin
LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib:/app/lib
PS1='[\u@\h:\w]# '
HUSHLOGIN=FALSE
# ... 后面还有一堆

而热插拔脚本里的环境,就寒酸多了:

$ env
HOME=/
USER=root
SHELL=/bin/sh
PATH=/bin:/sbin:/usr/bin
# TERM、PS1、LD_LIBRARY_PATH……全都没有

差异一目了然。

折腾过的那些方案

问题定位到环境后,我先试了各种"把进程搞成后台守护"的常规操作,结果全部阵亡:

方案 命令 结果
nohup + 重定向 nohup ./usbUpgrade >> /dev/console & 启动即退出,日志空
setsid setsid ./usbUpgrade  >> /dev/console & 同样失败(部分嵌入式系统里 setsid 不可用或行为不一样)
disown ./usbUpgrade >> /dev/console & + disown disown: not found——dash 没有这个 bash 内置命令
双重 fork ( ( ./usbUpgrade  >> /dev/console& 进程依然立即退出
exec exec ./usbUpgrade 脚本进程被替换,程序照样退
script script -q -c "./usbUpgrade" /dev/null & script: not found——嵌入式系统为了省空间没带
screen screen -dmS upgrade ./usbUpgrade screen: not found

一句话总结:把程序往"后台"折腾没用,它需要的不是后台,是一套像样的环境。

最终方案:把环境补齐再启动

思路转变之后,方案反而很简单:启动前把交互式终端的环境变量手动补全,顺便处理掉 SIGHUP 的问题。

ota_upgrade()
{
    echo "===== Starting OTA upgrade at $(date) ====="

    # 升级各分区
    echo "Upgrading mtd0 (boot)..."
    flashcp ${USBMOUNTPOINT}/a1n_boot* /dev/mtd0
    if [ $? -ne 0 ]; then
        echo "ERROR: flashcp failed"
        return 1
    fi

    # 设置内核启动参数
    fw_setenv bootargs ......

    # 准备升级文件
    cp ${USBMOUNTPOINT}/*app*.bin ${USBMOUNTPOINT}/tmp_upgrade.bin
    chmod 777 ${USBMOUNTPOINT}/tmp_upgrade.bin

    # 进入USB目录
    cd "$USBMOUNTPOINT"
    echo "Current directory: $(pwd), start to ota upgrade......"

    # ===== 关键步骤:设置完整的交互式环境 =====
    export TERM=xterm
    export HOME=/
    export USER=root
    export SHELL=/bin/sh
    export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/app/bin
    export LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib:/app/lib
    export PS1='[\u@\h:\w]# '

    # 忽略SIGHUP信号,防止父进程退出时被杀死
    trap '' HUP

    # 启动升级程序(重定向到控制台以便查看输出)
    ./usbUpgrade > /dev/console &

    echo "===== ota upgrade completed ====="
}

三个关键点:

  1. 环境变量一个不能少。 程序启动时可能会查这些,具体哪个是"压死骆驼的最后一根稻草"没挨个验证,反正全补齐就对了:
环境变量 作用 为什么需要
TERM=xterm 终端类型 程序检测终端类型,决定是否用交互特性
HOME=/ 用户家目录 程序可能要读配置文件
USER=root 用户名 程序可能要校验用户身份
PATH 命令搜索路径 程序可能要调用外部命令
LD_LIBRARY_PATH 动态库搜索路径 程序可能依赖特定位置的库
PS1 Shell 提示符 程序可能检测 shell 交互模式
  1. trap '' HUP 比 nohup 靠谱: 忽略 SIGHUP,防止父进程退出时把子进程一起带走,效果上相当于 nohup,但不用依赖那个"被裁掉"的命令。

  2. 输出打印保留到串口: ./usbUpgrade > /dev/console & 把输出打到系统控制台,方便看程序到底在干嘛;直接 > /dev/null 的话,下次还得抓瞎。

根因分析:手动和自动,用的根本不是同一套环境

手动执行成功,是因为串口终端登录时继承了完整的 shell 环境——TERM、PATH、LD_LIBRARY_PATH 齐全,还挂着 tty:

串口终端登录 → 继承完整shell环境 → TERM/PATH/LD_LIBRARY_PATH/tty 都在
             → usbUpgrade 各项检测通过 → 正常运行

而热插拔脚本是在一个"纯净"环境里起来的——PATH 不完整、LD_LIBRARY_PATH 缺失、没 TERM、没 tty。usbUpgrade 要么在检测终端,要么在加载动态库,要么在找外部命令的时候,直接判断"这里不对劲",然后默默退场:

热插拔脚本 → 简单shell环境 → TERM/PATH/LD_LIBRARY_PATH/tty 全缺
          → usbUpgrade 某项检测失败 → 立即退出

从行为上看,usbUpgrade 大概率是踩了下面某颗雷:

  1. 检测有没有 tty,没有就退出;
  2. 检查环境变量,缺 TERM 之类的就拒绝运行;
  3. 需要动态库路径,LD_LIBRARY_PATH 不对导致库加载失败;
  4. 调用外部命令,PATH 不完整导致依赖命令找不到。

反正环境补齐之后,问题没了。

经验总结

调试思路:先确认程序本身没问题(手动跑一遍),再给脚本加日志、打印关键变量,然后用 env 对比手动/自动的环境差异,最后逐步排除。嵌入式系统工具集有限,scriptscreen 都没有,别在这上面浪费时间。

这套坑踩下来,几个要点:

嵌入式系统特点 影响 应对
空间有限 缺高级命令 用基础 shell 命令
环境简陋 环境变量缺失 启动前手动补齐完整环境
/bin/sh 是 dash 不支持 bash 特性 避免 disown 等 bash 内置命令
热插拔后台运行 父进程退出牵连子进程 trap '' HUP + 重定向

最后给一个通用模板,遇到类似的"手动能行、自动不行",直接套:

# 1. 设置完整环境变量
setup_environment() {
    export TERM=xterm
    export HOME=/
    export USER=root
    export SHELL=/bin/sh
    export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/app/bin
    export LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib:/app/lib
    export PS1='[\u@\h:\w]# '
}
# 2. 忽略SIGHUP信号
trap '' HUP
# 3. 切换到工作目录
cd /path/to/workdir
# 4. 启动程序
./program > /dev/console 2>&1 &
# 5. 验证启动
sleep 2
if pgrep -f "program" > /dev/null 2>&1; then
    echo "Program started successfully"
else
    echo "Program failed to start"
fi

文末小结

这次的问题根本不是程序 bug,也不是脚本逻辑 bug,而是手动环境和自动环境不一致。usbUpgrade 依赖特定的环境变量和 tty,热插拔脚本给不了,它就直接"摆烂"退出。

调试过程中 AI 贡献了不少思路,但生成的代码通常很臃肿,需要自己做裁剪。一方面要搞清楚脚本中每一行的用途,另一方面也不至于让代码变成超级屎山。

如果你也遇到过嵌入式 Linux 下"手动正常、自动异常"的诡异问题,不妨先在 云栈社区 上搜索一下类似案例,环境变量对比这个方法,在排查热插拔、定时任务、开机自启等场景下的程序异常退出问题时,往往能一击命中。




上一篇:U-Boot环境变量机制详解:fw_printenv内置默认环境与Bootloader为何不一致
下一篇:嵌入式C语言少写一个星号:memset卡死、read报Bad address排查
您需要登录后才可以回帖 登录 | 立即注册

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

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

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

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