当前位置: 首页 > news >正文

linux 差分升级简介

好问题 👍
你已经知道了“整包升级”,现在想搞 差分升级。我来给你系统讲解下:


一、什么是差分升级?

👉 定义
差分升级(Delta Update / Binary Patch)就是:
不传完整固件,而是只传“旧版本和新版本之间的差异”,目标机利用差分包 + 旧版本数据,重构出新版本。

举个形象的例子:

  • 整包升级:直接把 整本书的新版本寄给你。
  • 差分升级:只寄给你一份 “勘误表”,你在旧版书上改一改,就得到了新版。

二、为什么要差分升级?

  1. 节省带宽
    比如 rootfs 有 1GB,但新版本只改了几十 MB 文件。差分包可能只有 20~50 MB。
  2. 节省升级时间
    包小 → 下载快 → 升级过程短。
  3. 适合远程 OTA / 弱网环境

缺点:

  • 生成与应用差分需要额外计算,升级流程比整包复杂。
  • 必须保证“基准版本”一致:设备必须正好是差分包要求的版本,否则 patch 失败(可能直接变砖)。

三、差分升级的实现方式

差分升级分为两大类:

1. 文件级差分

  • 针对文件系统内容,比较 文件 A(old) vs A(new),生成差分(如 rsync / bsdiff / xdelta)。
  • 应用时逐文件 patch。
  • 特点:更灵活,适合只更新应用层(例如 /usr/bin/xxx 程序)。

工具:

  • rsync 协议(增量同步)
  • bsdiff/bspatch(对单文件做二进制差分)
  • xdelta(对任意文件做差分,效率比 bsdiff 高)

适用:应用升级 / data 分区升级


2. 镜像级差分

  • 针对整块分区镜像(如 rootfs.img、boot.img),计算二进制差分。

  • 生成差分包 → 设备上应用 patch → 得到完整新镜像 → 写到 rootfs_b。

  • 常用工具:

    • imgdiff(Android OTA 用的工具,对 squashfs/ext4 镜像做差分)
    • xdelta3(通用二进制差分)

适用:系统级升级(rootfs/boot 分区)


四、在 RK3588S + Buildroot 的场景怎么做?

方案 1:整包升级(最简单)

  • 制作新 rootfs.img,通过 USB/OTA 整包替换。
  • 缺点:包大。

方案 2:镜像差分升级(推荐)

  1. PC 上用 xdelta3 对旧 rootfs.img 和新 rootfs.img 生成差分包:

    xdelta3 -e -s rootfs_old.img rootfs_new.img rootfs.patch
    
  2. OTA/USB 传输 rootfs.patch(远小于整包)。

  3. 设备端执行:

    xdelta3 -d -s rootfs_old.img rootfs.patch rootfs_new.img
    
  4. rootfs_new.img 写入 rootfs_b 分区,更新 U-Boot 环境变量,重启。

方案 3:文件级差分(适合应用层)

  • 如果只是应用更新,可以用 bsdiff 生成 app_old → app_new 的 patch,OTA 下发 patch,设备端用 bspatch 更新。
  • 优点:更细粒度,速度快。
  • 缺点:只能解决应用层,无法覆盖整个 rootfs 升级。

五、差分升级的注意事项

  1. 基准版本一致

    • 必须知道设备当前的 rootfs 版本号(比如 hash/版本号),否则 patch 会失败。
    • 通常要在升级包里写明“本 patch 适用于 v1.2 → v1.3”。
  2. 校验

    • 升级前后都要算 hash(SHA256/MD5)校验,保证新 rootfs.img 正确无误。
  3. 回滚机制

    • 差分升级一旦失败,设备要能从旧的 rootfs 分区启动,避免变砖。
  4. 性能问题

    • 生成差分包 → 在 PC 上做,问题不大。
    • 应用差分包 → 设备端 CPU/IO 开销要考虑。RK3588S 性能较强,完全可以。

六、落地建议(你的 Buildroot + USB 升级场景)

  • 如果你们是研发/量产阶段
    → 推荐整包 USB 线刷(省心,包大无所谓)。

  • 如果你们是最终产品 OTA/远程升级
    → 推荐 镜像差分升级(用 xdelta3 或 imgdiff),节省流量。

  • 如果你们的需求只是应用程序频繁更新
    → 推荐 应用层文件级差分,直接更新 /data 下的二进制或资源。


要不要我帮你出一份 基于 Buildroot + RK3588S 的差分升级 Demo(包含 PC 端生成 patch + 设备端应用 patch + USB 传输 + A/B 分区切换的完整流程脚本)?




