2. A/B系统及OTA/U盘升级

本章节将介绍如何制作A/B系统镜像、基于A/B系统实现OTA、U盘升级。

注解

本章节仅针对野火维护的extboot分区系统镜像,不适用于rkboot分区系统镜像。

2.1. 什么是A/B系统?

2.1.1. A/B系统简介

A/B系统也叫双槽位冗余升级系统,是嵌入式Linux、Android设备主流的安全升级架构。 核心是在设备存储中划分两套完全独立、可完整启动的系统分区组,分别命名为Slot A、Slot B,对应两套boot、rootfs。 设备运行时只会从当前活跃槽位(A或B)启动并运行系统,另一套槽位始终处于空闲备用状态。 所有系统升级、固件更新,都会写入非活跃的备用槽位,不会覆盖正在运行的系统,从根源避免升级变砖问题。

2.1.2. 实现机制

A/B系统的核心思想是双系统冗余+后台静默更新,主要包含以下4个环节:

  • 双分区设计:系统核心分区成对存在,分为Slot A(boot_a、rootfs_a)和Slot B(boot_b、rootfs_b),userdata分区通常是全局共享的。

  • 后台静默升级:设备当前运行在Slot A时,OTA 升级程序会在后台将新固件完整写入空闲的Slot B,此过程不影响当前系统的正常运行。

  • 槽位切换:写入并校验完成后,系统通过修改特定标记(如全局boot分区中的标志文件),通知U-Boot下次重启时从Slot B引导。

  • 自动回退机制:如果新系统(Slot B)无法正常启动,Bootloader会检测到异常,并自动将启动槽位切回正常的Slot A。

2.1.3. 优缺点

A/B系统优点:

  • 即使升级过程中突然断电,损坏的也只是未激活的备用分区,当前系统依然能正常启动。

  • 新固件若有严重Bug,也能回退到升级前系统。

  • 可以在后台完成升级准备,仅在维护窗口期重启切换,最大化减少停机时间。

A/B系统缺点:

  • 需要维护两套完整的系统镜像(boot+rootfs),对存储的容量要求显著提高,镜像烧录时间成倍增加。

  • 由于userdata是A/B共享的,如果新版本系统对用户数据的格式或数据库结构进行了不兼容的修改,需要在首次启动新槽位时编写额外的数据迁移或清空处理。

2.2. 制作A/B系统镜像

为了降低开发的复杂程度,不基于SDK源码编译生成A/B系统镜像,而是将SDK编译生成的extboot分区系统镜像通过瑞芯微的Linux_Pack_Firmware工具配合野火适配的全自动脚本重新打包生成A/B系统镜像。

注意

需要在Linux系统下进行操作,不能在windows等系统下操作。

2.2.1. 获取工具并解包镜像

访问资料网盘/6-开发软件/,获取Linux_Pack_Firmware_v2.29.zip,将其解压到Linux PC或虚拟机中。

1
2
#解压
unzip Linux_Pack_Firmware_v2.29.zip

需要确认Linux_Pack_Firmware软件包中是否有野火适配的全自动脚本, 如果没有则需要重新访问配套的资料网盘,下载最新的软件包压缩包。

1
2
3
4
5
#进入工具目录
cd Linux_Pack_Firmware/rockdev/

#需确认有以下.sh脚本
ls -l  build_ab_updateimg.sh update_ab_system.sh auto_upgrade.sh build_upgrade_manifest.sh

各脚本功能如下:

  • build_ab_updateimg.sh:用于将解包后的extboot分区系统镜像重新打包成A/B系统镜像。

  • update_ab_system.sh:用于OTA或者本地升级A/B系统镜像及槽位切换。

  • auto_upgrade.sh:用于检测U盘升级A/B系统的校验文件和镜像组件,完成A/B系统自动升级及槽位切换。

  • build_upgrade_manifest.sh:用于生成U盘升级A/B系统的校验文件。

访问资料网盘获取需要的系统镜像或者自己通过SDK源码编译好的系统镜像,传到Linux_Pack_Firmware/rockdev目录下。

以资料网盘的rk3568 Debian10 lite镜像为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#进入工具目录
cd Linux_Pack_Firmware/rockdev/

#将镜像压缩包传到Linux_Pack_Firmware/rockdev目录下

#解压镜像,根据实际压缩包名称而定
7z x lubancat-rk3568-debian10-lite-20260418_update.7z

#重命名镜像为update.img,根据实际镜像名称而定
mv lubancat-rk3568-debian10-lite-20260418_update.img update.img

#解包镜像
./unpack.sh

#解包的镜像组件位于output目录
#查看output目录
ls output/
#信息输出如下
boot.img  MiniLoaderAll.bin  package-file  parameter.txt  rootfs.img  uboot.img

2.2.2. 打包生成A/B系统镜像

通过build_ab_updateimg.sh脚本能很方便地将镜像组件重新打包成A/B系统镜像。

1
2
3
4
5
#进入工具目录
cd Linux_Pack_Firmware/rockdev/

#执行打包脚本
sudo ./build_ab_updateimg.sh

运行脚本后需要根据实际镜像选择目标芯片平台。 此处示例是以rk3568 Debian10 lite镜像为例,因此需要选择“ 2) RK3566/RK3568 ”项,键盘输入“2”进行选择,然后按下回车:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
=========================================
请选择目标芯片平台:
=========================================
1) RK3528
2) RK3566/RK3568
3) RK3562
4) RK3576
5) RK3588/RK3588S
=========================================
请输入选项 [1-5]: 2
>>> 已选择芯片: RK3568

需要根据实际需求选择是否需要userdata (data) 分区:

注解

如果选择了需要userdata分区,那么A/B系统镜像的最后一个分区将设置为userdata (data),userdata分区将使用存储剩余的全部空间,供A/B系统共享使用; 如果不需要userdata分区,那么系统最后一个分区就是Slot B的rootfs_b,rootfs_b将使用存储剩余的全部空间,而Slot A可能因此没有足够的空间本地存放升级组件。 如果需要实现OTA功能的用户建议此处选择需要userdata分区,另外没有特殊需求的也建议选择需要userdata分区。

1
是否需要 userdata (data) 分区?(y/n): y
  • 选择y分区表为:1.uboot 2.boot 3.boot_a 4.boot_b 5.rootfs_a 6.rootfs_b 7.data

  • 选择n分区表为:1.uboot 2.boot 3.boot_a 4.boot_b 5.rootfs_a 6.rootfs_b

需要根据实际需求设置rootfs空间:

注解

此处设置的rootfs空间为根文件系统可用空间,如果前面选择了需要userdata分区,那么Slot A、Slot B系统的根文件系统可用空间都是此处设置的值; 如果前面选择了不需要userdata分区,那么只有Slot A系统的根文件系统可用空间是此处设置的值,Slot B系统的根文件系统可用空间为存储剩余全部可用空间。

1
2
3
4
5
6
7
=========================================
正在计算 rootfs 分区大小...

rootfs.img 实际大小: 1593 MiB
最小允许分配大小:    1793 MiB (实际大小+200MiB)
=========================================
请输入分配给 rootfs_a 和 rootfs_b 的大小 (单位 MiB, 最小 1793): 2000

