ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

工控单板存储与系统升级实战:分区规划、OTA与OverlayFS恢复出厂

工控单板存储与系统升级实战:分区规划、OTA与OverlayFS恢复出厂 1. 项目背景为什么工控单板的存储和恢复方案值得单独写一篇做工控单板开发这几年我最大的感受是硬件选型只是开始真正的坑全在系统软件层面。一块RK3399或者i.MX6ULL的单板跑起来容易但要做到“现场断电不丢数据、远程升级不掉链子、恢复出厂一键搞定”靠的是存储配置、升级策略和OverlayFS这套组合拳。这篇指南就是围绕这三块内容把我实际调试和交付中的经验整理出来。这篇文章适合谁主要是刚接触工控单板的嵌入式Linux开发者、做设备运维的工程师以及准备把Linux引入到量产设备里的团队。你们会遇到的问题无非就是eMMC分区怎么规划才能兼顾系统稳定和数据安全设备发到客户现场后怎么安全升级固件用户乱改了系统文件怎么一键恢复出厂这些我在本文里都会给出方案和踩坑记录。先说结论在工控场景下系统分区用只读挂载 OverlayFS做可写层配合独立的用户数据分区是目前兼顾稳定性、可维护性和恢复成本的最优解之一。别急着反驳下面我把为什么这么选、具体怎么落地一步步拆开讲。2. 存储配置分区规划和布局是整台设备的命根子2.1 先搞懂工控单板上有哪些存储介质工控单板的存储不像PC那样只有一个C盘D盘常见的有这么几类eMMC最主流的系统盘焊接在板上容量从4GB到64GB不等。读写速度和寿命介于NAND和SSD之间胜在封装成熟、坏块管理由主控做了我们写软件的人基本不用操心。SPI NOR Flash容量很小常见8MB、16MB、32MB用来放Bootloader或者备用的最小系统。特点是读快、写慢、寿命长坏了也好换。NAND Flash部分老平台或者追求成本时会用需要软件自己做坏块管理和擦写均衡调试起来比eMMC麻烦不少。SD卡/U盘很多工控板留了SD卡槽既可以做量产烧录介质也可以做系统备份。SD卡在工业现场的可靠性不稳定一般不建议把系统长期跑在上面。我经手的绝大多数项目最终都是eMMC作为主存储SPI NOR放Bootloader的组合。这个组合的好处是NOR Flash上放一个最简ubooteMMC上放完整的系统万一eMMC分区表坏了NOR里的uboot还能引导进入恢复模式。2.2 分区规划不能照抄PC的“一个根分区打天下”你如果直接拿Ubuntu Server的镜像烧到eMMC里设备也能跑但后期维护会非常难受。工控设备的分区一定要按“系统、数据、恢复”三个维度来规划。我常用的一个分区表长这样分区挂载点大小文件系统作用mmcblk0p1/boot/64MBvfat存放kernel、dtbmmcblk0p2/1~2GBext4根文件系统只读挂载mmcblk0p3/data剩余空间ext4用户数据、日志、配置mmcblk0p4(不挂载)512MBext4升级备份分区或放恢复镜像为什么这么分/boot单独分出来是为了做系统升级时最小心地去操作。kernel和dtb的刷写风险最高单独放在一个vfat分区里就算rootfs写坏了恢复起来也容易。根分区只有1~2GB是因为只读挂载的根分区里只放系统软件不该让业务数据流进去。容量给太大反而是浪费也增加了刷写和备份的时间。/data独立分区是为了在恢复出厂时“保留用户数据”或者“清空用户数据”二选一。很多场景下恢复系统不能把采集了几个月的现场数据一起抹掉。留一个不挂载的备份分区是给OTA升级准备的回退空间。后面讲升级时你会看到它的价值。2.3 实际挂载配置fstab怎么写才能不翻车很多工控板开机启动慢排查到最后发现是fstab有问题某个分区挂载不上系统一直卡在等待超时。我强烈建议根文件系统不要依赖fstab去挂载而是通过内核cmdline指定root参数由initramfs或者内核直接挂载。fstab里只写/data等业务分区的挂载。一个典型的安全型fstab长这样# /etc/fstab # 数据分区defaults即可加上nofail /dev/mmcblk0p3 /data ext4 defaults,nofail,x-systemd.device-timeout5 0 2关键点有两个nofail这个选项必须加。它告诉系统“这个分区挂了也没关系别影响开机”。万一现场设备的eMMC出现坏块导致分区识别不了至少系统还能起来你可以远程进去查问题。x-systemd.device-timeout5这条是配合systemd用的限制等待设备出现的最大时间。不加的话系统可能因为一个分区起不来就卡住几分钟。2.4 分区大小的计算方法和一次踩坑经历分区大小不是拍脑袋定的。以RK3399平台的Linux系统为例根分区最小可以压到500MB左右但不建议这么极限。算一下系统内核源码编译出的用户空间软件包加上busybox工具集、OpenSSL库、运行时依赖基线一般在300~600MB。再预留30%余量、加上偶尔现场要放一些诊断工具我给根分区1.5GB比较稳。特别提醒一句给根分区留空间时一定要算上OverlayFS占的坑。如果你用OverlayFS做可写层后面会讲所有对系统目录的写操作都会落在upperdir上而upperdir是放在根分区里的。也就是说根分区既要装系统又要留下空间给运行时的临时写入。如果把根分区卡得太小用户装几个软件包就可能把根分区的upperdir撑爆。我有一次就是在量产阶段为了“省eMMC容量”把根分区从2GB压到1GB。结果设备在客户现场跑了一阵子应用往/usr/local下写日志直接把upperdir写满了系统瞬间变得各种诡异命令报错、配置文件写不进去、服务重启失败。后面我统一把根分区调大到2GBupperdir余量给足问题再没出现过。3. 系统升级从手动刷机到OTA的实用方案3.1 升级的本质内核、设备树、根文件系统三个要协调更新升级工控单板的Linux系统和你在PC上点一下“系统更新”完全是两码事。工控系统升级的本质是同步更新三个东西内核image、设备树dtb、根文件系统rootfs而且三者的版本要匹配。内核和dtb不匹配可能会导致外设初始化失败rootfs里的驱动模块.ko和内核版本对不上则直接modprobe报错。所以我在做升级设计时第一件事不是写代码而是定义好版本号规则和兼容矩阵。我习惯用一个version.ini放在系统分区里里面写上[system] kernel4.19.232 dtbrockchip-rk3399-industry-v1.2.dtb rootfs2025.06 compat_level3compat_level是兼容等级只有升级包里的等级不小于当前系统的等级时才允许升级rootfs。防止有人把老版本的rootfs刷到新版本内核上导致一大片驱动不兼容。3.2 升级包的结构不是传个tar包解压那么简单很多刚入行的朋友觉得“升级嘛就是把新rootfs打成tar包传到板子上解压覆盖”。这种做法在开发调试机上加个班能接受但绝对不适合量产设备。格式良好的升级包应该是自包含的镜像文件而不是文件散装集合。我采用的升级包格式是这样的update_20250601_0712.img ├── header ├── mtdblock0_uboot.bin ├── boot.img (包含kernel和dtb的完整启动镜像) ├── rootfs.img (可选的整包rootfs镜像) ├── rootfs.tar.zst (可选的rootfs压缩包) ├── md5sums.txt └── version.ini整包升级用dd把镜像写进对应分区差分包才用tar去更新具体文件。整包升级的好处是结果可预期写完校验一遍md5发现不对就回滚不存在“升级到一半文件缺失”的中间状态。3.3 OTA升级流程下载、校验、切换、回滚在工控场景下大部分设备没有固定公网IPOTA的常见实现方式有两种设备主动拉取设备定期或定时访问升级服务器检查manifest文件发现新版本就下载下载完提示重启升级。维护人员推送运维人员通过管理后台下发指令设备收到指令后去指定地址下载升级包。我实现过一套轻量级的OTA逻辑核心步骤可以归纳为这几个阶段下载与校验下载完成后对升级包做MD5/SHA256校验防止传输损坏。写入备用分区不直接覆盖当前系统而是把新镜像写入2.2中提到的那个“备份分区”写完后再次校验。切换启动项修改uboot的环境变量中的boot_part参数让下次启动从新分区引导。重启验证设备重启后运行新系统进行“启动自检”和“业务跑测”。提交/回滚如果自检通过则把当前使用的分区标记为“好”如果启动失败或自检不通过uboot检测到启动计数不增加就自动回滚到旧分区。注意这里的启动计数很关键。我的做法是uboot里维护一个bootcount变量每次启动引导时加一系统启动完成后由应用脚本清零。如果三次启动都没成功清零说明新系统有问题直接切回另一个分区。# 在系统启动完成后由systemd服务调用 if [ -f /sys/firmware/efi/efivars/bootcount ]; then # 实际硬件一般通过uboot env操作这里用fw_setenv示例 fw_setenv bootcount 0 fi3.4 页面升级与远程管理的一点建议热搜词里有一堆“页面升级访问中永久更新”这样的词说明很多人做的是网页方式的升级入口。工控设备上做一个本地Web升级页面确实很实用设备开机后同一个局域网内的维护人员打开浏览器输入设备IP就能操作免去拆机接串口的麻烦。做页面升级时要注意几个细节升级过程中一定要给Web页面返回进度否则维护人员以为卡死了断电就完蛋。我一般通过后端的SSEServer-Sent Events往前端推进度。升级期间锁定并发请求防止多个浏览器同时触发升级。我就在现场遇到过两个工程师同时打开页面各传了一个升级包结果互相覆盖。升级完成后不要立即断电页面提示“请等待设备自动重启”后台把缓冲区的数据落盘、umount分区再执行reboot。3.5 升级失败最常见的三个原因排障升级问题我总结出三个高频坑第一个坑升级过程断电分区表或关键数据损坏。对策是所有写操作前先备份关键区uboot、分区表、uboot env写完后立即fsync落盘。我甚至在设备上用一个GPIO接了“升级中”指示灯现场维护人员一看灯亮就知道不能断电。第二个坑空间不足。下载的升级包是压缩的解压后需要的空间可能是包体积的三四倍。下载之前先检查备份分区剩余空间不够就直接报错别等到写入一半才失败。第三个坑版本判断逻辑有bug。我曾经踩过一个大坑升级脚本判断“目标版本大于当前版本”时只比较了版本字符串的中间一位数字结果1.10被当成比1.9小导致设备永远升不到新版本。现在统一用dpkg --compare-versions类似的语义版本比较库或者干脆用整数表示的版本号。4. OverlayFS恢复出厂的底层原理和落地实操4.1 OverlayFS的作用让“只读系统”也有“可写表面”老实说没有OverlayFS之前工控设备要实现“恢复出厂”是很痛苦的。要么是直接dd整个eMMC镜像时间长、风险大要么是重新跑一遍工厂烧录脚本在现场不具备这条件。OverlayFS的出现改变了这个局面。它的思路特别简洁把底层目录lowerdir设为只读然后叠加一个可写的上层目录upperdir两者合并后呈现出新的视图mergeddir。所有对合并视图的写操作直接进upperdir不会碰下层。用户对系统的修改其实都记在upperdir里了。做个生活化的比喻lowerdir就像一本印刷好的书你不能改动书页本身upperdir就像贴在书边上的一叠便利贴。你在书上做备注时实际写在了便利贴上了。哪一天你不想要这些备注了把便利贴全部撕掉就行了——书还是原来的书一页都没坏。4.2 怎么判断你的系统该不该用OverlayFS我判断一个项目是否需要OverlayFS主要看三个问题系统是否希望在异常断电、误操作后还能恢复到一个干净的初始状态是否希望恢复出厂的操作快速且可靠而不是花好几分钟重新刷写eMMC是否有独立的数据分区用来存放真正需要保留的业务数据如果这三个问题的答案都是“是”那OverlayFS就是合适的方案。更直白地说凡是需要“系统分区只读 快速重置”的Linux设备OverlayFS都是最佳实践之一。反过来讲有些场景不适合如果你的业务要求在根分区里装一堆乱七八糟的软件、频繁更新系统内核那OverlayFS会带来额外的复杂性。这种场景优先考虑标准的读写rootfs 数据分区备份方案。4.3 构建一个OverlayFS启动系统的完整步骤我在项目里一般写一个init脚本来完成OverlayFS的挂载大概流程如下#!/bin/sh # /etc/init.d/overlayfs.sh (或放到systemd服务里) LOWER_DIR/mnt/lower # 真实只读的根文件系统 UPPER_DIR/mnt/upper # 可写层位于根分区独立区域或数据分区 WORK_DIR/mnt/work # workdir必须和upper在同一文件系统 MERGED_DIR/mnt/merged # 合并后的根视图 # 挂载真实根分区假设真实根分区是/dev/mmcblk0p2已由内核挂载为只读 mount -o remount,ro ${LOWER_DIR} # 准备upper和work目录 mkdir -p ${UPPER_DIR} ${WORK_DIR} ${MERGED_DIR} # 检查upper目录是否需要重置出厂标记 if [ -f /data/factory_reset ]; then rm -rf ${UPPER_DIR}/* rm -f /data/factory_reset fi # 挂载overlay mount -t overlay overlay -o lowerdir${LOWER_DIR},upperdir${UPPER_DIR},workdir${WORK_DIR} ${MERGED_DIR}要点是内核将真实根分区以只读方式挂载到/mnt/lower之后脚本再把overlay挂载到/mnt/merged然后用pivot_root切换到merged作为新的根目录。这样整个用户空间看到的就是“只读系统 可写覆盖层”的样子。4.4 恢复出厂为什么一个rm命令就能搞定这是OverlayFS最爽的地方恢复出厂只需要清空upperdir然后重启。因为lowerdir是出厂烧录进去的原始只读rootfs从来没被改过它本身就是“出厂状态”的完美副本。我的恢复出厂逻辑在系统里做成一个web按钮和一条命令两种入口# 恢复出厂命令 touch /data/factory_reset reboot重启后init脚本检测到/data/factory_reset文件存在就把upper目录清空然后重新挂载OverlayFS。整个恢复过程用时不到一秒钟而不是传统方式下dd整个分区的好几分钟。这里分享一个心得恢复出厂不等于清数据。如果设备里有业务数据、用户配置这些应该放在/data分区里恢复时可以选择保留或清空。我的恢复按钮会问一句“是否同时清空业务数据”回答“是”才会把/data分区也格式化。4.5 OverlayFS实现时的三个大坑第一个坑upperdir空间不够。系统运行一段时间后包管理器缓存、临时文件、日志都会堆积在upperdir里。我的对策是定期做目录大小检查超过阈值就报预警同时把日志重定向到/data分区。还可以设置upperdir的配额比如用quota限制单个用户/用户组可写量。第二个坑workdir和upperdir必须同文件系统。OverlayFS要求upperdir和workdir必须在同一个文件系统上否则挂载失败。我记得第一次配的时候把workdir放到了tmpfs上结果怎么mount都不成功查了半天内核日志才找到原因。第三个坑升级流程和OverlayFS的冲突。如果你升级rootfs的方式是“把整个分区dd新镜像”那么OverlayFS的lowerdir会被替换upperdir里的旧文件就可能和新lower层产生关联错乱。我的做法是升级rootfs后必须同步重置upperdir或者把升级流程设计成“只更新文件不更新分区”。5. 一台设备从烧录到交付完整流程演示5.1 烧录阶段工厂端怎么把干净系统灌进去工厂量产烧录和你在开发板上玩不太一样。工厂讲究的是“稳定可复制、快速、防呆”。这块要确认几个可复制的关键环节。在烧录阶段我一般准备两个层次的镜像烧录母片和工厂烧录镜像。烧录母片是开发阶段用SD卡启动后手工配置好的最小系统工厂烧录镜像则是将这母片导出为dd镜像再配合烧录工具整体写入eMMC。RRK3399平台常用的烧录工具是upgrade_tool或者瑞芯微的RKDevTooli.MX平台则用mfgtool。这些工具都能在USB烧录模式下直接写eMMC。量产脚本我写得比较保守流程是清空eMMC → 写uboot到NOR → 写boot分区 → 写rootfs → 写预置数据 → 校验。每一步校验完毕才进入下一步。简单点说工厂端和客户现场的交付区别只在于“有没有烧录母片”而系统侧的内容是一模一样的干净rootfs。5.2 首次开机OverlayFS怎么自动初始化首次开机时设备会自动检测upperdir目录是否存在。如果不存在说明是第一次启动脚本会把uboot env里的firstboot参数置位然后进入“首次初始化”流程清空数据分区并创建默认目录结构。重置upperdir确保后面所有自定义内容都从干净状态开始。生成本机唯一ID用于设备管理写入/data/device_id。写uboot env标记bootcount0表明系统已就绪。关闭firstboot标记。这个“首次初始化”千万不能省。有些团队图省事把upperdir在烧录时就预置了一些内容结果每台设备首次启动都带着同一套残留数据后期查问题非常痛苦。5.3 交付后运维视角的日常操作设备到了客户现场运维工具有限大多数时候只有一张Web升级页面和SSH。我强烈建议在做交付时把下面这一组命令提前固化成一个sysinfo.sh脚本放在系统的PATH里#!/bin/bash echo 系统信息 echo Kernel: $(uname -r) echo Version: $(cat /etc/os-release | grep VERSION) echo Machine: $(cat /proc/device-tree/model) echo RootFS: $(mount | grep / ) echo Overlay: $(mount | grep overlay) echo Data分区剩余: $(df -h /data | tail -1) echo Upper目录大小: $(du -sh /overlay/upper 2/dev/null) echo 上次升级时间: $(cat /data/update_time 2/dev/null)一个合格的交付文档一定要包含这个脚本的输出说明让现场工程师一看就知道问题出在系统层还是数据层。6. 常见问题与排查技巧实录6.1 开机失败只有黑屏/串口没输出可能原因Bootloader丢失、分区表损坏、uboot env被改坏。排查步骤接上串口看是完全没有打印还是有部分打印后停止。完全没有打印优先怀疑uboot没起来。对RK平台按住板上的recovery键上电进入maskrom模式重新烧uboot。有打印但停在启动内核之前查uboot env里的bootcmd和bootargs是不是被改过。用env default -a恢复默认环境变量。6.2 系统起来了但OverlayFS没挂载成功排查路径很典型先看内核是否支持OverlayFSgrep overlay /proc/filesystems。如果不支持要么换内核配置要么加载overlay.ko模块。再验mount -t overlay是不是报错。如果报No such file or directory八成是upperdir或workdir的路径没创建。最后确认workdir和upperdir是不是在同一个文件系统上这个是老坑。6.3 升级后系统版本没变怀疑升级生效了但又没生效这种问题多半是同时存在两套系统uboot启动时加载的boot分区里的内核与rootfs和你在shell里看到的不一致。排查方法很简单uname -r cat /proc/cmdline mount | grep / 对比这几条输出确认启动用的到底是哪个分区。我建议在uboot的kernel cmdline里加上一个boot_part参数并且把分区号打印在启动log中这样现场一看log就知道系统从哪个分区启动的。6.4 eMMC空间被日志撑爆工控设备最容易出现“磁盘满了”的地方就是日志。我见过一个设备跑了一年log目录占了几百MB直接把数据分区塞爆。我的对策是日志大小上限用logrotate做轮转限制总日志大小在50MB左右。日志重定向把应用日志写到/data/logs而不是/var/log。定期清理每天凌晨执行一次清理脚本删除超过7天且体积大于指定值的日志。另外OverlayFS的upperdir也可能因为日志被写满。我在系统里加了一个监控脚本当upperdir使用率超过85%时自动把/var/log下的文件迁移到/data/logs并对老日志做压缩和清理。6.5 排查工具速查表我在现网排查问题时最常用的就是下面这几个命令效率很高目标命令查看分区布局lsblk/fdisk -l查看挂载详情findmnt/mount查看内核命令行cat /proc/cmdline查看启动日志dmesg/journalctl -b用量最大的目录du -x --max-depth2 / | sort -k1 -rn | head -20检查overlay状态mount | grep overlay检查分区表一致性fsck.ext4 -fn /dev/mmcblk0p27. 几个让我印象最深的现场教训写到最后分享几个让我交了学费的真实教训。第一个教训不要轻易在量产设备上删分区。我有一次为了给数据分区腾空间在生产脚本里加了一条“如果eMMC分区表不对就重建”的逻辑结果新固件在个别设备上误判了老分区表直接把一个还在跑业务的分区给重新格式化了。从那以后凡是涉及分区表重写的代码我都要先在几十台设备上做灰度验证而且要加“目标设备当前分区表必须与预期完全一致”的前置校验。第二个教训升级包必须做幂等处理。也就是说同一个升级包执行两次结果应该和执行一次完全一样。我遇到过升级脚本里用了“追加写入”的方式记录升级日志导致同样一条日志写了两遍后续脚本解析时误判版本状态把系统回滚了。现在我的所有升级脚本都要求“可重复执行”而且执行前先保存一份现场状态方便出问题时对照。第三个教训Web升级页面要做好并发和会话管理。前面提过两个人同时点升级的问题这里再补一个示例我的Web后端在收到升级请求时先从Redis或者内存里拿一把锁如果锁已存在就直接拒绝新请求。前端则显示“已有工程师正在执行升级操作请勿重复操作”。这个功能看着小但真的能在关键时刻保住设备。第四个教训永远留一条“串口/SSH能进系统”的后路。不管你用OverlayFS还是OTA系统总有彻底坏掉的可能。所以uboot里一定要留一个bootdelay倒计时按键进入命令行或者从SD卡启动救援系统的入口。这个“后门”平时用不上但真出了大事它是你唯一的救命稻草。8. 接下来这篇还能扩展什么这次内容主要集中在存储配置、升级和OverlayFS恢复出厂的核心理念和落地动作。同一套方案里还有很多细节没有展开比如A/B分区的完整双备份方案和OverlayFS的“单分区重置”相比A/B分区的升级会更平滑但对存储容量要求更高适合对可用性要求极高的设备。U-Boot环境变量管理bootargs、bootcmd、bootcount、recovery_mode这些变量怎么配合使用。工厂量产防呆设计怎么样让产线烧录更不容易出错比如用烧录次数计数器、SN绑定、烧录日志回传。系统安全加固只读挂载配合dm-verity、SELinux策略进一步提升系统防篡改能力。我个人的计划是下一篇文章专门把A/B分区升级和OverlayFS的完整构建过程走一遍写一个从零到一、可以照着抄的完整脚本集。如果你在做类似的事也欢迎在评论区讲讲你碰到的奇葩问题说不定哪一篇就是为你写的那条路。
返回列表