在嵌入式设备的整个生命周期里,系统更新是一个至关重要的环节,却也充满了潜在风险。无论是修补安全漏洞、增加新功能,还是应对突发的设备故障,我们都需要一套既安全又可靠的更新方案。传统的手动刷机方式不仅效率低下,更可能在更新过程中因意外断电或数据错误导致设备“变砖”,这在量产产品中无疑是一场灾难。
而 RAUC (Robust Auto-Update Controller) 正是为解决这一核心痛点而生的轻量级更新框架。它作为一个运行在嵌入式设备上的客户端,能够可靠地控制新固件的安装流程。同时,它在主机端也提供了用于创建和操作更新包的工具。对于希望构建健壮OTA(Over-The-Air)功能的开发者而言,了解RAUC是提升嵌入式产品可维护性的关键一步。
设计哲学:可靠与安全至上
RAUC的设计源于解决众多嵌入式项目中定制化更新方案带来的重复工作和各种问题。它的目标并非成为一个大而全的GUI应用或独立的部署服务器,而是提供一个经过充分测试、稳固且通用的核心基础。开发者可以基于此,针对自己平台的特定限制和需求,灵活构建上层更新解决方案。
它有清晰的自我定位和边界:
- 角色定位:RAUC是嵌入在你应用程序和基础设施中的更新引擎,主要通过D-Bus接口集成,而非独立的图形应用。
- 职责分工:它不替代负责选择启动目标的引导程序(Bootloader),而是与之进行清晰协作。
- 功能范围:它不提供完整的部署服务器功能,但可以与像hawkBit这样的专业服务器进行对接。
- 使用方式:其更新包(Bundle)不是通用容器,安装必须通过
rauc install 或D-Bus API触发,以确保整个流程可控。
与U-Boot的集成:决策与执行分离
RAUC与U-Boot等引导程序的集成是实现A/B无缝更新的关键。其核心思想在于 “决策与执行分离” :RAUC在系统运行时决定下次启动哪个分区(槽位),而U-Boot则在启动时根据RAUC设定的标记,执行具体的引导动作。
下图清晰地展示了这一协作流程:

这种协作依赖于一套具体的配置。RAUC通过修改U-Boot环境变量(主要是 rauc.slot)来传递决策,U-Boot的启动脚本则负责解读并执行它。
-
RAUC的配置
在RAUC的系统配置文件(/etc/rauc/system.conf)中,你需要为每个可启动的“槽”设置一个在U-Boot中唯一的 bootname。
[slot.rootfs.0]
device=/dev/mmcblk2p2
type=ext4
bootname=system0 # 关键:对应U-Boot中的一个启动标识
[slot.rootfs.1]
device=/dev/mmcblk2p3
type=ext4
bootname=system1 # 关键:对应U-Boot中的另一个启动标识
-
U-Boot的配置
在U-Boot中,需要配置启动命令(通常在 bootcmd 或 extlinux.conf 中),使其根据 rauc.slot 变量的值来选择引导目标。一个简单的U-Boot脚本逻辑示例如下:
# U-Boot 环境变量示例
# 定义如何引导到 system0 (对应RAUC的 slot.rootfs.0)
load_system0=load mmc 1:2 ${kernel_addr_r} /boot/zImage; setenv fdtfile myboard.dtb
bootargs_system0=setenv bootargs console=ttyS0,115200 root=/dev/mmcblk2p2 rauc.slot=system0
# 定义如何引导到 system1 (对应RAUC的 slot.rootfs.1)
load_system1=load mmc 1:3 ${kernel_addr_r} /boot/zImage; setenv fdtfile myboard.dtb
bootargs_system1=setenv bootargs console=ttyS0,115200 root=/dev/mmcblk2p3 rauc.slot=system1
# 核心启动命令:读取 rauc.slot 变量,并跳转到对应的引导命令
bootcmd=if test "${rauc.slot}" = "system0"; then run load_system0 bootargs_system0;
elif test "${rauc.slot}" = "system1"; then run load_system1 bootargs_system1;
else echo "Invalid slot, booting default"; run load_system0 bootargs_system0;
fi; bootz ${kernel_addr_r} - ${fdt_addr}
你还可以实现更高级的“回滚”机制。RAUC除了设置 rauc.slot 变量外,还可以设置 bootcount 和 upgrade_available。如果新槽位启动失败,U-Boot的脚本可以自动递增 bootcount,并在数次失败后自动回滚到旧的、稳定的槽位,从而实现完全自动化的故障恢复。
上层D-Bus接口:标准化控制
RAUC的D-Bus API是为上层应用(如设备管理服务、UI界面)设计的标准化控制接口。所有核心操作,如安装更新、查询状态,都通过此接口完成。当RAUC作为系统服务运行时,D-Bus接口是推荐的控制方式。
主要接口位于系统总线(System Bus)上,服务名为 de.pengutronix.rauc,对象路径为 /,接口为 de.pengutronix.rauc.Installer。
主要方法与属性
- 安装更新 (
InstallBundle):核心方法,参数为更新包的URI(file:// 或 https://)。
- 获取状态 (
GetSlotStatus):返回所有槽的详细状态信息,包括版本、哈希值、启动状态等。
- 进度与信号:
Install 方法调用后,可通过 Progress 属性获取百分比,或监听 Operation 属性变化获取文本描述。安装完成、失败等事件会通过 Completed 等信号异步通知。
下面的Python代码片段展示了使用 dbus-python 库调用RAUC D-Bus API的基本流程:
import dbus
# 连接到系统总线,获取RAUC接口对象
bus = dbus.SystemBus()
rauc = bus.get_object('de.pengutronix.rauc', '/')
installer = dbus.Interface(rauc, 'de.pengutronix.rauc.Installer')
# 示例:获取槽状态
status = installer.GetSlotStatus()
# status是一个包含详细槽信息的数组,可进一步解析
# 示例:安装一个位于本地的更新包
bundle_uri = 'file:///tmp/update-bundle.raucb'
try:
installer.InstallBundle(bundle_uri, {}) # 第二个参数是附加选项,通常为空字典
print("安装已触发。")
except dbus.exceptions.DBusException as e:
print(f"安装触发失败: {e}")
使用步骤与核心特性
要在项目中用好RAUC,大致可以遵循以下步骤:
- 硬件与分区:为A/B系统准备存储介质(如eMMC),创建至少两套根文件系统(
rootfs)和内核分区。
- 配置RAUC系统文件:编写
/etc/rauc/system.conf,正确定义 rootfs.0、rootfs.1 等槽,指定其 device、type 和 bootname。
- 集成Bootloader:配置U-Boot,使其启动命令能根据RAUC设置的环境变量(如
rauc.slot)引导至正确的 bootname 对应的分区。可能需要编译包含特定脚本的U-Boot。
- 部署与测试:在目标系统上安装RAUC服务。使用
rauc bundle 命令在主机上创建并签名更新包,然后传输到目标设备,通过D-Bus或命令行(rauc install)进行安装测试。
- 安全保障:RAUC使用基于OpenSSL和X.509证书的加密签名来验证更新包的完整性与来源,防止未经授权的固件被刷入;支持对更新包进行加密,并可指定多个接收者的公钥,确保内容在传输过程中的机密性。
- 广泛的文件系统支持:支持ext4, vfat, UBIFS, JFFS2, squashfs等主流嵌入式文件系统。
为了降低集成门槛,RAUC已成功集成到主流的嵌入式Linux构建系统中:
- Yocto Project:可通过
meta-rauc 层方便地集成。
- Buildroot:自2017.08版本起提供官方支持。
总而言之,RAUC通过其原子性、可验证的更新机制和对嵌入式硬件的广泛适配能力,为开发者提供了一个构建企业级可靠OTA更新功能的强大基础。如果你正在为嵌入式Linux设备寻找一个坚实、灵活的更新解决方案,RAUC无疑是一个值得深入研究和采用的优秀框架。想了解更多嵌入式Linux和系统编程的实战知识,欢迎来云栈社区交流讨论。
|