以上设置完成后脚本将自动生成和修改对应的文件,然后打包各组件生成A/B系统镜像,完整脚本信息如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#运行打包脚本
sudo ./build_ab_updateimg.sh

#脚本信息如下
=========================================
请选择目标芯片平台:
=========================================
1) RK3528
2) RK3566/RK3568
3) RK3562
4) RK3576
5) RK3588/RK3588S
=========================================
请输入选项 [1-5]: 2
>>> 已选择芯片: RK3568

是否需要 userdata (data) 分区?(y/n): y
=========================================
正在计算 rootfs 分区大小...

rootfs.img 实际大小: 1593 MiB
最小允许分配大小:    1793 MiB (实际大小+200MiB)
=========================================
请输入分配给 rootfs_a 和 rootfs_b 的大小 (单位 MiB, 最小 1793): 2000
[生成] 复制boot_a.img、rootfs_a.img ...
[生成] 复制boot_b.img、rootfs_b.img ...
[生成] 创建boot.img ...
[修改] 挂载boot_a.img并修改boot.cmd ...
[编译] boot_a/boot.cmd -> boot.scr
Image Name:
Created:      Tue Oct 13 06:09:43 2026
Image Type:   ARM Linux Script (uncompressed)
Data Size:    3996 Bytes = 3.90 KiB = 0.00 MiB
Load Address: 00000000
Entry Point:  00000000
Contents:
Image 0: 3988 Bytes = 3.89 KiB = 0.00 MiB
[修改] 挂载boot_b.img并修改boot.cmd ...
[编译] boot_b/boot.cmd -> boot.scr
Image Name:
Created:      Tue Oct 13 06:09:44 2026
Image Type:   ARM Linux Script (uncompressed)
Data Size:    3996 Bytes = 3.90 KiB = 0.00 MiB
Load Address: 00000000
Entry Point:  00000000
Contents:
Image 0: 3988 Bytes = 3.89 KiB = 0.00 MiB
[生成] 挂载boot.img,创建全局boot.cmd ...
Image Name:
Created:      Tue Oct 13 06:09:44 2026
Image Type:   ARM Linux Script (uncompressed)
Data Size:    2406 Bytes = 2.35 KiB = 0.00 MiB
Load Address: 00000000
Entry Point:  00000000
Contents:
Image 0: 2398 Bytes = 2.34 KiB = 0.00 MiB
[修改] 挂载rootfs_a.img并修改boot_init.sh
[修改] 挂载rootfs_b.img并修改boot_init.sh
Android Firmware Package Tool v2.29
------ PACKAGE ------
Add file: ./package-file
package-file,Add file: ./package-file done,offset=0x800,size=0xd8,userspace=0x1
Add file: ./parameter.txt
parameter,Add file: ./parameter.txt done,offset=0x1000,size=0x1d8,userspace=0x1,flash_address=0x00000000
Add file: ./MiniLoaderAll.bin
bootloader,Add file: ./MiniLoaderAll.bin done,offset=0x1800,size=0x759c0,userspace=0xec
Add file: ./uboot.img
uboot,Add file: ./uboot.img done,offset=0x77800,size=0x800000,userspace=0x1000,flash_address=0x00004000
Add file: ./boot.img
boot,Add file: ./boot.img done,offset=0x877800,size=0x400000,userspace=0x800,flash_address=0x00008000
Add file: ./boot_a.img
boot_a,Add file: ./boot_a.img done,offset=0xc77800,size=0x8000000,userspace=0x10000,flash_address=0x0000a000
Add file: ./boot_b.img
boot_b,Add file: ./boot_b.img done,offset=0x8c77800,size=0x8000000,userspace=0x10000,flash_address=0x0004a000
Add file: ./rootfs_a.img
rootfs_a,Add file: ./rootfs_a.img done,offset=0x10c77800,size=0x63900000,userspace=0xc7200,flash_address=0x0008a000
Add file: ./rootfs_b.img
rootfs_b,Add file: ./rootfs_b.img done,offset=0x74577800,size=0x63900000,userspace=0xc7200,flash_address=0x00472000
Add CRC...
Make firmware OK!
------ OK ------
********rkImageMaker ver 2.29********
Generating new image, please wait...
Writing head info...
Writing boot file...
Writing firmware...
Generating MD5 data...
MD5 data generated successfully!
New image generated successfully!

============================================
打包完成! 最终镜像文件: ./output/update.img
============================================

打包完成后output目录下的文件如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#查看output目录下文件
ls -l ./output/

#信息输出如下
total 10610940
drwxr-xr-x 2 root  root        4096 Oct 13 06:09 boot
-rw-rw-r-- 1 guest guest  134217728 Oct 13 06:09 boot_a.img
-rw-r--r-- 1 root  root   134217728 Oct 13 06:09 boot_b.img
-rw-r--r-- 1 root  root     4194304 Oct 13 06:09 boot.img
-rw-rw-r-- 1 guest guest     481728 Oct 13 03:47 MiniLoaderAll.bin
-rw-r--r-- 1 root  root         216 Oct 13 06:09 package-file
-rw-rw-r-- 1 guest guest        460 Oct 13 06:09 parameter.txt
drwxr-xr-x 2 root  root        4096 Oct 13 06:09 rootfs
-rw-rw-r-- 1 guest guest 1670381568 Oct 13 06:09 rootfs_a.img
-rw-r--r-- 1 root  root  1670381568 Oct 13 06:09 rootfs_b.img
-rw-rw-r-- 1 guest guest    8388608 Oct 13 03:47 uboot.img
-rw-r--r-- 1 root  root  3622752842 Oct 13 06:11 update.img
-rw-r--r-- 1 root  root  3622270980 Oct 13 06:10 update_tmp.img

各组件说明如下:

  • boot_a.img:Slot A系统的boot引导分区镜像文件。

  • boot_b.img:Slot B系统的boot引导分区镜像文件。

  • boot.img:全局boot引导分区镜像文件。

  • rootfs_a.img:Slot A系统的rootfs根文件系统分区镜像文件。

  • rootfs_b.img:Slot B系统的rootfs根文件系统分区镜像文件。

  • uboot.img:uboot镜像文件。

  • parameter.txt:系统分区表文件。

  • package-file:打包配置文件。

  • MiniLoaderAll.bin:loader文件。

  • update_tmp.img:打包临时中间件镜像文件。

  • update.img:最终生成的A/B系统镜像文件。

其中最终生成的 update.img 就是我们需要的A/B系统镜像文件。

2.2.3. 镜像烧录测试

将 update.img 镜像烧录到板卡中并查看系统分区和根文件系统大小:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#查看系统分区信息
lsblk

#以下是选择了需要userdata分区的分区信息
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
mmcblk0      179:0    0 29.3G  0 disk
├─mmcblk0p1  179:1    0    8M  0 part
├─mmcblk0p2  179:2    0    4M  0 part
├─mmcblk0p3  179:3    0  128M  0 part
├─mmcblk0p4  179:4    0  128M  0 part /boot
├─mmcblk0p5  179:5    0    2G  0 part
├─mmcblk0p6  179:6    0    2G  0 part /
└─mmcblk0p7  179:7    0 25.1G  0 part /userdata

#查看根文件系统大小
df -m

