Linux 工控项目打包、烧录、批量量产部署完整流程
从开发板到出厂出货,中间隔着一条完整的量产流水线——这流程搞不定,代码写得再好也只是实验室玩具。
一、工控项目交付物构成
一个完整的Linux工控固件包,通常包含以下组件:
| 组件 | 文件 | 说明 |
|---|---|---|
| Bootloader | uboot.img / idbloader.img | 引导加载器,负责硬件初始化和加载内核 |
| 内核 | kernel.img / zImage / Image | Linux内核镜像 |
| 设备树 | dtb文件 | 硬件描述,告诉内核板子上有什么外设 |
| 根文件系统 | rootfs.img | 包含所有用户空间程序和库 |
| 应用包 | app.tar.gz / app.ipk | 你的工控应用程序 |
| 配置文件 | config/ | 默认配置、网络参数等 |
firmware_v1.0.0/ ├── idbloader.img # Bootloader阶段1 ├── uboot.img # Bootloader阶段2 ├── kernel.img # Linux内核 ├── rk3568-board.dtb # 设备树 ├── rootfs.img # 根文件系统(ext4/squashfs) ├── app/ │ ├── myapp # 主程序 │ ├── lib/ # 依赖库 │ └── config/ # 配置文件 └── version.txt # 版本信息二、固件打包工具
2.1 mkimage —— U-Boot镜像封装
mkimage是U-Boot自带的镜像打包工具,给内核镜像加上U-Boot识别的头信息:
# 将zImage打包成uImagemkimage-Aarm64-Olinux-Tkernel-Cnone\-a0x80080000-e0x80080000\-n"Linux Kernel"\-darch/arm64/boot/Image uImage# 参数说明:# -A 架构(arm64/x86/mips)# -O 操作系统(linux)# -T 镜像类型(kernel/ramdisk/script)# -C 压缩方式(none/gzip/bzip2)# -a 加载地址# -e 入口地址# -n 镜像名称# -d 输入文件2.2 genext2fs —— 制作ext2文件系统镜像
# 根据目录生成ext2文件系统镜像genext2fs-b32768-drootfs_dir rootfs.img# -b 块数(每块1KB,32768块=32MB)# -d 源目录2.3 mksquashfs —— 制作只读压缩文件系统
squashfs是工控设备根文件系统的首选,只读+压缩,掉电不损坏:
# 将rootfs目录打包成squashfs镜像mksquashfs rootfs_dir rootfs.squashfs-compxz -no-xattrs# -comp 压缩算法(xz/gzip/lzo/zstd)# xz压缩率最高,lzo解压最快2.4 dd —— 磁盘镜像工具
# 将SD卡内容完整导出为镜像ddif=/dev/sdbof=sdcard.imgbs=4Mstatus=progress# 将镜像写入SD卡ddif=sdcard.imgof=/dev/sdbbs=4Mstatus=progressconv=fsync三、SD卡烧录
3.1 分区方案
典型的SD卡分区布局:
| 分区 | 偏移 | 大小 | 内容 | 文件系统 | |------|------|------|------|----------| | idbloader | 0x40 | 32KB | Bootloader | 无 | | uboot | 0x4000 | 4MB | U-Boot | 无 | | boot | 0x8000 | 64MB | 内核+DTB | FAT32 | | rootfs | 0x88000 | 剩余 | 根文件系统 | ext4 |3.2 烧录脚本
#!/bin/bash# /opt/scripts/sdcard_burn.sh# SD卡烧录脚本SDCARD_DEV=$1# /dev/sdbFIRMWARE_DIR=$2# firmware_v1.0.0/if[-z"$SDCARD_DEV"]||[-z"$FIRMWARE_DIR"];thenecho"Usage:$0<sdcard_dev> <firmware_dir>"echo" e.g:$0/dev/sdb ./firmware_v1.0.0/"exit1fiecho"========================================"echo" SD Card Burning Tool"echo" Device:$SDCARD_DEV"echo" Firmware:$FIRMWARE_DIR"echo"========================================"# 安全确认echo"WARNING: All data on$SDCARD_DEVwill be erased!"echo"Type 'YES' to continue:"read-rCONFIRMif["$CONFIRM"!="YES"];thenecho"Aborted."exit0fi# 1. 卸载所有已挂载的分区umount${SDCARD_DEV}?*2>/dev/null# 2. 写入Bootloaderecho"[1/5] Writing idbloader..."ddif="${FIRMWARE_DIR}/idbloader.img"of="$SDCARD_DEV"bs=32kseek=1conv=fsyncecho"[2/5] Writing uboot..."ddif="${FIRMWARE_DIR}/uboot.img"of="$SDCARD_DEV"bs=64kseek=128conv=fsync# 3. 创建分区echo"[3/5] Creating partitions..."# 清空分区表ddif=/dev/zeroof="$SDCARD_DEV"bs=512count=2048conv=fsync# 用fdisk或sfdisk创建分区sfdisk"$SDCARD_DEV"<<EOF label: dos${SDCARD_DEV}1 : start=8192, size=131072, type=c, bootable${SDCARD_DEV}2 : start=139264, type=83 EOF# 刷新分区表partprobe"$SDCARD_DEV"2>/dev/nullsleep2# 4. 格式化分区echo"[4/5] Formatting partitions..."mkfs.vfat-F32-nBOOT"${SDCARD_DEV}1"mkfs.ext4-F-Lrootfs"${SDCARD_DEV}2"# 5. 写入内核和根文件系统echo"[5/5] Writing kernel and rootfs..."mkdir-p/tmp/sd_boot /tmp/sd_rootmount"${SDCARD_DEV}1"/tmp/sd_bootmount"${SDCARD_DEV}2"/tmp/sd_root# 拷贝内核和DTB到boot分区cp"${FIRMWARE_DIR}/kernel.img"/tmp/sd_boot/cp"${FIRMWARE_DIR}/"*.dtb /tmp/sd_boot/# 解压rootfs到root分区if[-f"${FIRMWARE_DIR}/rootfs.tar.gz"];thentar-xzf"${FIRMWARE_DIR}/rootfs.tar.gz"-C/tmp/sd_root/elif[-f"${FIRMWARE_DIR}/rootfs.img"];thenddif="${FIRMWARE_DIR}/rootfs.img"of="${SDCARD_DEV}2"bs=4Mconv=fsyncfisyncumount/tmp/sd_boot /tmp/sd_rootecho"========================================"echo" Burning Complete!"echo"========================================"四、eMMC/NAND烧录
4.1 fastboot(通用Android协议)
# 进入fastboot模式(U-Boot命令行中)=>fastboot0# 主机端烧录fastboot flash loader idbloader.img fastboot flash uboot uboot.img fastboot flash boot_a kernel.img fastboot flash dtbo_a rk3568-board.dtb fastboot flash system_a rootfs.img# 切换启动槽位并重启fastboot set_active a fastbootreboot4.2 rkflashtool(瑞芯微专用)
# 查看连接的设备rkflashtool p# 读取分区表rkflashtool r 0x0 0x2000 param.img# 烧录各分区rkdeveloptool db RK3568_loader.bin rkdeveloptool wl 0x40 idbloader.img rkdeveloptool wl 0x4000 uboot.img rkdeveloptool wl 0x8000 boot.img rkdeveloptool wl 0x88000 rootfs.img# 重启设备rkdeveloptool rd4.3 nandwrite(NAND Flash直接写入)
# 先擦除flash_erase /dev/mtd200# 写入镜像nandwrite-p/dev/mtd2 rootfs.ubi# UBI方式(NAND推荐)ubiformat /dev/mtd2-s2048-frootfs.ubi-yubiattach /dev/ubi_ctrl-m2mount-tubifs /dev/ubi0_0 /mnt/rootfs五、量产烧录自动化
批量烧录场景下,需要自动检测设备插入并触发烧录:
#!/bin/bash# /opt/scripts/mass_burn.sh# 批量SD卡烧录脚本(配合USB Hub多卡同时烧)FIRMWARE_DIR="/opt/firmware/current"LOG_FILE="/var/log/mass_burn.log"BURN_SCRIPT="/opt/scripts/sdcard_burn.sh"echo"$(date)[INFO] Mass burn service started">>"$LOG_FILE"# udev规则:SD卡插入时自动触发# /etc/udev/rules.d/99-sdburn.rules:# ACTION=="add", KERNEL=="sd[b-z]", SUBSYSTEM=="block", \# RUN+="/opt/scripts/mass_burn.sh /dev/%k"DEVICE=$1if[-z"$DEVICE"];thenecho"$(date)[ERROR] No device specified">>"$LOG_FILE"exit1fi# 等待设备稳定sleep3# 检查设备是否存在if[!-b"$DEVICE"];thenecho"$(date)[ERROR] Device$DEVICEnot found">>"$LOG_FILE"exit1fiecho"$(date)[INFO] Start burning$DEVICE">>"$LOG_FILE"# 调用烧录脚本(非交互模式)YES=YES$BURN_SCRIPT"$DEVICE""$FIRMWARE_DIR">>"$LOG_FILE"2>&1if[$?-eq0];thenecho"$(date)[OK]$DEVICEburned successfully">>"$LOG_FILE"# 闪烁指示灯或播放提示音echo1>/sys/class/leds/green/brightnesssleep1echo0>/sys/class/leds/green/brightnesselseecho"$(date)[ERROR]$DEVICEburn failed!">>"$LOG_FILE"echo1>/sys/class/leds/red/brightnessfi六、量产部署完整流程
固件版本管理 │ ▼ ┌─────────────┐ │ 1. 编译打包 │ ← CI/CD自动构建,产出固件包 └──────┬──────┘ ▼ ┌─────────────┐ │ 2. 烧录固件 │ ← SD卡/eMMC烧录 └──────┬──────┘ ▼ ┌─────────────┐ │ 3. 首次启动 │ ← 系统初始化、分区扩展 └──────┬──────┘ ▼ ┌─────────────┐ │ 4. 功能验证 │ ← 自动化测试脚本 └──────┬──────┘ ▼ ┌─────────────┐ │ 5. 烧录MAC │ ← 写入唯一MAC/序列号 └──────┬──────┘ ▼ ┌─────────────┐ │ 6. 出库登记 │ ← 记录序列号、版本、日期 └─────────────┘6.1 固件版本管理
# /etc/version 文件cat/etc/version# FW_VERSION=1.0.3# FW_DATE=2026-07-13# FW_GIT_COMMIT=a1b2c3d# HW_VERSION=V2.1与git tag关联:
# 发布固件时打taggittag-av1.0.3-m"Release v1.0.3: fix watchdog timeout issue"gitpush origin v1.0.3# 构建脚本读取tag写入版本文件GIT_TAG=$(gitdescribe--tags--always)BUILD_DATE=$(date+%Y-%m-%d)GIT_COMMIT=$(gitrev-parse--shortHEAD)cat>rootfs/etc/version<<EOF FW_VERSION=${GIT_TAG}FW_DATE=${BUILD_DATE}FW_GIT_COMMIT=${GIT_COMMIT}HW_VERSION=V2.1 EOF6.2 首次启动配置脚本
#!/bin/bash# /opt/scripts/firstboot.sh# 首次启动初始化(通过systemd firstboot服务触发)MARKER="/etc/.firstboot_done"if[-f"$MARKER"];thenexit0# 已执行过fiecho"=== First Boot Initialization ==="# 1. 扩展rootfs分区到整个磁盘ROOT_DEV=$(findmnt /-oSOURCE-n)PART_NUM=$(echo"$ROOT_DEV"|grep-o'[0-9]*$')# resize2fs扩展ext4文件系统resize2fs"$ROOT_DEV"# 2. 生成SSH密钥ssh-keygen-A# 3. 设置时区ln-sf/usr/share/zoneinfo/Asia/Shanghai /etc/localtime# 4. 生成唯一设备IDecho"DEV_ID=$(cat/proc/sys/kernel/random/uuid)">/etc/device_id# 5. 启用核心服务systemctlenablemyapp.service systemctlenablesshd.servicetouch"$MARKER"echo"=== First Boot Done ==="reboot七、OTA远程升级方案
7.1 A/B分区切换方案
| 分区 | 内容 | 状态 | |------|------|------| | boot_a | 内核槽位A | 当前活跃 | | boot_b | 内核槽位B | 备用 | | system_a | 根文件系统A | 当前活跃 | | system_b | 根文件系统B | 备用 |升级流程:
#!/bin/bash# /opt/scripts/ota_update.shNEW_FW=$1# 新固件包路径# 1. 确定当前活跃槽位ACTIVE_SLOT=$(bootctl status|grep"Current Slot"|awk'{print $3}')if["$ACTIVE_SLOT"="a"];thenTARGET_SLOT="b"elseTARGET_SLOT="a"fiecho"Active:$ACTIVE_SLOT, Target:$TARGET_SLOT"# 2. 写入新固件到非活跃槽位fastboot flash boot_${TARGET_SLOT}${NEW_FW}/kernel.img fastboot flash system_${TARGET_SLOT}${NEW_FW}/rootfs.img# 3. 设置下次启动使用新槽位bootctl set_active$TARGET_SLOT# 4. 重启,尝试新固件reboot# 5. 如果新固件正常,应用确认(在应用中调用)# bootctl mark_successful# 如果启动失败,bootloader自动回滚到旧槽位7.2 增量升级
如果只是应用层更新,不需要刷整包,用差分包更高效:
# 生成差分包bsdiff old_app.tar.gz new_app.tar.gz app_patch.diff# 设备端应用差分bspatch old_app.tar.gz new_app.tar.gz app_patch.diff八、烧录常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| dd写入失败 | SD卡写保护/损坏 | 检查写保护开关,换卡 |
| 设备不启动 | Bootloader未烧录或地址错误 | 检查偏移地址,确认idbloader写入位置 |
| 内核启动后挂死 | DTB不匹配 | 确认DTB与硬件版本一致 |
| rootfs挂载失败 | 文件系统损坏 | 重新格式化分区,检查镜像完整性 |
| 烧录后分区大小不对 | 未做首次扩展 | 执行resize2fs扩展分区 |
| fastboot无法连接 | USB驱动/线缆 | 换数据线,检查udev规则 |
8.1 验证镜像完整性
# 生成校验和sha256sum uboot.img kernel.img rootfs.img>checksums.sha256# 设备端校验sha256sum-cchecksums.sha256# uboot.img: OK# kernel.img: OK# rootfs.img: OK九、量产部署Checklist
| 序号 | 检查项 | 状态 |
|---|---|---|
| 1 | 固件版本号已更新并打git tag | ☐ |
| 2 | 所有镜像SHA256校验通过 | ☐ |
| 3 | SD卡/eMMC烧录成功,无报错 | ☐ |
| 4 | 设备首次启动正常,串口有输出 | ☐ |
| 5 | 首次启动脚本执行完成 | ☐ |
| 6 | rootfs分区已扩展到完整容量 | ☐ |
| 7 | 网口可通信,IP获取正常 | ☐ |
| 8 | 串口数据收发正常 | ☐ |
| 9 | 看门狗功能验证通过 | ☐ |
| 10 | 应用程序自动启动且运行正常 | ☐ |
| 11 | MAC地址/序列号已烧录 | ☐ |
| 12 | 重启3次无异常 | ☐ |
| 13 | 出库信息登记(序列号/版本/日期) | ☐ |
量产部署的核心是标准化和可重复。每一步都有明确的检查项,每一台设备都走同样的流程,不依赖人的经验,这样才能保证100台设备100台稳定。