基于 Yocto 与 SWUpdate 的双端离线升级实战:设备端 Web 服务与控制端程序完整落地

Tina R329 swupdate OTA升级步骤 第一步: 编译路径执行make menucinfig和make ota_menucinfig中分别选中: (1)Allwinner > swupdate (2)Allwinner > swupdate > Swupdate Settings > General Configuration > MTD support (3)Allwinner > swupdate > Image Handlers > ubivol 第二步: sys_config.fex中的s 阅读详情

📺 B站:博主个人介绍

📘 博主书籍-京东购买链接*:Yocto项目实战教程

📘 加博主微信,进技术交流群: jerrydev


基于 Yocto 与 SWUpdate 的双端离线升级实战:设备端 Web 服务与控制端程序完整落地

在很多嵌入式产品中,升级并不是“把一个镜像拷过去再执行命令”这么简单。真正进入量产阶段之后,工程上要同时面对四个问题:第一,升级包必须可校验、可追踪;第二,升级过程必须能抗断电、抗中断;第三,升级失败之后要能回退,不能把设备刷成“半死不活”的状态;第四,控制升级的那一端和真正执行安装的那一端,往往并不是同一台设备。SWUpdate 的定位恰恰不是一个“只会解包写盘”的命令行工具,而是一个可从本地介质或网络执行更新、并且能够和不同安装处理器、不同启动链路配合的升级框架;它既支持单拷贝,也支持双拷贝,还支持内置 WebServer 进行远程上传。([Sbabić][1])

如果把视角再拉高一点,会发现这类方案在市场上的成熟做法其实已经非常收敛。SWUpdate 的最佳实践强调,升级成功不是“文件写完了”,而是“更新代理写入成功、设备完成重启、Bootloader 能切到新版本、新版本完成自检并确认事务结束”;Mender 在设备支持文档中也明确要求,想做健壮的系统升级,Bootloader 必须参与分区选择和回滚逻辑;RAUC 则把“写入 inactive slot、由系统在启动后确认状态”作为一套明确的 slot 机制来实现。换句话说,行业里真正成熟的方案,不管表面上叫 SWUpdate、Mender 还是 RAUC,底层都在遵守同一套工程原则:写备用槽、让 Bootloader 参与事务、把控制面和安装面分离。 ([Sbabić][2])

本文就按这个成熟路径,给出一套完整的双端实战方案。为了避免把讨论绑死在某一个具体产品形态上,本文统一采用两个角色来描述:一端叫设备端,也就是被升级端;另一端叫控制端,也就是用户操作端、升级发起端。设备端运行 SWUpdate,并打开内置 WebServer;控制端不直接“安装镜像”,而是负责上传 .swu、监听进度、展示结果、必要时触发重启。这个方案适合局域网离线升级、主从架构升级、便携式主控终端控制从属节点升级,也适合未来继续扩展成一对多的批量升级架构。SWUpdate 官方文档里甚至已经给出了 master / slaves 的 forwarder 模式:主设备作为唯一对外入口,把 SWU 转发给隐藏在局域网内的从设备。([Sbabić][3])

一、为什么这里不直接把 swupdate-client 装到控制端

很多人第一次做双端方案时,都会自然地想到一句话:设备端有 SWUpdate,那控制端是不是装一个 swupdate-client 就行?这在单机方案里有时成立,但在双机网络方案里通常不是最佳答案。原因很简单:SWUpdate 官方定义的外部程序接口,本质上是通过 Unix Domain Socket 和本机运行中的 SWUpdate 通信。文档明确写到,控制 socket 的默认路径在 /tmp/sockinstctrl 一类的位置,客户端先发 REQ_INSTALL,收到 ACK 之后再把整个镜像流送给 SWUpdate。也就是说,swupdate-client 的天然定位是“本机前端程序和本机 SWUpdate 守护进程之间的 IPC 工具”,而不是“跨网络控制另一台设备的远程升级客户端”。([Sbabić][4])

真正适合双端网络方案的,是 SWUpdate 内置 WebServer 这条路径。官方文档给出的接口很明确:设备端可以开放 POST /upload 来接收 .swu,开放 POST /restart 在配置允许时执行重启,并通过 WebSocket 向外发送 JSON 事件,事件类型包括 status、source、info、message、step,状态值则包括 IDLE、START、RUN、SUCCESS、FAILURE、DONE。这意味着控制端完全可以自己实现一个轻量级控制程序,专门负责上传包、观察升级过程和做用户交互,而不必强行把 swupdate-client 远程化。([Sbabić][4])

因此,本文的核心判断只有一句话:设备端负责安装,控制端负责控制。 这不是文字游戏,而是把“数据平面”和“控制平面”从一开始就拆开。设备端要保证包能校验、能落盘、能写 Bootloader 状态;控制端要保证用户看到的是完整的升级流程、错误原因和最终结果。这样做,系统结构会干净很多,后面无论你把控制端做成 CLI、Qt 前端、Web 前端还是后台服务,都不会改动设备端的核心安装链路。([Sbabić][4])

二、整套方案的总体结构

本文采用如下架构:

控制端
 ├─ 自定义升级控制程序
 ├─ 选择 .swu
 ├─ 上传到设备端
 ├─ 监听 WebSocket 进度
 └─ 展示成功 / 失败 / 日志

            │  局域网 HTTP / WebSocket
            ▼

设备端
 ├─ SWUpdate 守护进程
 ├─ 内置 WebServer
 ├─ /dev/standby 指向当前备用槽
 ├─ sw-description 描述安装行为
 ├─ Bootloader 环境变量切换启动目标
 └─ 重启后由新系统确认事务完成