#信息输出如下
Filesystem     1M-blocks  Used Available Use% Mounted on
udev                1937     1      1937   1% /dev
tmpfs                391     6       385   2% /run
/dev/mmcblk0p6      1938  1301       528  72% /
tmpfs               1951     0      1951   0% /dev/shm
tmpfs                  5     1         5   1% /run/lock
tmpfs               1951     0      1951   0% /sys/fs/cgroup
tmpfs               1951     1      1951   1% /tmp
/dev/mmcblk0p7     25208    45     23861   1% /userdata
/dev/mmcblk0p4       124    43        76  36% /boot
tmpfs                391     0       391   0% /run/user/0

可以看到当前系统有7个分区,依次对应uboot、boot、boot_a、boot_b、rootfs_a、rootfs_b以及data。 当前启动的是Slot B系统(因为默认的设置为启动Slot B系统),根文件系统大小为1938M,可用空间为528M,data共享分区挂载到了/userdata目录下,可用空间为23861M

2.3. A/B系统镜像升级

2.3.1. 系统升级脚本介绍

通过update_ab_system.sh脚本能很方便实现OTA或者本地升级A/B系统镜像及槽位切换, 并且在打包镜像过程中已经将update_ab_system.sh脚本打包进了根文件系统的/usr/bin/目录下,可以直接使用。

查看update_ab_system.sh脚本位置和使用示例:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
#在烧录了A/B系统镜像的板卡中执行

#查看脚本位置
which update_ab_system.sh

#信息输出如下
/usr/bin/update_ab_system.sh

#查看脚本帮助信息
update_ab_system.sh --help

#信息输出如下
============================================================
LubanCat-RK A/B 系统升级脚本
============================================================

【脚本简介】
本脚本用于LubanCat-RK平台的A/B系统升级(OTA/本地)及槽位切换。
脚本会自动识别当前运行的系统槽位(Slot A 或 Slot B)及所在物理磁盘,
并将新固件写入到同一块磁盘的另一个非活动槽位中,
最后通过修改全局boot分区的标志文件来通知U-Boot在下次重启时切换到新系统。

【参数说明】
仅切换操作参数:
--switch-only              : 仅切换A/B系统槽位并重启。
                            使用此参数时,无需提供 --mode, --target 等参数。
                            脚本会自动识别当前系统,并切换到另一个槽位。
升级模式必填参数(与 --switch-only 互斥):
--mode <online|local>      : 指定升级模式。
                            'online' 表示从HTTP服务器下载镜像。
                            'local'  表示使用本地已存在的镜像文件。
--target <boot|rootfs|all> : 指定需要升级的组件。
                            'boot'   : 仅升级boot分区 (内核/设备树)。
                            'rootfs' : 仅升级rootfs分区 (根文件系统)。
                            'all'    : 同时升级boot和rootfs分区。
--boot <path_or_name>      : 指定boot镜像的路径(本地模式)或文件名(在线模式)。
--rootfs <path_or_name>    : 指定rootfs镜像的路径(本地模式)或文件名(在线模式)。
                            (注意:如果--target指定了boot或rootfs,对应的镜像参数必须提供)

在线模式(online)必填参数:
--url <server_url>         : 指定OTA服务器的基础URL(例如: http://192.168.1.100:8080)。
                            脚本会自动拼接文件名,如: http://192.168.1.100:8080/boot_b.img
--dir <download_dir>       : 指定在线下载时,镜像文件在本地板卡上的存放目录。

可选参数:
--format-data              : 升级完成后,在重启前自动格式化userdata (data)分区为ext4。
                            适用于大版本更新或需要清空用户数据的场景。
--keep                     : 保留升级使用的镜像文件。默认情况下不加此参数,
                            升级完成后会自动删除镜像文件以释放存储空间。
--no-reboot                : 升级/切换完成后不自动重启系统。
                            默认情况下会自动重启。用于外部脚本调用以接管后续重启/关机处理。
【使用示例】

示例 1: 在线升级全部组件(boot + rootfs)到目标槽位,并清空data分区
sudo update_ab_system.sh \
    --mode online \
    --target all \
    --boot boot_a.img \
    --rootfs rootfs_a.img \
    --url http://10.255.200.107:8080 \
    --dir /userdata/ota_tmp \
    --format-data

示例 2: 在线仅升级boot分区
sudo update_ab_system.sh \
    --mode online \
    --target boot \
    --boot boot_a.img \
    --url http://10.255.200.107:8080 \
    --dir /userdata/ota_tmp

示例 3: 在线仅升级rootfs分区
sudo update_ab_system.sh \
    --mode online \
    --target rootfs \
    --rootfs boot_a.img \
    --url http://10.255.200.107:8080 \
    --dir /userdata/ota_tmp

示例 4: 本地升级全部组件(镜像已提前通过scp/U盘传到板卡上)
sudo update_ab_system.sh \
    --mode local \
    --target all \
    --boot /userdata/boot_a.img \
    --rootfs /userdata/rootfs_a.img

示例 5: 本地仅升级boot分区,且希望保留本地的镜像文件不被删除
sudo update_ab_system.sh \
    --mode local \
    --target boot \
    --boot /mnt/boot_a.img \
    --keep

示例 6: 本地仅升级rootfs分区,且希望保留本地的镜像文件不被删除
sudo update_ab_system.sh \
    --mode local \
    --target rootfs \
    --rootfs /mnt/rootfs_b.img \
    --keep

示例 7: 仅切换系统槽位并重启
sudo update_ab_system.sh --switch-only

【注意事项】
1. 必须使用root权限运行本脚本。
2. 升级过程会自动处理槽位切换,无需手动干预U-Boot环境变量。
============================================================

2.3.2. OTA升级测试

为了方便测试,直接使用打包镜像的服务器主机进行测试,需要服务器主机和板卡处于同一个网络下,并且可以互ping。

在服务器主机通过python直接创建简单web服务器:

1
2
3
4
5
6
7
8
#进入工具镜像输出目录
cd Linux_Pack_Firmware/rockdev/output/

#通过python直接创建简单web服务器
python3 -m http.server 8080

#信息输出如下
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

python3 -m http.server 8080命令是在当前目录下,快速启动一个轻量级的HTTP静态文件服务器,其中-m http.server告诉Python运行其内置的标准库模块http.server,8080指定该Web服务器监听的端口号。

  • 升级示例1 :通过OTA完整升级boot、rootfs分区并格式化data分区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#作者板卡处于Slot B系统,因此需要下载Slot A系统的boot_a.img、rootfs_a.img
#url中10.255.200.107为作者服务器主机IP,需要修改为自己服务器主机实际IP
#dir指定下载镜像的存放目录,可以根据实际修改,需要保证空间足够存放下载的镜像组件
#--format-data会使更新系统后重启系统前,格式化data分区

sudo update_ab_system.sh \
    --mode online \
    --target all \
    --boot boot_a.img \
    --rootfs rootfs_a.img \
    --url http://10.255.200.107:8080 \
    --dir /userdata/ota_tmp \
    --format-data

#信息输出如下
>>> 检测到LED设备: /sys/class/leds/sys_led,后台控制1秒周期循环闪烁
>>> 检测到当前系统所在物理磁盘: /dev/mmcblk0
>>> 当前运行系统: Slot B (/dev/mmcblk0p6)
>>> 目标切换/升级系统: Slot a
>>> 本次升级组件: all
>>> 目标 boot 分区: /dev/mmcblk0p3
>>> 目标 rootfs 分区: /dev/mmcblk0p5
=========================================
[在线升级] 正在从服务器下载镜像...
=========================================
下载: http://10.255.200.107:8080/boot_a.img
保存路径:/userdata/ota_tmp/boot_a.img
--2026-10-13 15:31:08--  http://10.255.200.107:8080/boot_a.img
Connecting to 10.255.200.107:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 134217728 (128M) [application/octet-stream]
Saving to: ‘/userdata/ota_tmp/boot_a.img’

boot_a.img          100%[===================>] 128.00M  78.7MB/s    in 1.6s

2026-10-13 15:31:10 (78.7 MB/s) - ‘/userdata/ota_tmp/boot_a.img’ saved [134217728/134217728]

下载: http://10.255.200.107:8080/rootfs_a.img
保存路径:/userdata/ota_tmp/rootfs_a.img
--2026-10-13 15:31:10--  http://10.255.200.107:8080/rootfs_a.img
Connecting to 10.255.200.107:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1670381568 (1.6G) [application/octet-stream]
Saving to: ‘/userdata/ota_tmp/rootfs_a.img’

rootfs_a.img        100%[===================>]   1.56G  93.2MB/s    in 17s

2026-10-13 15:31:27 (94.5 MB/s) - ‘/userdata/ota_tmp/rootfs_a.img’ saved [1670381568/1670381568]

=========================================
[写入分区] 正在刷入目标系统...
=========================================
>>> 写入 boot_a 到 /dev/mmcblk0p3
128+0 records in
128+0 records out
134217728 bytes (134 MB, 128 MiB) copied, 1.8443 s, 72.8 MB/s
>>> 写入 rootfs_a 到 /dev/mmcblk0p5
1631584256 bytes (1.6 GB, 1.5 GiB) copied, 9 s, 181 MB/s
1593+0 records in
1593+0 records out
1670381568 bytes (1.7 GB, 1.6 GiB) copied, 15.6271 s, 107 MB/s
>>> 刷写完成,正在同步磁盘缓存...
>>> 清理升级镜像文件 ...
=========================================
[切换槽位] 配置 U-Boot 下次从 Slot a 启动...
=========================================
[ 3529.542392] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null)
=========================================
[格式化] 正在格式化 userdata (data) 分区...
=========================================
>>> 找到 data 分区: /dev/mmcblk0p7
mke2fs 1.44.5 (15-Dec-2018)
/dev/mmcblk0p7 contains a ext4 file system
        last mounted on /userdata on Mon Oct 12 18:04:13 2026