太好了 — 我把 “Buildroot + RK3588S,走 USB 通道的差分升级(镜像级)”可复现实战 Demo 做成一套你能直接跑的说明和脚本。下面包含:

  • PC 端:如何 生成差分包(xdelta3) + 签名 + 打包成传输用的 .tar(或 .patch)。(thewiki.moe, Cool Fish In Glacier)
  • 设备端:如何 接收(USB Mass Storage / FunctionFS)、验证、合成新镜像、写入非活动槽(A/B)、更新 U-Boot 环境变量并重启(含失败回滚保护)。(opensource.rock-chips.com)
  • 必要的安全/校验步骤(hash 与签名)。
  • 注意事项与调试小技巧。
    如果你想要我把某部分改成“文件级差分(bsdiff)”或改用 imgdiff 的 Android 风格差分,也可以马上给你替换版本。(Android Git Repositories, XDA论坛)

1)设计前提(约定)

  • 设备分区(示例,实际请用你设备的分区表):

    • boot_a /dev/mmcblk0p5(当前)
    • boot_b /dev/mmcblk0p6(备用)
    • rootfs_a /dev/mmcblk0p7(当前)
    • rootfs_b /dev/mmcblk0p8(备用)
  • 设备当前运行 slot 由 U-Boot 环境变量 boot_slot 标识(ab)。设备上有 fw_setenv/fw_printenv(mtd-utils)可设置 U-Boot env。

  • 差分采用 xdelta3(镜像级差分),因通用且速度/体积较好。也可以换 imgdiff。(thewiki.moe, Cool Fish In Glacier)


2)PC 端:生成差分包(示例脚本)

文件:gen_rootfs_patch.sh(在 PC 上运行,假设已有 rootfs_old.imgrootfs_new.img

#!/bin/bash
set -eOLD_IMG="$1"
NEW_IMG="$2"
OUT_DIR="$3"   # e.g. ./out
PATCH_NAME="rootfs_${OLD_VER}_to_${NEW_VER}.xdelta"if [ -z "$OLD_IMG" ] || [ -z "$NEW_IMG" ] || [ -z "$OUT_DIR" ]; thenecho "Usage: $0 <rootfs_old.img> <rootfs_new.img> <out_dir>"exit 1
fimkdir -p "$OUT_DIR"
# compute version ids (sha256 short)
OLD_VER=$(sha256sum "$OLD_IMG" | awk '{print $1}' | cut -c1-12)
NEW_VER=$(sha256sum "$NEW_IMG" | awk '{print $1}' | cut -c1-12)PATCH_FILE="$OUT_DIR/rootfs_${OLD_VER}_to_${NEW_VER}.xdelta"
META_FILE="$OUT_DIR/metadata.txt"
BUNDLE="$OUT_DIR/rootfs_patch_${OLD_VER}_to_${NEW_VER}.tar"echo "Generating xdelta3 patch..."
xdelta3 -e -s "$OLD_IMG" "$NEW_IMG" "$PATCH_FILE"echo "Writing metadata..."
cat > "$META_FILE" <<EOF
old_hash=$(sha256sum "$OLD_IMG" | awk '{print $1}')
new_hash=$(sha256sum "$NEW_IMG" | awk '{print $1}')
old_ver=$OLD_VER
new_ver=$NEW_VER
tool=xdelta3
EOF# Sign the patch (optional but recommended)
echo "Signing patch with openssl (use your private key private.pem)"
openssl dgst -sha256 -sign private.pem -out "$PATCH_FILE.sig" "$PATCH_FILE"echo "Bundling..."
tar -cvf "$BUNDLE" -C "$OUT_DIR" "$(basename "$PATCH_FILE")" "$(basename "$PATCH_FILE").sig" "$(basename "$META_FILE")"echo "Done: $BUNDLE"

说明:

  • xdelta3 -e -s old new patch 生成 patch。应用用 xdelta3 -d -s old patch new。(thewiki.moe, Reddit)
  • 我用 SHA256 做版本识别(在 metadata.txt 内),设备端会比对 old_hash 与本地实际镜像一致才应用。

3)PC → 设备 的 USB 传输方式(两种选项)

A)USB Mass Storage(虚拟 U 盘):把 .tar 放到设备暴露的 U 盘里(用户拖拽),设备端守护进程检测到文件并启动升级。优点简单。

B)USB FunctionFS + 自定义协议:PC 端写一个小工具(libusb 或 pyusb)把文件上传,设备端通过 FunctionFS 收包(更自动/可加速)。

你之前有兴趣做 FunctionFS 文件传输,这一步可以直接沿用。两者设备端接收后的后续处理相同。