这套方案的关键,不只是“能升级”,而是“升级语义正确”。SWUpdate 的最佳实践明确建议,在双拷贝模式下要么通过 selection/mode 指定备用槽,要么在启动时建立类似 /dev/standby 这样的链接,让 SWUpdate 永远对“当前备用副本”写入,而不是误写正在运行的活动槽。文档还明确指出,双拷贝 A/B 的 stand-by copy 要在运行前就识别好;如果不想依赖启动参数,也可以在运行时通过脚本或 Lua 去识别当前 rootfs。本文为了让工程尽快落地,采用的是更稳定、也更容易在远程 Web 模式下使用的办法:在设备端启动时建立 /dev/standby 符号链接,让 SWUpdate 的更新目标始终固定成 /dev/standby。 ([Sbabić][2])

把这个设计放到真实项目里,它的好处非常明显。控制端不需要知道设备此刻到底跑在 A 还是 B,也不需要在上传前给 SWUpdate 额外传 mode;它只管把同一个 .swu 上传过去。真正的槽位判断发生在设备端启动阶段,一次判断、全程复用。这比“控制端先查询活动槽,再决定发哪一个专用包”更优雅,也比“每次远程启动 SWUpdate 时临时拼 -e 参数”更适合守护进程常驻的产品。这个思路和 RAUC 的 inactive slot 选择机制在工程上是高度一致的:安装面应该有一套稳定的“写备用槽”机制,而不是把槽位逻辑散落在控制端 UI 里。 ([RAUC][5])

三、设备端:在 Yocto 中集成 SWUpdate WebServer

1. 加入 meta-swupdate

SWUpdate 官方推荐在 Yocto 体系里通过 meta-swupdate 集成。官方文档写得很直接:meta-swupdate 负责交叉编译 SWUpdate 应用,也负责生成复合 SWU 镜像;把它加进 bblayers.conf 时,还需要同时加入 meta-oe。文档还提到,它提供了一个 swupdate-image 用来生成 initrd/rescue 系统,但那是“救援系统”方向的用法;如果你做的是常规双端在线升级,更常见的做法是把 SWUpdate 运行时直接放进业务镜像。([Sbabić][6])

一个典型的 bblayers.conf 片段如下:

BBLAYERS += " \
    ${TOPDIR}/../poky/meta \
    ${TOPDIR}/../poky/meta-poky \
    ${TOPDIR}/../poky/meta-yocto-bsp \
    ${TOPDIR}/../meta-openembedded/meta-oe \
    ${TOPDIR}/../meta-openembedded/meta-python \
    ${TOPDIR}/../meta-swupdate \
    ${TOPDIR}/../meta-your-product \
"

这里额外把 meta-python 一并放进来,是因为后文会在控制端镜像中放一个 Python 写的升级控制程序;如果你的控制端不是 Python,也可以换成自己的技术栈。这个取舍属于项目实现,不影响 SWUpdate 的基础接入。

2. 把 SWUpdate 运行时直接装进设备端镜像

meta-swupdate 的 recipe 会把不同资源拆成多个包。GitHub 中的 swupdate.inc 可以看到,它会安装 swupdate.service、swupdate.socket、swupdate.sh,还会在启用 Mongoose 时安装默认的 10-mongoose-args 配置;与此同时,/www 目录中的 Web 资源也会被打包进去。默认的 Mongoose 参数文件内容是:

SWUPDATE_WEBSERVER_ARGS="-r /www ${SWUPDATE_MONGOOSE_EXTRA_ARGS:--p 8080}"

也就是说,只要编译时启用了 Mongoose 并把对应包装进镜像,默认文档根目录就是 /www,默认监听端口就是 8080。([GitHub][7])

因此,设备端镜像最直接的做法是在 image recipe 或 local.conf 中加入:

IMAGE_INSTALL:append = " swupdate swupdate-www"

这样,设备端启动后就具备三个核心能力:能运行 swupdate,能由 systemd 拉起守护进程,能提供 Web 界面和 Web API。swupdate-www 是否必须,取决于你是否需要默认 Web 资源;如果你只打算用 REST 和 WebSocket 而不打开 HTML 页面,可以按你的发行版做更细的裁剪,但在前期原型验证阶段,直接一起装进去通常更高效。([GitHub][7])

3. 在自己的 layer 中写 bbappend,不要直接改官方层

SWUpdate 最佳实践明确建议,在 OE/Yocto 中通过 swupdate_%.bbappend 加自己的 defconfig,而不是直接去修改官方层里的 recipe。官方还专门建议用 bitbake -c menuconfig swupdate 先检查配置,确认安全选项、Bootloader 接口和需要的 handlers。([Sbabić][2])

你的自定义层结构可以这样组织:

meta-your-product/
└── recipes-support/
    └── swupdate/
        ├── swupdate_%.bbappend
        └── files/
            └── defconfig

swupdate_%.bbappend 先写成最小版本:

FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

SRC_URI += "file://defconfig"

然后执行:

bitbake -c menuconfig swupdate

把你需要的配置导出成自己的 defconfig。官方最佳实践特别指出,默认配置对大多数项目并不合适,应该根据项目关闭不需要的接口和 handler,只保留真正需要的部分,同时强烈建议启用 Lua 以扩展运行时行为。([Sbabić][2])

4. defconfig 最少要关注哪些能力

由于不同版本的 SWUpdate 配置符号可能略有差异,最稳妥的方式永远是以你当前源码树中的 menuconfig 为准。不过从官方文档和集成行为出发,设备端至少要确认五类能力:WebServer/Mongoose、raw 镜像写入、Bootloader 环境处理、哈希/签名验证,以及你实际要用到的压缩或脚本能力。SWUpdate 最佳实践还特别强调:接口和 handler 不要贪多,但也不要忘了未来可能用到的能力。([Sbabić][2])

下面给一个“思路型”的配置片段,表达的是方向而不是某个版本下逐字不差的最终答案:

# 说明:以下仅示意需要打开的能力类别,具体符号名称以 menuconfig 为准
CONFIG_WEBSERVER=y
CONFIG_MONGOOSE=y
CONFIG_RAW=y
CONFIG_BOOTLOADERHANDLER=y
CONFIG_SIGNED_IMAGES=y
CONFIG_HASH_VERIFY=y
CONFIG_LUA=y