Discarding device blocks: done
Creating filesystem with 6589435 4k blocks and 1648320 inodes
Filesystem UUID: 8e2a0bb4-7397-404d-b4ba-408d0c953b22
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
        4096000

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

data 分区格式化完成!
=========================================
升级完成!系统将在重启后进入 Slot a。
>>> 正在重启系统...

从升级信息可以看到:

  • 升级期间会控制系统心跳灯1秒周期循环闪烁。

  • 检测到当前系统所在物理磁盘和当前运行系统,识别到要升级系统的分区。

  • 从服务器上下载了boot_a.img、rootfs_a.img,保存到了/userdata/ota_tmp/目录下。

  • 将boot_a.img写入到了Slot A的boot分区/dev/mmcblk0p3,将rootfs_a.img写入到了Slot A的rootfs分区/dev/mmcblk0p5。

  • 切换槽位,也就是将boot全局分区/dev/mmcblk0p2进行挂载并创建boot_a文件供uboot识别,下次启动将启动Slot A系统。

  • 识别并格式化data分区/dev/mmcblk0p7为ext4格式。

  • 最后重启系统。

系统重启后最次查看系统分区和根文件系统大小:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#查看系统分区信息
lsblk

#以下是选择了需要userdata分区的分区信息
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
mmcblk0      179:0    0 29.3G  0 disk
├─mmcblk0p1  179:1    0    8M  0 part
├─mmcblk0p2  179:2    0    4M  0 part
├─mmcblk0p3  179:3    0  128M  0 part /boot
├─mmcblk0p4  179:4    0  128M  0 part
├─mmcblk0p5  179:5    0    2G  0 part /
├─mmcblk0p6  179:6    0    2G  0 part
└─mmcblk0p7  179:7    0 25.1G  0 part /userdata
mmcblk0boot0 179:32   0    4M  1 disk
mmcblk0boot1 179:64   0    4M  1 disk

#查看根文件系统大小
df -m

#信息输出如下
Filesystem     1M-blocks  Used Available Use% Mounted on
udev                1937     1      1937   1% /dev
tmpfs                391    11       380   3% /run
/dev/mmcblk0p5      1938  1302       527  72% /
tmpfs               1951     0      1951   0% /dev/shm
tmpfs                  5     1         5   1% /run/lock
tmpfs               1951     0      1951   0% /sys/fs/cgroup
tmpfs               1951     1      1951   1% /tmp
/dev/mmcblk0p7     25208    45     23861   1% /userdata
/dev/mmcblk0p3       124    43        76  36% /boot
tmpfs                391     0       391   0% /run/user/0

可以看到当前系统挂载的boot分区为/dev/mmcblk0p3、根文件系统为/dev/mmcblk0p5,启动的是Slot A系统,说明系统OTA升级成功并切换到升级后的系统运行。

  • 升级示例2 :通过OTA仅升级boot分区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#作者板卡处于Slot A系统,因此需要下载Slot B系统的boot_b.img
#url中10.255.200.107为作者服务器主机IP,需要修改为自己服务器主机实际IP
#dir指定下载镜像的存放目录,可以根据实际修改,需要保证空间足够存放下载的镜像组件

sudo update_ab_system.sh \
    --mode online \
    --target boot \
    --boot boot_b.img \
    --url http://10.255.200.107:8080 \
    --dir /userdata/ota_tmp

#信息输出如下
>>> 检测到LED设备: /sys/class/leds/sys_led,后台控制1秒周期循环闪烁
>>> 检测到当前系统所在物理磁盘: /dev/mmcblk0
>>> 当前运行系统: Slot A (/dev/mmcblk0p5)
>>> 目标切换/升级系统: Slot b
>>> 本次升级组件: boot
>>> 目标 boot 分区: /dev/mmcblk0p4
>>> 目标 rootfs 分区: /dev/mmcblk0p6
=========================================
[在线升级] 正在从服务器下载镜像...
=========================================
下载: http://10.255.200.107:8080/boot_b.img
保存路径:/userdata/ota_tmp/boot_b.img
--2026-10-13 16:00:39--  http://10.255.200.107:8080/boot_b.img
Connecting to 10.255.200.107:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 134217728 (128M) [application/octet-stream]
Saving to: ‘/userdata/ota_tmp/boot_b.img’

boot_b.img          100%[===================>] 128.00M  77.9MB/s    in 1.6s

2026-10-13 16:00:41 (77.9 MB/s) - ‘/userdata/ota_tmp/boot_b.img’ saved [134217728/134217728]