4)设备端:接收后自动化脚本(核心部分)

文件:apply_rootfs_patch.sh(设备 /usr/local/bin,需可执行)

#!/bin/bash
set -eWORK_DIR="/tmp/upgrade"
BUNDLE="$1"   # /mnt/usb/rootfs_patch_abc.tar
OLD_IMG="/tmp/rootfs_old.img"
NEW_IMG="/tmp/rootfs_new.img"
PATCH="/tmp/rootfs.patch"
META="/tmp/metadata.txt"
SIG="/tmp/rootfs.patch.sig"mkdir -p "$WORK_DIR"
cd "$WORK_DIR"# 1. extract bundle
tar -xvf "$BUNDLE"# 2. read metadata
source metadata.txt   # defines old_hash/new_hash# 3. locate current active slot and extract its image to compare
CURRENT_SLOT=$(fw_printenv -n boot_slot)
if [ "$CURRENT_SLOT" = "a" ]; thenCURRENT_ROOT="/dev/mmcblk0p7"   # rootfs_aINACTIVE_ROOT="/dev/mmcblk0p8"  # rootfs_b
elseCURRENT_ROOT="/dev/mmcblk0p8"INACTIVE_ROOT="/dev/mmcblk0p7"
fi# Optional: dump current partition to file for patch base (may need space)
echo "Dumping current rootfs for patch base (this may take time)..."
dd if="$CURRENT_ROOT" of="$OLD_IMG" bs=4M conv=sync status=progress# 4. verify old hash matches
CALC_OLD_HASH=$(sha256sum "$OLD_IMG" | awk '{print $1}')
if [ "$CALC_OLD_HASH" != "$old_hash" ]; thenecho "Base image mismatch: expected $old_hash got $CALC_OLD_HASH"exit 2
fi# 5. verify signature (optional)
openssl dgst -sha256 -verify public.pem -signature "$SIG" "$PATCH" || { echo "signature invalid"; exit 3; }# 6. apply xdelta3 patch to produce new image
xdelta3 -d -s "$OLD_IMG" "$PATCH" "$NEW_IMG"# 7. verify new hash
CALC_NEW_HASH=$(sha256sum "$NEW_IMG" | awk '{print $1}')
if [ "$CALC_NEW_HASH" != "$new_hash" ]; thenecho "New image hash mismatch"exit 4
fi# 8. write new image to inactive partition
echo "Writing new image to inactive partition $INACTIVE_ROOT"
# Ideally use block-level tools; dd with sync is used here
dd if="$NEW_IMG" of="$INACTIVE_ROOT" bs=4M conv=fsync status=progress# 9. set U-Boot env to boot inactive slot next
if [ "$CURRENT_SLOT" = "a" ]; thenfw_setenv boot_slot b
elsefw_setenv boot_slot a
fi
fw_setenv upgrade_available 1
fw_setenv bootcount 0# 10. reboot
echo "Rebooting to new slot..."
reboot

说明要点:

  • dd 把当前分区导出为 rootfs_old.img(作为 xdelta 的基线)。如果你的系统保存中了原始镜像(比如 /opt/images/rootfs_v1.img),就可以用它而不用再 dd。这样更快、也省空间。
  • 一定要做 old_hash 检查,避免把错的基线去 patch 导致产出垃圾镜像。
  • 写入 inactive partition 后用 fw_setenv 切槽并设置 upgrade_available 标志,设备下一次启动从新槽引导并做健康检测。U-Boot 的 bootcount/bootlimit 可配合实现失败回滚。(opensource.rock-chips.com)

5)U-Boot & 回滚策略(设备上)

U-Boot env 建议字段(示例):

boot_slot=a
upgrade_available=0
bootcount=0
bootlimit=3

引导流程(简述):

  1. U-Boot 读取 boot_slot,尝试从对应 slot 启动内核/根。
  2. 内核 / init 程序启动后负责做“健康自检”(比如关键服务能启动、应用返回 OK)并在成功时运行 fw_setenv upgrade_available 0; fw_setenv bootcount 0
  3. 如果内核没确认成功,下一次 U-Boot 会增加 bootcount;当 bootcount>=bootlimit 时,U-Boot 或启动脚本会把 boot_slot 切回另一槽,清 bootcount 并启动旧系统(回滚)。

该机制在很多嵌入式 OTA 系统常用(RAUC 也实现类似行为)。(RAUC)