为什么要把 Lua 放进去?因为官方明确建议在复杂运行逻辑里优先使用 Lua,而不是 Shell。原因一是安全性更好,二是运行时不需要 fork/exec 外部 shell,资源占用和可控性都更优。([Sbabić][2])

四、设备端:让 /dev/standby 永远指向备用槽

前面已经说过,双端 Web 上传模式下,最怕的是控制端还没搞清楚当前槽位,设备端就把镜像写错地方。因此,本文采用 /dev/standby 方案。SWUpdate 最佳实践明确把它列为 A/B 方案的一种典型实现方式。([Sbabić][2])

1. 启动脚本

下面这个脚本的作用是在系统启动时识别当前根文件系统所在分区,并把 /dev/standby 链接到另一个分区。本文假设双槽是 /dev/mmcblk0p2 和 /dev/mmcblk0p3,你实际项目里替换成自己的分区即可。

#!/bin/sh
set -eu

rootdev="$(findmnt -n -o SOURCE /)"

mkdir -p /run

case "$rootdev" in
    /dev/mmcblk0p2)
        ln -sfn /dev/mmcblk0p3 /dev/standby
        echo "A" > /run/active_slot
        echo "/dev/mmcblk0p3" > /run/standby_slot
        ;;
    /dev/mmcblk0p3)
        ln -sfn /dev/mmcblk0p2 /dev/standby
        echo "B" > /run/active_slot
        echo "/dev/mmcblk0p2" > /run/standby_slot
        ;;
    *)
        echo "unsupported active root device: $rootdev" >&2
        exit 1
        ;;
esac

这个脚本本身不是 SWUpdate 官方自带的文件,但它符合官方最佳实践里“在启动时建立 stand-by link”的思路。它最大的价值是把“谁是备用槽”这个问题前移到系统启动阶段,从而让 sw-description 永远只面对一个稳定的设备路径:/dev/standby。([Sbabić][2])

2. systemd 服务

为了保证 SWUpdate 启动前这个链接已经准备好,可以做一个 one-shot 服务:

[Unit]
Description=Create standby slot symlink for SWUpdate
DefaultDependencies=no
After=local-fs.target
Before=swupdate.service
Requires=local-fs.target

[Service]
Type=oneshot
ExecStart=/usr/bin/standby-link.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

然后把脚本和 service 一起打进设备端镜像。这样每次开机,系统都会自动决定备用槽,并且在 swupdate.service 之前准备好环境。

五、设备端:编写 sw-description 与 SWU 打包 recipe

1. sw-description 的设计思路

sw-description 是 SWUpdate 的中心描述文件。官方文档强调,它应该是你的“升级概念”落地后的直接产物,而不是临时拼凑出来的一堆标签。它还特别建议:始终显式设置 type,始终为 artifact 设置 sha256,不要依赖默认安装器,也不要只相信 CPIO 自带的弱校验。([Sbabić][2])

在本文的双槽设计里,sw-description 的核心只有两件事:
第一,把根文件系统写入 /dev/standby;
第二,写入 Bootloader 变量,让下次启动切换到新槽,并进入“待确认”的事务状态。

SWUpdate 文档说明了两种修改 Bootloader 环境的方法:可以把一个文件作为 type = "bootloader" 的镜像交给 bootloader handler,也可以直接在 bootenv 段里列出要修改的变量。对于工程整洁性而言,bootenv 往往更直观。([Sbabić][8])

下面给出一个适合本方案的 sw-description 示例:

software =
{
    version = "@@DISTRO_VERSION@@";

    images: (
        {
            filename = "core-image-minimal-@@MACHINE@@.ext4.gz";
            device = "/dev/standby";
            type = "raw";
            compressed = "zlib";
            installed-directly = true;
            sha256 = "$swupdate_get_sha256(core-image-minimal-@@MACHINE@@.ext4.gz)";
        }
    );

    bootenv: (
        {
            name = "ustate";
            value = "1";
        },
        {
            name = "upgrade_available";
            value = "1";
        }
    );
}

这里有几个要点。installed-directly = true 表示启用流式安装,官方最佳实践把它称为 zero-copy 或 streaming mode;对于普通 rootfs 分区,这可以减少临时 RAM 占用。sha256 使用的是 meta-swupdate 提供的自动替换能力。ustate 则对应官方推荐的升级状态机变量,Bootloader 可以根据它判断“当前新系统是否处于待确认状态”。([Sbabić][2])

2. SWU 打包 recipe

官方文档给出了使用 inherit swupdate 打包 SWU 的标准方式:在 recipe 中声明 SRC_URI 包含 sw-description,声明 IMAGE_DEPENDS 和 SWUPDATE_IMAGES,然后给出每个镜像对应的文件后缀。([Sbabić][6])

一个最小可用的 recipe 可以这样写:

DESCRIPTION = "SWU package for product rootfs"
LICENSE = "MIT"

SRC_URI = "file://sw-description"

IMAGE_DEPENDS = "core-image-minimal"
SWUPDATE_IMAGES = "core-image-minimal"
SWUPDATE_IMAGES_FSTYPES[core-image-minimal] = ".ext4.gz"

inherit swupdate

如果你的产品镜像不是 core-image-minimal,只要把名字替换掉即可。构建时执行:

bitbake your-product-swu

生成出来的 .swu 就是控制端要上传的包。

3. 关于签名:不要只做“能升级”,要做“可信升级”

SWUpdate 的签名文档把问题说得非常透:如果你只签整个复合镜像,往往要等整包都加载完才能确认是否合法;如果你只签子镜像,攻击者又有可能把不同 release 的已签名子镜像重新拼起来。官方给出的更稳妥方案,是签名 sw-description,再对每个子镜像做哈希校验。这样,只有可信来源生成的 sw-description 会被接受,而 sw-description 里面又包含每个子镜像的哈希,从而保证 release 描述与实际交付内容是一一对应的。文档还明确说明,公钥应该安装在目标端,私钥则属于构建系统。([Sbabić][9])