=========================================
[写入分区] 正在刷入目标系统...
=========================================
>>> 写入 boot_b 到 /dev/mmcblk0p4
128+0 records in
128+0 records out
134217728 bytes (134 MB, 128 MiB) copied, 1.485 s, 90.4 MB/s
>>> 刷写完成,正在同步磁盘缓存...
>>> 清理升级镜像文件 ...
=========================================
[切换槽位] 配置 U-Boot 下次从 Slot b 启动...
=========================================
[ 1702.505667] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null)
=========================================
升级完成!系统将在重启后进入 Slot b。
>>> 正在重启系统...

从升级信息可以看到:

  • 升级期间会控制系统心跳灯1秒周期循环闪烁。

  • 检测到当前系统所在物理磁盘和当前运行系统,识别到要升级系统的分区。

  • 从服务器上下载了boot_b.img,保存到了/userdata/ota_tmp/目录下。

  • 将boot_b.img写入到了Slot B的boot分区/dev/mmcblk0p4。

  • 切换槽位,也就是将boot全局分区/dev/mmcblk0p2进行挂载并创建boot_b文件供uboot识别,下次启动将启动Slot B系统。

  • 最后重启系统。

  • 升级示例3 :通过OTA仅升级rootfs分区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#作者板卡处于Slot B系统,因此需要下载Slot A系统的rootfs_a.img
#url中10.255.200.107为作者服务器主机IP,需要修改为自己服务器主机实际IP
#dir指定下载镜像的存放目录,可以根据实际修改,需要保证空间足够存放下载的镜像组件

sudo update_ab_system.sh \
    --mode online \
    --target rootfs \
    --rootfs boot_a.img \
    --url http://10.255.200.107:8080 \
    --dir /userdata/ota_tmp

#信息输出如下
>>> 检测到LED设备: /sys/class/leds/sys_led,后台控制1秒周期循环闪烁
>>> 检测到当前系统所在物理磁盘: /dev/mmcblk0
>>> 当前运行系统: Slot B (/dev/mmcblk0p6)
>>> 目标切换/升级系统: Slot a
>>> 本次升级组件: rootfs
>>> 目标 boot 分区: /dev/mmcblk0p3
>>> 目标 rootfs 分区: /dev/mmcblk0p5
=========================================
[在线升级] 正在从服务器下载镜像...
=========================================
下载: http://10.255.200.107:8080/rootfs_a.img
保存路径:/userdata/ota_tmp/rootfs_a.img
--2026-10-13 16:05:14--  http://10.255.200.107:8080/rootfs_a.img
Connecting to 10.255.200.107:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1670381568 (1.6G) [application/octet-stream]
Saving to: ‘/userdata/ota_tmp/rootfs_a.img’

rootfs_a.img        100%[===================>]   1.56G  80.9MB/s    in 20s

2026-10-13 16:05:34 (79.9 MB/s) - ‘/userdata/ota_tmp/rootfs_a.img’ saved [1670381568/1670381568]

=========================================
[写入分区] 正在刷入目标系统...
=========================================
>>> 写入 rootfs_a 到 /dev/mmcblk0p5
1630535680 bytes (1.6 GB, 1.5 GiB) copied, 9 s, 181 MB/s
1593+0 records in
1593+0 records out
1670381568 bytes (1.7 GB, 1.6 GiB) copied, 15.6294 s, 107 MB/s
>>> 刷写完成,正在同步磁盘缓存...
>>> 清理升级镜像文件 ...
=========================================
[切换槽位] 配置 U-Boot 下次从 Slot a 启动...
=========================================
[  283.580620] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null)
=========================================
升级完成!系统将在重启后进入 Slot a。
>>> 正在重启系统...

从升级信息可以看到:

  • 升级期间会控制系统心跳灯1秒周期循环闪烁。

  • 检测到当前系统所在物理磁盘和当前运行系统,识别到要升级系统的分区。

  • 从服务器上下载了rootfs_a.img,保存到了/userdata/ota_tmp/目录下。

  • 将rootfs_a.img写入到了Slot A的boot分区/dev/mmcblk0p5。

  • 切换槽位,也就是将boot全局分区/dev/mmcblk0p2进行挂载并创建boot_a文件供uboot识别,下次启动将启动Slot A系统。

  • 最后重启系统。

2.3.3. 本地升级测试

本地升级需要将升级镜像组件提前下载到板卡或者外接的U盘等存储设备中。

  • 升级示例1 :通过本地镜像组件完整升级boot、rootfs分区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#作者板卡处于Slot A系统,因此需要提前准备Slot B系统的boot_b.img、rootfs_b.img
#镜像组件路径需修改为实际的绝对路径

sudo update_ab_system.sh \
    --mode local \
    --target all \
    --boot /userdata/boot_b.img \
    --rootfs /userdata/rootfs_b.img

#信息输出如下
>>> 检测到LED设备: /sys/class/leds/sys_led,后台控制1秒周期循环闪烁
>>> 检测到当前系统所在物理磁盘: /dev/mmcblk0
>>> 当前运行系统: Slot A (/dev/mmcblk0p5)
>>> 目标切换/升级系统: Slot b
>>> 本次升级组件: all
>>> 目标 boot 分区: /dev/mmcblk0p4
>>> 目标 rootfs 分区: /dev/mmcblk0p6
=========================================
[本地升级] 校验本地镜像文件...
=========================================
=========================================
[写入分区] 正在刷入目标系统...
=========================================
>>> 写入 boot_b 到 /dev/mmcblk0p4
128+0 records in
128+0 records out
134217728 bytes (134 MB, 128 MiB) copied, 1.51942 s, 88.3 MB/s
>>> 写入 rootfs_b 到 /dev/mmcblk0p6
1591738368 bytes (1.6 GB, 1.5 GiB) copied, 8 s, 199 MB/s
1593+0 records in
1593+0 records out
1670381568 bytes (1.7 GB, 1.6 GiB) copied, 14.9555 s, 112 MB/s
>>> 刷写完成,正在同步磁盘缓存...
>>> 清理升级镜像文件 ...
=========================================
[切换槽位] 配置 U-Boot 下次从 Slot b 启动...
=========================================
[  858.987554] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null)
=========================================
升级完成!系统将在重启后进入 Slot b。
>>> 正在重启系统...

从升级信息可以看到:

  • 升级期间会控制系统心跳灯1秒周期循环闪烁。

  • 检测到当前系统所在物理磁盘和当前运行系统,识别到要升级系统的分区。

  • 将本地的/userdata/boot_b.img写入到了Slot B的boot分区/dev/mmcblk0p4,将本地的/userdata/rootfs_b.img写入到了Slot B的rootfs分区/dev/mmcblk0p6。

  • 切换槽位,也就是将boot全局分区/dev/mmcblk0p2进行挂载并创建boot_b文件供uboot识别,下次启动将启动Slot B系统。

  • 最后重启系统。

  • 升级示例2 :通过本地镜像组件仅升级boot分区,并且保留镜像组件,升级完成后格式化data分区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#作者板卡处于Slot B系统,因此需要提前准备Slot A系统的boot_a.img
#作者将boot_a.img保存到了U盘并挂载到了/media/usb0/目录下
#镜像组件路径需修改为实际的绝对路径

sudo update_ab_system.sh \
    --mode local \
    --target boot \
    --boot /media/usb0/boot_a.img \
    --format-data
    --keep

