工控单板跑 Linux,最怕的不是性能不够,而是系统在客户现场出了乱子没人能管。前两篇聊完系统裁剪和启动流程,这篇把存储、升级和恢复出厂这三块一次性讲透。这三件事在开发阶段往往被当成“杂活”,可真上了产线、进了项目,它们才是决定产品好不好维护的关键。我会结合实际踩过的坑,把为什么这样设计、现场怎么操作、遇到问题怎么排查都拆开说清。
1. 存储布局:工控系统稳定性的底座
1.1 双存储架构怎么选,eMMC 还是 SD 卡
工控单板上的存储介质,绝大多数就两种:eMMC 和 SD 卡。eMMC 焊死在板子上,速度尚可、抗震动、不会因为接触不良掉线,适合放系统和核心程序;SD 卡可以随时拔插,扩容方便,但触点氧化、意外弹出、掉电写入中断这些问题都可能导致文件系统损坏。
所以我的习惯是“系统与数据分离”:根文件系统放 eMMC,业务日志、临时文件、用户配置放 SD 卡或独立数据分区。这样即使 SD 卡坏了,换一张就能恢复运行,不用拆机重新烧录。你们拿到板子以后,先用lsblk看一眼存储拓扑,心里有数再动手。
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE这行命令会列出所有块设备、容量、挂载点和文件系统类型。工控板常见的输出是mmcblk0(eMMC)和mmcblk1(SD 卡),下面再分p1、p2等分区。比如我常用的板子是这样的:
mmcblk0 ├─mmcblk0p1 uboot ├─mmcblk0p2 boot ├─mmcblk0p3 rootfs ├─mmcblk0p4 userdata mmcblk1 └─mmcblk1p1 sddata分区规划不复杂,但每条都有讲究。uboot 分区放引导程序,boot 分区放内核和设备树,rootfs 分区是根文件系统镜像,userdata 是留给 overlayfs 做可写层的数据分区。这样设计之后,系统和数据彻底隔离,恢复出厂时只动 userdata,不动 boot 和 rootfs,安全得多。
1.2 分区规划与挂载关系
分区规划的另一层考虑是文件系统类型。我推荐 rootfs 用 ext4 或 squashfs,userdata 用 ext4。squashfs 是只读压缩文件系统,容量利用率高,配合 overlayfs 几乎不可能被用户进程写坏;ext4 成熟稳定,适合需要频繁读写的分区。
挂载参数的取舍也很重要。userdata 分区挂载时加上noatime,可以避免每次读文件都更新访问时间,减少不必要的 eMMC 写入。eMMC 的写入寿命虽然不差,但工控设备动不动就要跑十年,能省一次是一次。如果项目对可靠性要求更高,可以考虑在挂载时加上commit=600,让数据延迟写入,降低掉电损坏概率。下面是/etc/fstab里常见的写法:
/dev/mmcblk0p3 / ext4 ro,noatime 0 1 /dev/mmcblk0p4 /var/lib ext4 rw,noatime,commit=600 0 2这里我把 rootfs 挂成只读了。很多工控项目其实都可以接受“根文件系统只读、数据分区可写”的模式,这样系统不容易被写坏,也更符合 OverlayFS 的典型用法。
1.3 存储空间检查与在线扩容
现场排查问题时,第一步永远是看空间够不够用。df -h是基础,但工控系统里经常出现“明明删了文件,空间却没释放”的情况,这是因为进程还在占用被删除的文件。可以用lsof | grep deleted找出占用进程,重启它或者 kill 掉,空间才会真正释放。
如果 userdata 分区当初分小了,又不想重新刷机,可以用resize2fs在线扩容。前提是分区后面还有空闲空间。比如要把 mmcblk0p4 扩大 2GB,流程是:
parted /dev/mmcblk0 resizepart 4 100% resize2fs /dev/mmcblk0p4parted调整分区表,resize2fs扩展文件系统。整个过程可以热操作,但建议先备份关键数据。生产设备上执行分区表变更,风险仍然存在,能不上就不上,真正需要扩容时再这样做。
提示:如果板子上的分区表是固定写在代码里的,调整分区后要同步修改设备树里的分区定义,否则重启后分区信息会还原到默认状态,整个扩容就等于白做了。
2. 系统升级:如何在不大拆大改的情况下完成换代
2.1 升级包结构与校验
工控设备的升级和手机刷 ROM 不一样。现场工程师多数没有串口线,也不能拆壳短接跳线,所有操作都得通过远程或人机界面完成。所以升级包和升级逻辑必须设计得简单可靠。
我习惯把升级包做成一个 tar.gz 压缩包,里面至少包含这些东西:
upgrade_package.tar.gz ├── VERSION # 版本号 ├── boot.img # 内核/设备树镜像 ├── rootfs.squashfs # 根文件系统镜像 ├── app_update.deb # 业务软件的安装包 └── md5sums.txt # 所有文件的校验值升级程序第一步就是做完整性校验。直接用解压工具解包不可靠,要先读取md5sums.txt,逐文件校验,任何一项不通过就中止升级并保留下一次重试的机会。别嫌这一步麻烦,我在现场遇到过好几次下载中断导致升级包损坏的情况,如果少了校验环节,刷到一半才发现问题,设备就可能救不回来了。
校验代码可以用 Shell 写,也可以用 Python,关键是不能因为校验逻辑太复杂导致自身产生 bug。下面是一段简化版:
while read md5 file; do echo "$md5 $file" | md5sum -c - || exit 1 done < md5sums.txt2.2 软件包级升级与镜像级升级
升级要看范围。如果只是业务程序更新,没必要整个系统重刷。在 Debian/Ubuntu 系的工控系统里,把新的应用打成.deb包,升级时dpkg -i一下就完事。这样升级包小、速度快、风险低。
但内核、驱动或根文件系统层面的改动,软件包升级就无能为力了,必须走镜像级升级。镜像级升级的本质是替换 rootfs,有三条常见路线:
- 整体写 flash:把新 rootfs 直接 dd 写入 rootfs 分区。实现简单,但升级过程如果断电,设备可能变砖。需要配合恢复分区或 U-Boot 的引导兜底。
- A/B 双分区切换:系统准备两个 rootfs 分区,一个当前运行,一个空闲待写入。升级时写入空闲分区,重启后引导到新分区。这条路线最稳妥,代价是要多预留一倍 flash 空间。
- squashfs 镜像替换:因为 squashfs 只读,替换时先写入新镜像,更新完重新挂载。配合旧镜像备份,也能做到可回滚。
实际项目里,如果设备价格敏感、flash 空间有限,我推荐第一种加“升级前备份旧系统”的组合;如果产品有空间冗余,直接用 A/B 分区,升级体验最接近消费级设备。下面是一个镜像升级的简化脚本片段:
# 写入新 rootfs 到空闲分区 dd if=/rootfs.squashfs of=/dev/mmcblk0p5 bs=4M conv=fsync # 更新 U-Boot 环境变量,下次从新分区启动 fw_setenv boot_para root=/dev/mmcblk0p5 sync rebootfw_setenv是 U-Boot 环境变量读写工具。用 U-Boot 环境变量记录“下一次从哪里启动”,是整个升级逻辑里最关键的一环。环境变量写错或写入失败,设备可能无法正常引导。
2.3 A/B 分区与回滚机制
A/B 分区方案里,U-Boot 环境变量通常记录当前槽位和尝试次数。每次启动时引导程序读环境变量,确定加载哪个分区的内核和根文件系统。升级流程大致是:
- 确认当前在 A 槽,B 槽空闲。
- 把新系统刷入 B 槽。
- 写“bootcount=3,bootslot=B”。
- 重启,U-Boot 尝试从 B 槽引导并递减 bootcount。
- 系统正常启动后,业务程序上报“良好”,清除 bootcount。
- 如果启动失败,bootcount 归零,U-Boot 自动回退到 A 槽。
这个机制在很多工业板卡上都有现成实现,原理就是“试用三次,失败自动回滚”。好处是升级不再让人提心吊胆,即使新系统起不来,旧系统也会被拉回来继续工作。
2.4 升级前备份与升级后校验
升级操作再完善,也得考虑用户配置的保留。我在设计升级流程时,会在升级前自动备份/etc下关键配置到/var/lib/backup,升级完成后根据配置内容选择恢复,避免升级后所有设备参数都回到默认值。
升级后校验分两步。第一步检查版本号是否符合预期:
cat /etc/version第二步做基本功能冒烟测试,比如网络接口是否起来、业务进程是否在运行、关键服务端口是否监听。这些判断可以在升级脚本末尾自动执行,也可以留给现场人员用简单命令确认。
注意:升级过程中最忌讳断电。如果有看门狗在跑,升级前要先暂停或延长看门狗超时,否则刷到一半系统被看门狗复位,后果和断电几乎一样。很多工控系统升级失败的根因不是镜像坏了,而是看门狗没关。
3. OverlayFS 恢复出厂:把系统拉回出厂状态的底层逻辑
3.1 OverlayFS 工作原理解读
OverlayFS 是 Linux 内核提供的联合文件系统,用来把两个目录合并成一个视图。一个典型的挂载命令是这样的:
mount -t overlay overlay \ -o lowerdir=/mnt/rootfs_ro,upperdir=/var/lib/overlay/upper,workdir=/var/lib/overlay/work \ /mnt/merged这里的lowerdir是只读的根文件系统镜像目录,upperdir是可写层,workdir是 OverlayFS 的工作目录。合并后的/mnt/merged看起来像一份完整文件系统,读文件优先读 upper,upper 不存在时回落到 lower。
这套机制有一个天然特性:底层只读镜像永远不会被修改。系统运行时产生的改动全都在 upper 层。这就像给系统装了一个“保护罩”,用户改了什么、写坏了什么,都只发生在罩子外面,罩子里的原始系统永远完整。
理解了这个机制就能明白,OverlayFS 恢复出厂的内核逻辑极其简单:只要把 upper 层清空,重启后 merged 视图就会回归到镜像出厂时的状态。听起来像魔法,实际就是一个清目录操作。
3.2 恢复出厂实现:清空 upperdir 就够了
恢复出厂逻辑可以挂在业务程序里,也可以单独写一个守护脚本。核心就两步:删 upper 内容,重启系统。
rm -rf /var/lib/overlay/upper/* rm -rf /var/lib/overlay/upper/.[!.]* sync reboot注意第二条rm,Linux 默认不匹配隐藏文件,必须单独处理。这句话一定不能漏,否则用户配置里常见的点开头文件还在,恢复就不彻底。
有的系统用find配合-delete清理,更安全一些:
find /var/lib/overlay/upper -mindepth 1 -delete-mindepth 1保证不会删除 upper 目录本身,只清空内部。清空之前最好把当前 upper 层打包备份,万一用户又反悔要求恢复误删的数据,还能找回来。工控现场这种情况并不少见。
3.3 保留业务配置的白名单设计
恢复出厂不能一刀切。有些业务配置是设备唯一标识或出厂校准数据,一旦清掉,设备可能无法正常联网或丧失功能。比如设备序列号、无线模块的校准信息、屏幕亮度校准参数等。
我的做法是在恢复出厂脚本里定义一个白名单文件列表,默认要保留的配置在清理时跳过。流程可以设计成这样:
- 把要保留的文件先复制到临时目录。
- 清空 upper 层。
- 把保留文件复制回原位。
- 重启。
下面是一段示意脚本:
SAVE_DIR=/mnt/.reserved for f in /var/lib/overlay/upper/etc/device_id \ /var/lib/overlay/upper/etc/calib.conf; do cp "$f" "$SAVE_DIR/" 2>/dev/null done find /var/lib/overlay/upper -mindepth 1 -delete for f in "$SAVE_DIR"/*; do cp "$f" /var/lib/overlay/upper/etc/ done sync reboot这段逻辑尤其适合带“出厂校准”的工控品。现场恢复后设备马上就能用,不需要重新做一轮校准,否则用户会认为恢复出厂是“把设备恢复成废品”。
3.4 加入 U-Boot 与物理按钮的兜底方案
软件层的恢复出厂再可靠,也会遇到系统完全瘫痪的情况。比如业务程序在启动阶段就崩溃,或 upper 层塞满了导致磁盘写满,系统起不来。这种时候靠操作员在系统里点“恢复出厂”是不可能的。
我的兜底方案是 U-Boot 加物理按钮。板子上留一个 GPIO 按键,设备断电状态下按住,上电后停在 U-Boot 交互模式。工程人员在串口里执行:
uboot> run recovery_cmd这个环境变量会在 U-Boot 阶段格式化或清空 userdata 分区,然后重新引导。由于不依赖上层系统,即使 rootfs 已经完全损坏,也能靠这个通道回到一个可用状态。
如果连串口都不方便接,也可以做成“多次重启进入恢复模式”:业务程序启动时检测某个标志文件,存在就下次重启自动清理 upper;同时 bootcount 机制兜底,连续三次启动失败就自动恢复出厂并回退到旧系统。工控设备在现场无人值守的场景里,这层设计能把故障恢复时间从“小时级”降到“分钟级”。
4. 常见问题与排查实录
4.1 磁盘空间被写满导致系统异常
现象:设备运行一段时间后业务程序启动失败,远程登录执行df -h发现根分区或数据分区使用率 100%。
排查思路:先定位是哪个目录吃了空间。用du -sh /*逐层排查,重点检查/var/log、/tmp、/home、以及业务程序的缓存目录。日志文件是最常见的元凶,很多工控程序只知道往外打日志,从不考虑轮转清理。
解决办法:把日志目录挂载到独立数据分区,并让logrotate定期轮转。如果日志量实在大,可以在 OverlayFS 的 upper 层允许范围之外限制最大占用,写满后直接丢弃最老日志。不要指望现场工程师定期登进去删日志,设备要能自我消化。
4.2 OverlayFS 层文件残留导致故障
现象:系统配置改过一次后,恢复出厂仍能观察到旧配置的影响,软件行为“恢复不干净”。
原因:业务程序在 upper 层生成了一些数据文件,而这些文件没有出现在清空范围里,或者清空脚本执行了但程序马上又把文件写回来了。
排查方法:恢复出厂后立刻find /var/lib/overlay/upper -type f查看残留。另外要确认一个容易忽略的细节——如果系统重新挂载了 overlay 之后旧的数据还缓存在页缓存里,最好执行一次sync并稍等片刻。部分文件系统会延迟写入,清空后立刻重启,有些数据还是会落盘。
经验做法:恢复出厂脚本里既要清 upper,也要清数据分区的缓存目录。比如业务缓存放在/var/cache/app,直接连 cache 一起清掉,不给“复写”留机会。
4.3 升级后启动失败或版本未改变
现象:执行升级脚本后重启,系统仍显示旧版本,或直接启动不了。
先排查版本为什么没变。最常见原因是升级写入的分区和实际启动分区不一致。U-Boot 环境变量里写的 boot partition 是 p3,升级脚本却把镜像写进了 p4。看起来刷成功了,实际刷的是从来没被加载的分区。建议升级脚本先读取当前启动分区信息,再决定写入目标,不要写死。
启动失败则要看串口日志。如果卡在内核加载阶段,多半是内核镜像本身问题或设备树不匹配;如果卡在 mount rootfs,查看 U-Boot 的bootargs里 root 参数是否写对了分区。值得检查的首先是 U-Boot 环境变量,这个问题值得第一个排查,它在 A/B 升级失败案例里占比非常高。
如果手头有备份旧系统,可以直接写回旧分区,恢复现场再慢慢分析,不必硬着头皮在故障状态下调试。
4.4 掉电后文件系统损坏的处理
现象:设备突然掉电重启后无法引导,或进入系统后大量异常报错。
原因:eMMC 和 SD 卡在写入过程中掉电,可能造成文件系统元数据不一致。ext4 自带日志,一般能自行恢复,但极少数情况下会卡在fsck等待人工干预。
处理方式:在 U-Boot 阶段加一个自动fsck步骤,启动时检查文件系统并自动修复。同时业务程序要保证写文件时的原子性——先写临时文件再rename,避免主文件被写了一半。
# 启动脚本里可以先做一次修复(只读方式检查或自动修复) e2fsck -p /dev/mmcblk0p4注意:
e2fsck -p会自动修复部分错误,但它不是万能药。如果分区出现的是物理坏块,修复程序也只能尽力跳过。工控设备最重要的防线是供电设计,UPS 或掉电检测电路远比软件 fsck 可靠。
4.5 实用速查表
| 问题 | 疑似原因 | 排查命令/方法 |
|---|---|---|
| 空间满 | 日志或缓存膨胀 | df -h、du -sh /* |
| 删除文件空间不释放 | 进程持有已删除文件句柄 | lsof | grep deleted |
| 恢复出厂不彻底 | 隐藏文件残留或缓存回写 | find /upper -type f |
| 升级后版本没变 | 刷错分区或 U-Boot 环境变量指向错误 | fw_printenv |
| 掉电后无法引导 | 文件系统损坏 | 串口日志 +fsck |
| OverlayFS 挂载失败 | workdir 不存在或权限异常 | dmesg | tail |
5. 写在最后的一点经验
存储、升级、恢复出厂这三块,看着是“底层杂活”,其实决定了设备交付之后的日子好不好过。我在项目里已经养成了几个习惯,每次发板子前都要过一遍:分区表写进文档,不能只存在某个人脑子里;升级脚本先跑十遍断电测试再发布;恢复出厂固化到 U-Boot 兜底,永远不让软件故障变成硬件故障。
如果你们正在做类似的项目,建议先从查看板子的存储布局入手,搞清楚分区和挂载关系,再根据实际情况设计升级和恢复流程。整套方案里,OverlayFS 是成本最低、效果最明显的一环,靠它做出来的“恢复出厂”,比花大价钱买商业备份还原工具还要省心。也希望这一篇能帮大家少踩几个我曾经踩过的坑,设备发出去以后少接几通半夜的救援电话。