因此,正式版本建议至少做到三件事:
构建机持有私钥;
设备端镜像内置公钥;
所有产线包都带 sw-description.sig 和每个 artifact 的 sha256。

如果你当前还处在验证阶段,可以先把签名留到第二阶段;但从产品演进角度看,这一步几乎是迟早要补上的。SWUpdate 甚至要求,一旦启用了 CONFIG_SIGNED_IMAGES,运行时不能随便关掉检查。([Sbabić][9])

六、控制端:编写真正可用的升级控制程序

1. 控制端程序应当做什么

既然设备端已经暴露出 POST /upload、POST /restart 和 WebSocket 事件,那么控制端程序的职责就很清晰了:
先建立 WebSocket 监听;
再通过 multipart/form-data 上传 .swu;
根据 WebSocket 返回的 status、step、message 事件刷新进度;
升级完成后,视策略调用 POST /restart;
重启后,再通过你的业务链路去确认新版本是否真正生效。

这套交互逻辑完全符合 SWUpdate 官方 Web 接口的设计:WebServer 用 REST 负责“触发动作”,用 WebSocket 负责“持续反馈过程”。([Sbabić][4])

2. 一个可直接运行的 Python 控制端示例

下面给出一个简化但可实际工作的控制端程序。它使用 requests 负责上传,使用 websocket-client 监听事件。你后续无论把它包成 CLI、Qt 调用层,还是服务进程,都可以在这个基础上继续扩展。

#!/usr/bin/env python3
import argparse
import json
import os
import sys
import threading
import time

import requests
import websocket


class SwuMonitor:
    def __init__(self, host: str, port: int):
        self.host = host
        self.port = port
        self.ws_url = f"ws://{host}:{port}/ws"
        self.last_status = "IDLE"
        self.failed = False
        self.finished = False

    def on_message(self, ws, message):
        try:
            data = json.loads(message)
        except Exception:
            print(f"[WS] invalid message: {message}")
            return

        msg_type = data.get("type")

        if msg_type == "status":
            status = data.get("status", "UNKNOWN")
            self.last_status = status
            print(f"[STATUS] {status}")
            if status in ("SUCCESS", "DONE"):
                self.finished = True
            elif status == "FAILURE":
                self.failed = True
                self.finished = True

        elif msg_type == "step":
            name = data.get("name", "")
            step = data.get("step", "?")
            number = data.get("number", "?")
            percent = data.get("percent", "0")
            print(f"[STEP] {step}/{number} {name} {percent}%")

        elif msg_type == "message":
            level = data.get("level", "")
            text = data.get("text", "")
            print(f"[MESSAGE][{level}] {text}")
            if str(level) == "3":
                self.failed = True

        elif msg_type == "info":
            print(f"[INFO] {data.get('source', '')}")

        elif msg_type == "source":
            print(f"[SOURCE] {data.get('source', '')}")

        else:
            print(f"[WS] {data}")

    def on_error(self, ws, error):
        print(f"[WS][ERROR] {error}")

    def on_close(self, ws, close_status_code, close_msg):
        print(f"[WS] closed: {close_status_code} {close_msg}")

    def run(self):
        self.ws = websocket.WebSocketApp(
            self.ws_url,
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close,
        )
        self.ws.run_forever()

    def start(self):
        t = threading.Thread(target=self.run, daemon=True)
        t.start()
        # 给 websocket 一点建立连接的时间
        time.sleep(1.0)


def upload_swu(host: str, port: int, swu_path: str):
    url = f"http://{host}:{port}/upload"
    with open(swu_path, "rb") as f:
        files = {"file": (os.path.basename(swu_path), f, "application/octet-stream")}
        resp = requests.post(url, files=files, timeout=3600)
    print(f"[HTTP] upload response: {resp.status_code}")
    print(resp.text)
    resp.raise_for_status()


def restart_device(host: str, port: int):
    url = f"http://{host}:{port}/restart"
    resp = requests.post(url, timeout=30)
    print(f"[HTTP] restart response: {resp.status_code}")
    print(resp.text)


def main():
    parser = argparse.ArgumentParser(description="SWUpdate remote control client")
    parser.add_argument("--host", required=True, help="device ip or hostname")
    parser.add_argument("--port", type=int, default=8080, help="swupdate web port")
    parser.add_argument("--file", required=True, help="path to .swu")
    parser.add_argument("--restart", action="store_true", help="restart after success")
    args = parser.parse_args()

    if not os.path.exists(args.file):
        print(f"SWU not found: {args.file}", file=sys.stderr)
        sys.exit(2)

    monitor = SwuMonitor(args.host, args.port)
    monitor.start()

    try:
        upload_swu(args.host, args.port, args.file)
    except Exception as e:
        print(f"[ERROR] upload failed: {e}", file=sys.stderr)
        sys.exit(3)

    while not monitor.finished:
        time.sleep(0.5)

    if monitor.failed:
        print("[RESULT] update failed", file=sys.stderr)
        sys.exit(4)

    print("[RESULT] update success")

    if args.restart:
        try:
            restart_device(args.host, args.port)
        except Exception as e:
            print(f"[WARN] restart request failed: {e}", file=sys.stderr)


if __name__ == "__main__":
    main()

这个程序对应的交互逻辑,和官方文档是对得上的:上传用 POST /upload,事件监听用 WebSocket,状态由 status 和 step 等事件表示。([Sbabić][4])

3. 控制端在 Yocto 中的打包方法

如果控制端系统本身也是 Yocto 发行版,可以把这个程序单独做成一个 recipe,安装到镜像里。下面给一个简化版本:

SUMMARY = "SWUpdate control client"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835a3157e3b7f8a5fef18df2d5f2d9b"

SRC_URI = "file://swu-control.py"