#信息输出如下
>>> 检测到LED设备: /sys/class/leds/sys_led,后台控制1秒周期循环闪烁
>>> 检测到当前系统所在物理磁盘: /dev/mmcblk0
>>> 当前运行系统: Slot B (/dev/mmcblk0p6)
>>> 目标切换/升级系统: Slot a
>>> 本次升级组件: boot
>>> 目标 boot 分区: /dev/mmcblk0p3
>>> 目标 rootfs 分区: /dev/mmcblk0p5
=========================================
[本地升级] 校验本地镜像文件...
=========================================
=========================================
[写入分区] 正在刷入目标系统...
=========================================
>>> 写入 boot_a 到 /dev/mmcblk0p3
128+0 records in
128+0 records out
134217728 bytes (134 MB, 128 MiB) copied, 1.60901 s, 83.4 MB/s
>>> 刷写完成,正在同步磁盘缓存...
>>> 清理升级镜像文件 ...
=========================================
[切换槽位] 配置 U-Boot 下次从 Slot a 启动...
=========================================
[ 1010.758376] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null)
=========================================
[格式化] 正在格式化 userdata (data) 分区...
=========================================
>>> 找到 data 分区: /dev/mmcblk0p7
mke2fs 1.44.5 (15-Dec-2018)
/dev/mmcblk0p7 contains a ext4 file system
    last mounted on /userdata on Tue Oct 13 16:21:14 2026
Discarding device blocks: done
Creating filesystem with 6589435 4k blocks and 1648320 inodes
Filesystem UUID: 58e0853b-fa9e-4dfa-aafc-7db181f619c0
Superblock backups stored on blocks:
    32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
    4096000

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

data 分区格式化完成!
=========================================
升级完成!系统将在重启后进入 Slot a。
>>> 正在重启系统...

从升级信息可以看到:

  • 升级期间会控制系统心跳灯1秒周期循环闪烁。

  • 检测到当前系统所在物理磁盘和当前运行系统,识别到要升级系统的分区。

  • 将本地的/media/usb0/boot_a.img写入到了Slot A的boot分区/dev/mmcblk0p3。

  • 切换槽位,也就是将boot全局分区/dev/mmcblk0p2进行挂载并创建boot_a文件供uboot识别,下次启动将启动Slot A系统。

  • 识别并格式化data分区/dev/mmcblk0p7为ext4格式。

  • 最后重启系统。

2.3.4. U盘升级测试

U盘升级是在本地升级的基础上实现U盘插入后自动升级系统,无需用户执行任何命令操作。

该方式通过usbmount工具实现U盘的自动挂载,并且挂载后会自动执行/etc/usbmount/mount.d/目录下的脚本,而LubanCat系统(26年4月17日后的)均安装了usbmount工具。

注意

如果U盘格式是NTFS需要确认系统是否安装了ntfs-3g,如果格式是exFAT需要确认系统是否安装了exfat-fuse。

如果使用旧镜像则需要自行安装usbmount工具。

1
2
3
4
#安装usbmount
sudo apt update
sudo apt install exfat-fuse exfat-utils ntfs-3g pmount
sudo apt install usbmount

因此,基于usbmount工具只需要一个U盘升级脚本来校验镜像组件,确认需要升级的部分,再调用update_ab_system.sh将各组件打入对应的分区,最后切换系统启动标志并重启即可。

前面介绍的auto_upgrade.sh、build_upgrade_manifest.sh就是用来实现以上功能的。

  • auto_upgrade.sh:用于检测U盘升级A/B系统的校验文件和镜像组件,完成A/B系统自动升级及槽位切换。

  • build_upgrade_manifest.sh:用于生成U盘升级A/B系统的校验文件。

2.3.4.1. 生成校验文件

通过build_upgrade_manifest.sh脚本来生成U盘升级A/B系统的校验文件。脚本开头部分有可配置项,可以根据实际需求自行修改,默认全升级,不格式化data分区,不持续烧录。

1
2
3
4
#配置升级参数,可根据需要修改
TARGET="all"            #可选: boot, rootfs, all
FORMAT_DATA="false"     #可选: true, false
REPEAT_BURN="false"     #可选: true, false
  • REPEAT_BURN设置为true时,升级完成后系统会关机而不是重启,关机后拔下U盘插入另一个板卡可继续进行更新。

生成校验文件需要在运行build_ab_updateimg.sh脚本打包A/B系统后执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#进入工具目录
cd Linux_Pack_Firmware/rockdev/

#运行脚本
./build_upgrade_manifest.sh

#信息输出如下
正在生成判断文件...

--- 判断文件内容预览 ---
[Upgrade_Config]
TARGET=all
FORMAT_DATA=false
REPEAT_BURN=false

[Checksums]
boot_a.img=eeea8ee590bb13dc50799b68c1104910
boot_b.img=27446863035a6ff34cd967893ad15095
rootfs_a.img=48c60ab260a0547f5ce6df354274c9c2
rootfs_b.img=ef578cbf256799628a380c27363678f2

==================================================
生成完成! 判断文件: ./output/upgrade_manifest.txt
==================================================

生成的./output/upgrade_manifest.txt文件就是我们需要的U盘升级A/B系统的校验文件。

文件校验值通过自定义快速抽样哈希算法生成,根据“文件大小+lubancat字符串+文件头1M空间的MD5+尾1M空间的MD5+中1M空间的MD5”结合生成MD5校验值。

2.3.4.2. 拷贝镜像组件到U盘

需要将升级组件以及校验文件拷贝到U盘。如果TARGET配置了“all”就需要将boot_a.img、boot_b.img、rootfs_a.img、rootfs_b.img以及upgrade_manifest.txt拷贝到U盘; 如果TARGET配置了“boot”,可以不拷贝rootfs_a.img、rootfs_b.img;如果TARGET配置了“rootfs”,可以不拷贝boot_a.img、boot_b.img。

注意

如果某个镜像组件文件大小大于4G,需要注意U盘格式,fat32格式单文件最大仅支持4G,而NTFS格式单个文件最大大小为64GB到16EB,exFAT格式单个文件最大可支持16EB。 使用NTFS、exFAT格式需要确认系统已经安装了对应的软件包,否则无法自动挂载。

2.3.4.3. 插入U盘自动升级系统

因为在打包A/B系统镜像时已经将auto_upgrade.sh拷贝到了rootfs的/etc/usbmount/mount.d/05_auto_upgrade,因此插入U盘将自动升级系统。

升级系统时系统心跳灯会1秒周期闪烁,升级完成后会自动重启系统。 如果升级失败系统心跳灯会常亮,并且将U盘中的upgrade_manifest.txt重命名为upgrade_manifest.txt.failed。 如果升级成功会将U盘中的upgrade_manifest.txt重命名为upgrade_manifest.txt.done。

升级过程中的日志信息保存到了升级前系统的/var/log/usb_auto_upgrade.log。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#插入U盘后,在升级过程中查看升级日志或升级完成后挂载升级前系统分区查看
cat /var/log/usb_auto_upgrade.log