6)安全性(必须做)

  • 签名:补丁包签名(示例用 openssl),设备端用公钥验证。
  • 双重 hash 校验:校验 old_hash(防误用)、校验 new_hash(防传输损坏)。
  • 写入前检查空间:确保设备有足够临时空间(xdelta 需要输出完整 new.img,可能临时占用与镜像相近空间)。
  • 保障救砖通道:始终保留 loader / maskrom 可刷写接口(用于无法回滚/救砖时)。(opensource.rock-chips.com, wiki.t-firefly.com)

7)性能与优化建议

  • 避免全量 dd 导出:如果设备上保存了“当前镜像的原始副本”,用它当基线会快很多。
  • 分块差分:若镜像很大,可把镜像分成固定大小 chunk(如 64MB),对每个 chunk 做 xdelta 差分并只更新变更的 chunk(复杂度高但流量更低)。Android 的 imgdiff 思路类似。(Cool Fish In Glacier, Android Git Repositories)
  • 并行写入:RK3588S 性能强,可在写入阶段尽量使用 bs=4Mconv=fsync 确保写入完整性。

8)调试流程(常见问题与排查)

  • old_hash 不匹配 → 检查你对基线的定位:是否设备已被改动?是否你拿到的基线版本不是设备当前版本?
  • xdelta 解包失败 → 确认 xdelta3 版本一致(不同版本产生的 patch 兼容性问题极少但可能)并确保没有传输损坏(用 sha256sum)。(thewiki.moe)
  • 写入后无法启动 → 先不要擦除旧槽,使用 loader 模式(rkdeveloptool)线刷来救砖;或使用 UART 查看内核日志定位问题。(opensource.rock-chips.com, Android Git Repositories)

9)如果你要我把 Demo 做成可直接运行的“整套工程”

我可以直接给你(一次性交付):

  1. gen_rootfs_patch.sh(PC 端生成与签名脚本)
  2. usb_send.sh(PC 端:把 bundle 复制到设备的 MassStorage 或通过 libusb 上传的示例)
  3. apply_rootfs_patch.sh(设备端自动化脚本,上面已给出核心)
  4. u-boot env 推荐模板与 fw_setenv 使用示例。
  5. 一个小 README,包含测试步骤(如何在开发板上做 end-to-end 测试)和常见失败恢复方法(如何用 rkdeveloptool 线刷回出厂镜像)。

你要不要我现在就把这些脚本整理成一个压缩包(把脚本全部写好并显示在这里),还是先只要 apply_rootfs_patch.sh 的可改进版本(比如支持 chunked patch / 进度显示)?另外告诉我你偏好哪种 USB 传输方式:Mass Storage(拖拽) 还是 FunctionFS(自动化 + 自写 PC 工具)

http://www.xdnf.cn/news/1337941.html

相关文章:

  • git增加ignore文件
  • 健康常识查询系统|基于java和小程序的健康常识查询系统设计与实现(源码+数据库+文档)
  • UEM终端防御一体化
  • 2026 济南玉米及淀粉深加工展:从原料到创新产品的完整解决方案
  • AI Agent与LLM区别
  • Jmeter接口测试之文件上传
  • QT的项目pro qmake编译
  • 【51单片机学习】AT24C02(I2C)、DS18B20(单总线)、LCD1602(液晶显示屏)
  • Prompt魔法:提示词工程与ChatGPT行业应用读书笔记:提示词设计全能指南
  • 智能制造加速器:某新能源车智慧工厂无线网络优化提升方案
  • 美国联邦调查局警告俄罗斯针对思科设备的网络间谍活动
  • Android APP防止应用被动态调试
  • 无监督学习(聚类 异常检测)
  • 北京JAVA基础面试30天打卡14
  • GO学习记录七——上传/下载文件功能,添加启动运行工具
  • 如何使用Prometheus + Grafana + Loki构建一个现代化的云原生监控系统
  • 20250821日记
  • leetcode 76 最小覆盖子串
  • leetcode-python-349两个数组的交集
  • 如何使用 DeepSeek 助力工作​
  • Seaborn数据可视化实战
  • 审美积累 | 界面设计拆分 | Redesign Health - Services 医疗页面设计
  • 记录一次el-table+sortablejs的拖拽bug
  • 打开或者安装Navicat时出现Missing required library libcurl.dll,126报错解决方法(libmysql_e.dll等)
  • 【运维进阶】if 条件语句的知识与实践
  • 【CS创世SD NAND征文】存储芯片在工业电表中的应用与技术演进
  • RabbitMQ:延时消息(死信交换机、延迟消息插件)
  • 深入理解Docker网络:从docker0到自定义网络
  • Python核心技术开发指南(001)——Python简介
  • NPM组件 @angular_devkit/core 等窃取主机敏感信息