S = "${WORKDIR}"

RDEPENDS:${PN} += " \
    python3-core \
    python3-requests \
    python3-websocket-client \
"

do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/swu-control.py ${D}${bindir}/swu-control
}

然后在控制端镜像里加入:

IMAGE_INSTALL:append = " swu-control"

这样,控制端开机后就有一个现成的升级程序可用。若你后续想做图形界面,可以让 Qt 或 WebView 前端去调用这个 CLI,也可以把它改造成一个常驻 D-Bus 服务或本地 HTTP 服务,供前端间接调用。

4. 实际调用方式

上传一份升级包并在成功后重启设备端:

swu-control --host 192.168.8.2 --port 8080 --file product.swu --restart

如果你还在早期验证阶段,甚至可以先不用写 Python,直接用官方示例里的 curl 命令测通链路:

curl -F file=@product.swu http://192.168.8.2:8080/upload

这是官方文档明确给出的调用方式,非常适合做第一阶段连通性验证。([Sbabić][4])

七、Bootloader、回滚与事务确认:真正可靠升级的关键

真正把嵌入式升级做成熟,难点不在“上传成功”,而在“失败时仍能安全恢复”。SWUpdate 最佳实践把这件事解释得很清楚:升级不是一个瞬间动作,而是一个事务。这个事务至少跨越四个阶段:更新代理写入、设备重启、Bootloader 切换、用户空间确认。文档还定义了两个重要变量:recovery_status 用于表示升级过程是否被中断或失败,ustate 用于表示新版本是否处于待验证状态;Bootloader 应该根据这些变量和启动次数决定是否回退。([Sbabić][2])

这也是为什么我前面一直强调,sw-description 里不要只写“把 rootfs 刷进去”。如果 Bootloader 完全不知道这次升级发生了什么,那么一旦新系统启动失败,系统就失去了自动回退的抓手。反过来,只要 Bootloader 和 SWUpdate 对事务变量有共识,即使升级中途断电,Bootloader 也能识别“上一次事务没有完成”,然后选择继续走恢复路径或回退路径。SWUpdate 官方文档明确说明,在双拷贝场景下,如果断电发生在安装过程中,由于活动槽还没切换,系统仍然可以回到旧副本启动。([Sbabić][10])

因此,本文推荐的最小 Bootloader 协议是这样的:

  1. 设备端当前运行在槽 A,/dev/standby 指向槽 B。
  2. SWUpdate 把新 rootfs 写入槽 B,并把 ustate=1、upgrade_available=1 一类变量写入环境。
  3. Bootloader 下次启动时检测到 ustate=1,于是切换到槽 B。
  4. 新系统启动成功后,在用户空间做完自检,再把 ustate=0 或等效确认变量写回环境。
  5. 若新系统没有在规定次数内完成确认,Bootloader 则判定新软件有问题,自动切回槽 A。

这套流程和 Mender 文档中“Bootloader 负责选择恰当分区并提供回滚能力”的要求,以及 RAUC 对 inactive slot 的使用方式,本质上是同一类稳健更新思想。([docs.mender.io][11])

八、成熟方案为什么都在强调安全与最小依赖

SWUpdate 最佳实践里有两句非常值得反复体会。第一句是:升级要尽量减少对业务应用的依赖。原因是业务应用本身也会被更新,假如你把升级流程深度绑在业务应用上,一次应用 bug 就可能把升级能力本身也拖死。第二句是:总是打开 sha256 校验,总是显式设置 type,总是优先保证原子性。这几条看似琐碎,实际上都是为了让现场设备在“系统已经不太正常”的情况下,仍然保留恢复能力。([Sbabić][2])

从安全角度看,正式量产时建议最少做到四层防护。第一层是签名 sw-description,并让设备端只信任构建系统签出的包;第二层是所有 artifact 带 sha256,避免 release 描述和真实内容被拼接篡改;第三层是 Bootloader 环境以原子方式写入,不要在 postinstall 脚本里反复 fw_setenv 多个变量;第四层是对 Web 接口加控制手段,例如局域网白名单、会话令牌或上层认证,否则任何能访问设备端 8080 的主机理论上都能发起升级。前两层来自 SWUpdate 的签名与校验机制,第三层来自它对 Bootloader 环境原子写入的最佳实践,第四层则是任何生产级远程控制接口都绕不开的工程常识。([Sbabić][9])

如果你的产品后续还需要进一步加强保护,SWUpdate 的 Yocto 集成还支持对 SWU 内容进行对称加密,并允许在 recipe 里通过 SWUPDATE_AES_FILE、SWUPDATE_IMAGES_ENCRYPTED[...] 等变量控制 artifact 加密;文档也说明了,对 sw-description 本身做加密是可行的,但前提是 SWUpdate 编译时打开了相应选项。是否现在就启用加密,要看你的产品是否已经进入“必须防止离线包被直接分析”的阶段。([Sbabić][6])

九、测试路径、常见问题与演进方向

这类方案建议按“三层递进”来验证。第一层只验证链路:设备端 swupdate.service 是否启动,8080 是否监听,控制端 curl -F 是否能成功触发上传。第二层验证安装:上传后 WebSocket 是否能收到 START、RUN、SUCCESS 或 FAILURE,目标槽位内容是否真的被更新。第三层验证事务:设备端重启后,Bootloader 是否切到新槽,新系统是否能完成确认,如果人为制造失败,是否真的能自动回退。SWUpdate 的官方 Web API 事件模型已经把升级过程拆成了足够清晰的状态和步骤字段,因此这套测试并不难做。([Sbabić][4])

实战中最常见的错误,往往不是 SWUpdate 本身,而是“外围工程没闭环”。例如,很多人能成功生成 .swu,但 Bootloader 没实现事务变量,所以升级后切不过去;也有人把 A/B 槽都写了,结果看起来“升级成功”,本质上却把回滚能力一并覆盖掉;还有人把控制程序做成了直接 SSH 执行一串命令,早期调试没问题,一到产品化阶段就遇到权限、稳定性和状态同步问题。SWUpdate 最佳实践其实已经把这些坑提前点出来了:写升级概念、做风险分析、减少依赖、不要破坏原子性、规划救援系统。([Sbabić][2])