#信息输出如下
=========================================
[Wed Oct 14 09:23:45 CST 2026]
[U盘自动升级] 检测到升级判断文件!
=========================================
>>> 当前系统槽位: rootfs_b, 目标升级槽位: a
校验通过: boot_a.img
校验通过: rootfs_a.img
=========================================
所有镜像校验通过,准备调用升级脚本...
=========================================
>>> 执行命令: /usr/bin/update_ab_system.sh --mode local --target all --keep --no-reboot --boot /media/usb0/boot_a.img --rootfs /media/usb0/rootfs_a.img
>>> 检测到LED设备: /sys/class/leds/sys_led,后台控制1秒周期循环闪烁
>>> 检测到当前系统所在物理磁盘: /dev/mmcblk0
>>> 当前运行系统: Slot B (/dev/mmcblk0p6)
>>> 目标切换/升级系统: Slot a
>>> 本次升级组件: all
>>> 目标 boot 分区: /dev/mmcblk0p3
>>> 目标 rootfs 分区: /dev/mmcblk0p5
=========================================
[本地升级] 校验本地镜像文件...
=========================================
=========================================
[写入分区] 正在刷入目标系统...
=========================================
>>> 写入 boot_a 到 /dev/mmcblk0p3
127926272 bytes (128 MB, 122 MiB) copied, 8 s, 15.9 MB/s
128+0 records in
128+0 records out
134217728 bytes (134 MB, 128 MiB) copied, 9.49599 s, 14.1 MB/s
>>> 写入 rootfs_a 到 /dev/mmcblk0p5
1664090112 bytes (1.7 GB, 1.5 GiB) copied, 113 s, 14.7 MB/s
1593+0 records in
1593+0 records out
1670381568 bytes (1.7 GB, 1.6 GiB) copied, 116.495 s, 14.3 MB/s
>>> 刷写完成,正在同步磁盘缓存...
>>> 保留升级镜像文件 ...
=========================================
[切换槽位] 配置 U-Boot 下次从 Slot a 启动...
=========================================
=========================================
升级完成!系统将在重启后进入 Slot a。
>>> 已指定 --no-reboot,跳过自动重启,流程结束。
>>> 升级脚本执行成功。
>>> [单次升级模式] 准备重启系统...

从升级信息可以看到:

  • 识别到了upgrade_manifest.txt校验文件,将自动进行升级。

  • 解析了校验文件的TARGET、FORMAT_DATA和REPEAT_BURN配置。

  • 根据自定义快速抽样哈希算法计算文件校验值并比较校验文件中记录的文件校验值。

  • 调用update_ab_system.sh升级脚本进行升级,命令和TARGET、FORMAT_DATA和REPEAT_BURN配置项设置的一致。

  • 镜像组件成功写入对应分区并切换槽位。

  • 调用升级脚本时指定了–no-reboot,因此不会通过升级脚本重启系统。

  • 因为REPEAT_BURN为false,因此执行重启而不是关机。

升级完成后可以查看U盘文件,可发现upgrade_manifest.txt被重命名为upgrade_manifest.txt.done以提示升级完成。

如果REPEAT_BURN为true,升级信息则为:

1
2
3
4
...
>>> 升级脚本执行成功。
>>> [重复烧录模式] 恢复校验文件原名,准备关机...
>>> 正在关机,关机后请拔下U盘插入下一块板卡继续烧录...

设置REPEAT_BURN为true时升级完成后系统会关机,此时拔下U盘插入下一块板卡可继续烧录,无需手动重命名upgrade_manifest.txt文件。

2.3.5. 切换系统槽位测试

通过update_ab_system.sh脚本能很方便切换系统槽位,执行以下命令进行切换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#在烧录了A/B系统镜像的板卡中执行
sudo update_ab_system.sh --switch-only

#信息输出如下
>>> 检测到LED设备: /sys/class/leds/sys_led,后台控制1秒周期循环闪烁
>>> 检测到当前系统所在物理磁盘: /dev/mmcblk0
>>> 当前运行系统: Slot B (/dev/mmcblk0p6)
>>> 目标切换/升级系统: Slot a
=========================================
[切换槽位] 配置 U-Boot 下次从 Slot a 启动...
=========================================
[  392.543055] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null)
=========================================
槽位切换完成!系统将在重启后进入 Slot a。
>>> 正在重启系统...

从切换信息可以看到:

  • 切换期间会控制系统心跳灯1秒周期循环闪烁。

  • 检测到当前系统所在物理磁盘和当前运行系统,确认要切换后系统。

  • 切换槽位,也就是将boot全局分区/dev/mmcblk0p2进行挂载并创建boot_a文件供uboot识别,下次启动将启动Slot A系统。

  • 最后重启系统。

2.4. 自定义槽位引导

2.4.1. 默认引导流程解析

查看build_ab_updateimg.sh有以下一段内容,用于引导系统从Slot A或Slot B启动:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
if [ ! -f boot/boot.scr ]; then
    echo "[生成] 挂载boot.img,创建全局boot.cmd ..."
    cat <<'BOOTCMD_EOF' > boot/boot.cmd
echo [boot.cmd] run boot.cmd scripts ...;

setenv script_addr ${pxefile_addr_r}

if test -e ${devtype} ${devnum}:${distro_bootpart} /boot_a; then
    setenv expect_slot a
elif test -e ${devtype} ${devnum}:${distro_bootpart} /boot_b; then
    setenv expect_slot b
else
    setenv expect_slot b
fi

echo "=== Verifying expected slot ${expect_slot} ==="
if test "${expect_slot}" = "a"; then
    setenv test_boot_part 3
    setenv test_rootfs_part 5
else
    setenv test_boot_part 4
    setenv test_rootfs_part 6
fi

setenv is_valid false

if load mmc 0:${test_rootfs_part} ${script_addr} /etc/fstab; then
    echo "  -> rootfs /etc/fstab OK"

    if load mmc 0:${test_boot_part} ${script_addr} boot.scr; then
        echo "  -> boot boot.scr OK"
        echo "Slot ${expect_slot} is bootable."
        setenv slot_name ${expect_slot}
        setenv is_valid true
    else
        echo "  -> boot boot.scr FAILED"
    fi
else
    echo "  -> rootfs /etc/fstab FAILED"
fi

if test "${is_valid}" != "true"; then
    echo "=== Expected slot ${expect_slot} is NOT bootable. Trying fallback... ==="

    if test "${expect_slot}" = "a"; then
        setenv fallback_slot b
        setenv test_boot_part 4
        setenv test_rootfs_part 6
    else
        setenv fallback_slot a
        setenv test_boot_part 3
        setenv test_rootfs_part 5
    fi

    echo "=== Verifying fallback slot ${fallback_slot} ==="

    if load mmc 0:${test_rootfs_part} ${script_addr} /etc/fstab; then
        echo "  -> rootfs /etc/fstab OK"
    else
        echo "  -> rootfs /etc/fstab FAILED"
    fi

    if load mmc 0:${test_boot_part} ${script_addr} boot.scr; then
        echo "  -> boot boot.scr OK"
        echo "Slot ${fallback_slot} is bootable."
        setenv slot_name ${fallback_slot}
        setenv is_valid true
    else
        echo "  -> boot boot.scr FAILED"
        setenv slot_name ${fallback_slot}
    fi
fi

if test "${slot_name}" = "a"; then
    setenv distro_bootpart 3
    setenv boot_part_num 3
    setenv rootfs_part_num 5
else
    setenv distro_bootpart 4
    setenv boot_part_num 4
    setenv rootfs_part_num 6
fi

if test "${is_valid}" = "true"; then
    echo "Successfully verified slot ${slot_name}. Executing boot.scr..."
else
    echo "!!! WARNING: Both slots have issues. Attempting to boot from slot ${slot_name} anyway (initrd recovery mode)..."
fi

source ${script_addr}
BOOTCMD_EOF
    mkimage -C none -A arm -T script -d boot/boot.cmd boot/boot.scr
fi

引导流程分步解析如下:

  • 第一步:读取标志位,确定“期望槽位”

1
2
3
4
5
6
7
if test -e ${devtype} ${devnum}:${distro_bootpart} /boot_a; then
    setenv expect_slot a
elif test -e ${devtype} ${devnum}:${distro_bootpart} /boot_b; then
    setenv expect_slot b
else
    setenv expect_slot b
fi

U-Boot检查全局boot分区里面是否存在boot_a或boot_b文件,通过读取这个标志,知道用户期望下一次启动进入哪个槽位。如果都没有,默认保底启动Slot B。

  • 第二步:双重校验“期望槽位”的完整性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
setenv is_valid false

if load mmc 0:${test_rootfs_part} ${script_addr} /etc/fstab; then
    echo "  -> rootfs /etc/fstab OK"

    if load mmc 0:${test_boot_part} ${script_addr} boot.scr; then
        echo "  -> boot boot.scr OK"
        echo "Slot ${expect_slot} is bootable."
        setenv slot_name ${expect_slot}
        setenv is_valid true
    else
        echo "  -> boot boot.scr FAILED"
    fi
else
    echo "  -> rootfs /etc/fstab FAILED"
fi

为了防止OTA升级中途断电导致分区损坏,不会盲目信任标志位,而是进行双重校验:尝试从目标rootfs分区读取/etc/fstab文件,验证目标rootfs分区文件可读。 尝试从目标boot分区读取boot.scr,验证引导脚本是否存在。只有两者都成功,才认为该槽位是健康的(is_valid=true)。

  • 第三步:故障回退防止变砖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if test "${is_valid}" != "true"; then
    echo "=== Expected slot ${expect_slot} is NOT bootable. Trying fallback... ==="

    if test "${expect_slot}" = "a"; then
        setenv fallback_slot b
        setenv test_boot_part 4
        setenv test_rootfs_part 6
    else
        setenv fallback_slot a
        setenv test_boot_part 3
        setenv test_rootfs_part 5
    fi

    echo "=== Verifying fallback slot ${fallback_slot} ==="

    if load mmc 0:${test_rootfs_part} ${script_addr} /etc/fstab; then
        echo "  -> rootfs /etc/fstab OK"
    else
        echo "  -> rootfs /etc/fstab FAILED"
    fi

    if load mmc 0:${test_boot_part} ${script_addr} boot.scr; then
        echo "  -> boot boot.scr OK"
        echo "Slot ${fallback_slot} is bootable."
        setenv slot_name ${fallback_slot}
        setenv is_valid true
    else
        echo "  -> boot boot.scr FAILED"
        setenv slot_name ${fallback_slot}
    fi
fi

如果期望的槽位校验失败,说明新系统损坏,会自动回退到另一个槽位。即使新系统刷写失败或严重损坏,设备重启后依然能自动回到旧系统正常开机,防止设备变砖。

  • 第四步:重定向环境变量,准备最终引导

1
2
3
4
5
6
7
8
9
if test "${slot_name}" = "a"; then
    setenv distro_bootpart 3
    setenv boot_part_num 3
    setenv rootfs_part_num 5
else
    setenv distro_bootpart 4
    setenv boot_part_num 4
    setenv rootfs_part_num 6
fi

经过前面的校验或回退,已经确定了最终要启动的槽位(slot_name)。 将全局变量distro_bootpart、boot_part_num、rootfs_part_num强制覆盖为该槽位对应的分区号。 为了后续执行boot.scr时,boot.scr内部可以通过读取这些变量,准确地去正确的分区加载内核文件和设备树以及挂载根文件系统等。

  • 第五步:执行目标槽位的引导脚本

1
2
3
4
5
6
7
if test "${is_valid}" = "true"; then
    echo "Successfully verified slot ${slot_name}. Executing boot.scr..."
else
    echo "!!! WARNING: Both slots have issues. Attempting to boot from slot ${slot_name} anyway (initrd recovery mode)..."
fi

source ${script_addr}

使用source命令执行之前加载到内存${script_addr}中的boot.scr。 如果A/B两个槽位全部损坏(is_valid=false),脚本依然会强行执行source。 因为会触发内核的initrd恢复模式,进入initramfs临时文件系统,该文件系统带有自动修复boot、rootfs功能,如果无法自动修复,也可以通过串口或屏幕连接板卡进行救砖。

2.4.2. GPIO电平控制引导示例

默认引导是通过全局boot分区的标志文件并经双重校验确认最终的槽位系统的, 如果希望通过GPIO电平直接控制槽位切换可修改build_ab_updateimg.sh以下部分:

以LubanCat-2-V3板卡40pin的GPIO1_B2引脚为例,查看板卡原理图该引脚为GPIO1_B2_d,带有d后缀说明默认为低电平(u后缀则默认高电平),计算引脚编号为1x32+8+2=42。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# if test -e ${devtype} ${devnum}:${distro_bootpart} /boot_a; then
#     setenv expect_slot a
# elif test -e ${devtype} ${devnum}:${distro_bootpart} /boot_b; then
#     setenv expect_slot b
# else
#     setenv expect_slot b
# fi

if gpio input 42; then
    echo "  -> GPIO 42 is LOW (0), setting expect_slot to a"
    setenv expect_slot a
else
    echo "  -> GPIO 42 is HIGH (1), setting expect_slot to b"
    setenv expect_slot b
fi

修改完成后重新制作A/B系统镜像,然后烧录到板卡中。

将控制引脚短接到3.3V并启动系统有以下启动信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Found U-Boot script /boot.scr
2689 bytes read in 3 ms (875 KiB/s)
## Executing script at 00c00000
[boot.cmd] run boot.cmd scripts ...
gpio: pin 42 (gpio 42) value is 1
-> GPIO 42 is HIGH (1), setting expect_slot to b
=== Verifying expected slot b ===
537 bytes read in 17 ms (30.3 KiB/s)
-> rootfs /etc/fstab OK
4060 bytes read in 4 ms (991.2 KiB/s)
-> boot boot.scr OK
Slot b is bootable.
Successfully verified slot b. Executing boot.scr...

将控制引脚短接到GND或悬空并启动系统有以下启动信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Found U-Boot script /boot.scr
2689 bytes read in 3 ms (875 KiB/s)
## Executing script at 00c00000
[boot.cmd] run boot.cmd scripts ...
gpio: pin 42 (gpio 42) value is 0
-> GPIO 42 is LOW (0), setting expect_slot to a
=== Verifying expected slot a ===
447 bytes read in 14 ms (30.3 KiB/s)
-> rootfs /etc/fstab OK
4060 bytes read in 3 ms (1.3 MiB/s)
-> boot boot.scr OK
Slot a is bootable.
Successfully verified slot a. Executing boot.scr...

可以看到当短接到3.3V时系统从Slot B启动,当短接到GND或悬空时系统从Slot A启动,说明通过GPIO电平控制引导成功。