当你的系统以后从“一台控制端对一台设备端”升级到“一台控制端对多台从设备”时,可以进一步研究 SWUpdate 的 swuforward handler。官方明确说明,这个 handler 就是为 master / slaves 场景准备的:主设备作为唯一对外接口,把嵌入式 SWU 转发给运行着 SWUpdate WebServer 的从设备。换句话说,本文这套“控制端自定义程序 + 设备端 WebServer”的模型,并不是死路,而是恰恰可以自然过渡到更大规模的主从升级架构。([Sbabić][3])

十、结语:把升级做成系统能力,而不是一次性脚本

到这里,整套方案已经清楚了:
设备端在 Yocto 中集成 meta-swupdate,安装 swupdate 与 swupdate-www,打开 Mongoose/WebServer,借助 systemd 常驻运行;
设备端启动时建立 /dev/standby,确保升级始终写向备用槽;
sw-description 只描述两件事:如何把新镜像写到备用槽,如何让 Bootloader 进入待验证事务;
控制端不去“远程运行 swupdate-client”,而是自己实现一个轻量控制程序,调用 POST /upload 上传 .swu,通过 WebSocket 监听过程,必要时再发 POST /restart;
Bootloader 负责最后一环:选择新槽启动、统计失败次数、完成自动回退。

这套架构之所以值得长期使用,不是因为它“看起来合理”,而是因为它和 SWUpdate 官方推荐的启动方式、Web API、A/B 设计思路、签名机制,以及更广义上的 Mender/RAUC 稳健升级原则,是彼此对齐的。真正成熟的升级系统,从来不是一条命令,也不是一个按钮,而是一条从构建系统、包签名、设备端守护进程、Bootloader 状态机到控制端交互程序都闭合起来的链路。只要这条链路搭好了,后面你无论增加版本控制、升级审批、多设备管理,还是把离线局域网升级扩展成云端统一编排,整体思路都不需要推倒重来。


📺 B站:博主个人介绍

📘 博主书籍-京东购买链接*:Yocto项目实战教程

📘 加博主微信,进技术交流群: jerrydev


arm linux ota,嵌入式Linux的OTA更新,现有更新系统的比较 原标题:嵌入式Linux的OTA更新,现有更新系统的比较在基本原理和实现文章中,我们讨论了嵌入式Linux更新系统的基础和实现。在这里,我们讨论了一些现成的开源更新系统,这些系统现在可以与您的Embedded Linux项目集成。此更新系统非常专业,可以立即使用。它使用的双rootfs更新系统与上一篇文章中描述的系统非常相似。它与U-Boot紧密集成,以便在非引导映像的情况下进行回退。Mender... 阅读详情

相关推荐

swupdate(一)

2.配置选项:m menuconfig/m ota_menuconfig 2.1选上swupdate包,主系统和recovery系统均需要 Allwinner <*> swupdate 一般默认即可,特殊调整,比如剪裁网络部分 2.2都需要选上 wifimanager-demo,主系统和recovery系统都需要 Allwinner > wifimanager [*] Enable wifimanager daemon support ...

qq_34738528的博客 2628

迅为i.mx6q开发板OTA 远程升级

1 SWUpdata概述 SWUpdata是一个开源项目,该项目可以从存储介质或网络为嵌入式设备更新嵌入式系统。但是,它应该主要被视为一个框架,可以轻松地将更多协议或安装程序(在SWUpdate 中称为处理程序)添加到应用程序中。 一种用例是从外部本地媒体更新,如USB或SD 卡。在这种情况下,更新是在没有操作员干预的情况下完成的:它被认为是“一键更新”,软件在重置时只需按下一个键(或以任何目标可以识别的方式),自动进行所有检查。最后,更新过程只向操作员报告状态(成功或失败)。 ...

Chihiro_S的专栏 1251

swupdate:嵌入式系统的软件更新

SWUpdate-嵌入式Linux设备的软件更新 是一种Linux Update代理,旨在提供一种有效,安全的方式来在现场更新嵌入式Linux系统。 SWUpdate支持本地和OTA更新,多种更新策略,并且在设计时考虑了安全性。 入门 要开始使用SWUpdate,建议您查看文档并为一个评估板构建(或在主机上运行SWUpdate作为第一个概述)。 产品特点 SWUpdate是一个具有许多可配置选项的框架: 设备所有组件的更新(rootfs,内核,引导程序,微控制器固件) 在嵌入式媒体上安装(eMMC,SD,原始NAND,UBIFS,NOR和SPI-NOR闪存) GPT和MBR分区表的分区程序 允许在多个设备上传送单个图像 流模式,没有工件的临时副本 多个接口(本地和OTA)以获取软件 本地存储(USB等) 集成式Web服务器 集成到REST客户端连接器以更新车队。 远程服务器下载 以图像,压缩的tarball等形式交付的软件。 允许自定义处理程序通过自定义协议安装FPGA固件和微控制器固件。 Delta更新基于librsync。 故障安全和原子更新 Lua解释器可根据您

【亲测免费】 SWUpdate 软件更新工具使用教程

SWUpdate 软件更新工具使用教程 1. 项目介绍 SWUpdate 是一个用于嵌入式 Linux 系统的软件更新代理,旨在提供一种高效且安全的更新方式。它支持本地和 OTA(Over-The-Air)更新,并设计了多种更新策略以确保系统的安全性和稳定性。SWUpdate 支持更新设备的所有组件,包括根文件系统、内核、引导加载程序和微控制器固件。此外,它还支持多种存储介质(如 eMMC、SD ...

gitblog_00531的博客 2807

自动驾驶仿真(二)—— 基于PreScan与Simulink的联合仿真

自动驾驶仿真软件介绍二—— PreScan与Simulink联合仿真1. Prescan仿真软件2. Prescan与Simulink联合仿真2. 1 Prescan和matlab软件安装2. 2 联合仿真配置3. 联合仿真实例3.1 一辆不按法规行驶的小车3.2 另一辆的正常行驶的小车3.3 视觉辅助摄像机3.4 传感器检测4. 系列学习视频教程5. 参考学习的书目教材 上一篇博文简单介绍了Carsim与Simulink联合仿真,接着这篇就再继续简单介绍另一款自动驾驶仿真软件Prescan,并且简单做个P

zhudj08的博客 1万+

HWSD全球土壤数据下载、处理与提取完整指南

全球尺度的科学研究与工程项目中,土壤数据是必不可少的基础底图,而HWSD(和谐世界土壤数据库)凭借其统一的分辨率与属性标准,成为农业区划、生态模型、土地利用变化和水资源评估等领域的通用数据源。深入理解其“栅格存编号、数据库存属性”的核心原理,是高效使用这套数据的关键。通过掌握MDB数据库结构、属性字段含义以及栅格与属性表的关联方法,研究者可以将原始数据转化为可用的研究底图。无论你是需要提取特定区域的土壤属性,还是进行面积统计与点位采样,合理的处理流程都能显著提升工作效率。本文从下载注册开始,一步步梳理数据清

weixin_29056781的博客 313

SWUpdate 项目使用教程

SWUpdate 项目使用教程 1. 项目目录结构及介绍 SWUpdate 是一个用于嵌入式 Linux 系统的软件更新代理。以下是项目的目录结构及其介绍: swupdate/ ├── bindings/ # 绑定文件 ├── bootloader/ # 引导加载程序相关文件 ├── ci/ # 持续集成相关文件 ├── configs...

gitblog_00499的博客 1219

三角洲行动交易行自动化脚本

DFMarketBot - 三角洲行动自动交易助手 DFMarketBot是一款专为三角洲行动游戏设计的交易行自动化工具,通过OCR识别和鼠标模拟实现自动补卡、补子弹功能。项目采用Python开发,支持Windows平台,主要功能包括: 自动检测并购买指定物品(房卡/子弹) 价格监控与智能购买(低于设定价格自动下单) 自定义购买数量与次数限制 支持窗口/无窗口两种游戏模式 使用前需安装Tesseract OCR引擎并配置游戏为1920*1080分辨率。通过JSON文件可灵活配置监控物品清单、理想价格和购买策

愤怒的阿木木的博客 3万+

嵌入式Linux离线升级实战:Yocto+SWUpdate双端架构详解

嵌入式设备的固件升级是保障系统长期稳定运行的关键能力,尤其在无法连接公网的内网或离线环境中,如何安全可靠地完成系统更新成为工程难点。A/B双副本分区机制配合开源框架SWUpdate,可在升级失败时自动回退,大幅降低现场运维风险;Yocto项目则提供了高度定制化根文件系统的构建能力,确保设备镜像精简且可复现。SWUpdate支持签名校验、硬件兼容性检查和灵活的双端交互,可实现可控、防呆、可观测的升级流程。该方案适用于工业机柜、产线设备等嵌入式Linux场景,能有效解决设备部署后无法上云带来的升级难题。文章以实

weixin_29189381的博客 287

Android存储空间不足下,APP出现奔溃等情况

版权声明:本文为博主原创文章,未经博主允许不得转载。 1. 问题起因     实时录制视频,使用getExternalCacheDir()函数返回的路径作为文件缓存区 2. 问题产生     系统在空间不足的情况下第一清除getExternalCacheDir和getCache文件下的缓存,导致正在录制的视频出现Error。 3. 解决方案     第一

OverseasAndroid的博客 4158

Qualcomm AI Engine Direct SDK(QNN)的使用介绍

Qualcomm AI Engine Direct SDK,高通新的AI模型推理框架,又名QNN,为AI开发提供统一API的底层框架。通过这个SDK,可以在高通的 CPU/GPU和HTP上对AI模型进行性能加速。2.Qualcomm AI Engine Direct SDK的公开英文文档。1.Qualcomm AI Engine Direct SDK的下载和安装。

u013250861的博客 6970

Yocto集成SWUpdate实现离线双端升级架构与回滚方案

嵌入式设备的系统升级是产品落地后无法回避的工程难题。在工业现场等离线环境中,云端OTA往往因网络隔离而失效,本地化、可回滚的离线升级方案成为关键。Yocto构建系统为定制Linux发行版提供了灵活的基础,而SWUpdate作为开源升级框架,通过A/B双副本分区和U-Boot启动切换机制,能够实现安全可靠的软件更新。本文围绕设备端Web服务与控制端工具的双端架构,讲解如何集成SWUpdate、设计分区布局、制作签名SWU包,并分享实测故障复盘,帮助工程团队快速落地离线升级闭环。

weixin_42696271的博客 261

基于RK3588与昇腾310B的异构K8s集群部署与NPU统一调度实战

本文介绍了基于RK3588与昇腾310B的异构K8s集群部署方案,通过HAMi+Volcano实现NPU统一调度。方案包含硬件配置、操作系统准备(银河麒麟/欧拉)、容器运行时部署及K8s集群搭建等关键步骤,支持多厂商NPU设备的统一抽象和精细化切分,为边缘AI计算提供高效资源管理能力。

huanghm88的专栏 280

【亲测免费】 SWUpdate 教程:嵌入式系统的软件更新框架

SWUpdate 的源代码仓库在 [GitHub](https://github.com/sbabic/swupdate.git) 上。典型的目录结构如下: - `src/` - 主要的源代码,包括核心组件和服务。 - `core/` - 更新引擎的核心部分。 - `fetchers/` - 定义了不同的数据获取策略(如HTTP下载)。 - `parser/` - 处理更...

gitblog_00871的博客 1350

大语言模型原理与工程实践:行业大语言模型的评测:以金融行业大语言模型为例

大语言模型、金融行业、模型评测、自然语言处理、深度学习、Transformer、BERT、GPT 1. 背景介绍 近年来,大语言模型(Large Language Model,LLM)在自然语言处理(Natural Language Processing,NLP)领域取得了突破性的进

AI大模型应用之禅 136

CloudCompare 二次开发(35)——提取多条直线

使用CloudCompare与PCL编程实现的多条空间直线的提取。

点云侠的博客 436

使用SWUpdate 软件进行程序升级

SWUpdate 官方说明文档: https://zqb-all.github.io/swupdate/swupdate.html /*********************************** *@file *@brief *@author *@e-mail: *@company *@date ************************************/ #include "MainWindow.h" #include "ui_MainWindow.h" #includ.

Believeshuifei的博客 5465

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

swupdate linux ota故障安全升级方案

swupdate是一个基于嵌入式的Linux平台的升级服务框架程序,它提供了分区升级,文件升级,差分升级(补丁应用)功能,并提供了开放接口,方便用户添加自定义升级处理函数。swupdate提供了故障安全的升级方案。支持Recovery方案和A/B系统方案来保证断电等异常情况下能保证升级正常。它提供了完整性,签名验证,固件加密等功能,保证了升级固件的安全性和私密性。swupdate 采用cpio的方式进行归档,第一文件是描述文件,默认名称为sw-description。

qq_16810885的博客 5953

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

【亲测免费】 SWUpdate 开源项目教程

SWUpdate 是一个针对嵌入式 Linux 设备的软件更新代理,旨在提供一种高效且安全的现场更新嵌入式 Linux 系统的方法。SWUpdate 支持本地和 OTA(Over-The-Air)更新,多种更新策略,并且设计时考虑了安全性。 ## 项目快速启动 ### 环境准备 在开始之前,确保你的系统已经安装了必要的构建工具和依赖项。以下是一个基本的安装命令示例: ```bash sud...

gitblog_00435的博客 883

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

如何快速掌握嵌入式系统OTA更新:SWUpdate终极指南

SWUpdate是一款专为嵌入式系统设计的开源OTA(空中下载)更新工具,它能够帮助开发者和工程师轻松实现嵌入式设备的软件更新。无论是智能家居设备、工业控制单元还是汽车电子系统,SWUpdate都能提供可靠、安全的更新解决方案。本文将为您提供一个全面的SWUpdate学习指南,帮助您快速掌握嵌入式系统OTA更新的核心技术和实践方法。 ## SWUpdate简介:嵌入式系统的OTA更新利器 🚀

gitblog_00219的博客 401

嵌入式软件更新完整教程:3步掌握SWUpdate开源框架

SWUpdate是一个专为嵌入式系统设计的开源软件更新框架,它为开发者提供了一套完整的嵌入式系统软件更新解决方案。无论您是新手还是经验丰富的工程师,本教程都将帮助您快速理解这个强大工具的核心概念和使用方法。 ## SWUpdate开源框架的核心架构解析 SWUpdate采用模块化设计,整个系统由多个功能模块协同工作。主要的核心组件包括: - **核心引擎** (core/) - 负责更新流程

gitblog_00250的博客 395

SWUpdate快速入门:30分钟搭建嵌入式系统更新环境

SWUpdate是一款专为嵌入式系统设计的高效软件更新工具,能够帮助开发者快速实现设备固件的安全可靠升级。本文将带你在30分钟内完成嵌入式系统更新环境的搭建,掌握SWUpdate的核心功能与基本使用方法。 ## 📋 准备工作:环境与依赖 在开始前,请确保你的开发环境满足以下要求: - Linux操作系统(推荐Ubuntu 20.04或更高版本) - 基本编译工具链(gcc、make等) -

gitblog_00748的博客 547

【亲测免费】 SWUpdate安装与配置完全指南

**SWUpdate** 是一个面向嵌入式Linux系统的软件更新工具,旨在提供一种高效且安全的方法来现场更新嵌入式设备的软件,包括根文件系统、内核、引导加载器以及微控制器固件等。此项目高度灵活,支持本地及Over-The-Air(OTA)更新,并注重安全性设计。SWUpdate由C语言为主要开发语言,同时利用Lua脚本增强其扩展性。 ## 关键技术和框架 - **更新策略**:支持多种更新方...

gitblog_07976的博客 1170

嵌入式linux设备的升级实现原理

快速原型(你能马上上手):按上面的 “主机端 bsdiff + 设备端 agent 接收 → bspatch → 写入 inactive → 修改 U-Boot env → reboot → agent commit/rollback” 做一个 POC。这个路径对你目前“USB 升级 + 想做差分”是最直接的。中长期产线/运维方案:当 POC 稳定后,把 agent 替换成 RAUC(或 SWUpdate)并在产品上做签名管理、服务器端部署(如果需要集中 OTA)。RAUC 比较轻量;

m0_57249200的博客 1349

推荐使用:SWUpdate - 嵌入式Linux设备软件更新利器

在物联网时代,对嵌入式系统的远程和安全更新是至关重要的。**SWUpdate** 正是一款专为嵌入式Linux设备设计的高效、安全的软件更新工具。它提供了本地和远程在线(OTA)更新功能,支持多种更新策略,并且在设计时充分考虑了安全性。 ## 项目介绍 ![SWUpdate Logo](https://raw.githubusercontent.com/sbabic/swupdate/mast...

gitblog_00062的博客 1588
上一篇: RK3588 升级方案到底该怎么选?把 SWUpdate、updateEngine、A/B、本地离线升级一次讲透
下一篇: 从一行命令到完整 OTA 框架:把 SWUpdate 讲清楚,也讲清楚它为什么常常比 Mender 更适合自定义嵌入式项目
嵌入式孙老师
博客等级 码龄7年 5686粉丝 898原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值