6. 分区镜像备份

在产品研发和量产过程中,经常需要对已经完成软件配置和环境部署的板卡进行系统镜像备份, 并将备份镜像批量烧录到其他产品设备中,以提高生产效率并保证设备软件环境一致性。

6.1. 分区备份说明

由于Hi3403平台当前不支持从可插拔存储设备启动,系统运行在eMMC时无法直接对正在使用中的eMMC文件系统进行完整备份。 一方面,系统运行过程中,eMMC处于挂载和读写状态,直接备份可能导致文件系统数据不一致;另一方面,部分分区内容在运行过程中可能被占用,无法保证安全读取。

分析烧录了镜像的eMMC分区结构可知,主要包含以下几个分区:

  • boot分区:存放二进制启动文件,烧录完成后内容固定,不会发生变化;

  • env分区:存放U-Boot环境变量,内容相对固定,可通过修改配置文件重新编译生成,无需频繁备份;

  • kernel分区:存放Linux内核镜像,烧录完成后内容固定;

  • rootfs分区:存放完整Linux根文件系统,包括应用程序、配置文件、动态库及用户数据,是系统运行环境的核心部分,也是备份的重点。

因此,在系统镜像备份过程中,主要需要备份rootfs分区。boot、kernel和env分区内容固定,还可以通过重新编译方式恢复。

为解决eMMC无法在线备份rootfs的问题,可采用SD卡辅助启动方案。

eMMC和SD卡在Linux系统中均作为MMC块设备(mmcblk)使用,具有相同的块设备访问方式。因此,可以将eMMC镜像烧录到SD卡,使SD卡具备相同的分区结构。

虽然Hi3403无法直接通过SD卡完成完整启动流程,但在Linux启动到kernel阶段后,MMC控制器和SD卡驱动已经完成初始化,Linux内核具备访问SD卡的能力。

因此,无需依赖SD卡完成前级启动,只需在Linux根文件系统挂载阶段,将rootfs切换到SD卡对应分区即可。

此时,eMMC中的rootfs分区不再作为当前运行环境,不会被挂载和访问,处于离线状态,可以安全进行读取和备份。

6.2. SD启动卡制作

前面说过,Hi3403的eMMC镜像可以直接烧录到SD卡中,但当前是分区镜像,需要按照分区表中的大小和偏移位置将镜像一个一个烧录到SD卡很麻烦。 此时可以借助ToolPlatform烧录工具的 制作eMMC烧片器镜像 功能将分区镜像按分区表中的配置合并成一个镜像,就可以使用SD卡烧录工具直接写入了。

打开ToolPlatform烧录工具,在烧写eMMC选项卡中,先加载分区表文件,然后根据分区选择对应的分区镜像文件。

rootfs分区镜像选择ubuntu系统,缺少工具可以通过网络安装,有些Buildroot系统镜像可能工具不全,备份时不建议使用。

修改rootfs分区的长度为rootfs镜像文件大小,单位为MB。

注解

有些系统显示的文件大小的值是四舍五入后的结果,为确保设置的分区长度大小不小于实际的文件,建议向上取整。如535M向上取整为540M,1.27GB向上取整为1.30GB*1024=1331.2,实际设置1332M

../../_images/sdimg01.png

勾选所有分区,然后点击 “制作eMMC烧片器镜像” 按钮

会弹出一个窗口,需要选择即将生成的进行的保存位置和文件名,在文件名处输入 sdcard.img 然后点击保存

../../_images/sdimg02.png

等待制作成功的弹窗提示后,使用SD卡烧录工具将生成的镜像烧录到SD卡。

这里使用 balenaEtcher 烧录镜像:

打开balenaEtcher,点击 Flash from file 选择本地文件并确认

../../_images/sdimg03.png

接着会弹出没有分区标识的说明,选择 Continue

../../_images/sdimg04.png

将SD卡使用读卡器插入电脑,点击balenaEtcher的 sclect target

警告

由于稍后的烧录过程会对SD卡进行格式化,此处请务必确认选择的是刚刚插入的SD卡,以防止误格式化其他存储。

通过盘符和容量等特征选择刚刚插入的SD卡。

../../_images/sdimg05.png

点击 Flash! 开始烧录

../../_images/sdimg06.png

等待烧录完成

../../_images/sdimg07.png

6.3. 挂载并启动SD卡中的rootfs

将烧录完成的SD卡插入板卡,并使用USB连接电脑和板卡的DEBUG接口,打开调试串口。

重启板卡,在启动时按住 空格键 向串口终端输入,使串口终端进入U-Boot命令行模式

../../_images/sdimg08.png

使用printenv命令查看U-Boot传递给内核的启动参数,并记录未修改前的配置

1
2
3
4
5
# 打印bootargs环境变量
printenv bootargs

# 输出的变量值
bootargs=mem=512M console=ttyAMA0,115200 clk_ignore_unused rw rootwait root=/dev/mmcblk0p4 rootfstype=ext4 blkdevparts=mmcblk0:512K(u-boot.bin),512K(env),16M(kernel),-(rootfs.ext4)

这里关注要修改的bootargs参数的作用:

  • root=/dev/mmcblk0p4:指定根文件系统所在的分区是mmcblk0p4

  • blkdevparts:存储分区表,格式是blkdevparts=<设备>:<大小>(名称),<大小>(名称)…

当kernel完成SD卡初始化后,SD卡会被识别为块设备/dev/mmcblk1。 由于SD卡中烧录的镜像与eMMC镜像一致,因此SD卡上的分区布局与eMMC完全相同,仅块设备编号不同:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# eMMC分区结构
/dev/mmcblk0
├── /dev/mmcblk0p1  u-boot.bin(512K)
├── /dev/mmcblk0p2  env(512K)
├── /dev/mmcblk0p3  kernel(16M)
└── /dev/mmcblk0p4  rootfs.ext4(-表示所有eMMC剩余空间)

# 对应的blkdevparts分区表为
mmcblk0:512K(u-boot.bin),512K(env),16M(kernel),-(rootfs.ext4)


# SD卡分区结构
/dev/mmcblk1
├── /dev/mmcblk1p1  u-boot.bin(512K)
├── /dev/mmcblk1p2  env(512K)
├── /dev/mmcblk1p3  kernel(16M)
└── /dev/mmcblk1p4  rootfs.ext4(-表示所有SD卡剩余空间)

# 对应的blkdevparts分区表为
mmcblk1:512K(u-boot.bin),512K(env),16M(kernel),-(rootfs.ext4)

根据以上的分析,需要修改的内容如下

1
2
3
4
5
# root修改为SD卡的rootfs分区
root=/dev/mmcblk1p4

# blkdevparts分区表同时保留eMMC分区表和SD卡分区表
blkdevparts=mmcblk0:512K(u-boot.bin),512K(env),16M(kernel),-(rootfs.ext4);mmcblk1:512K(u-boot.bin),512K(env),16M(kernel),-(rootfs.ext4)

在U-Boot命令行终端输入下面的命令修改bootargs的值,不要使用saveenv命令保存修改后的环境变量, 这样在重启时就会恢复到原来的值,重新挂载eMMC中的根文件系统

1
2
3
4
5
6
7
8
# 修改环境变量
setenv bootargs 'mem=512M console=ttyAMA0,115200 clk_ignore_unused rw rootwait root=/dev/mmcblk1p4 rootfstype=ext4 blkdevparts=mmcblk0:512K(u-boot.bin),512K(env),16M(kernel),-(rootfs.ext4);mmcblk1:512K(u-boot.bin),512K(env),16M(kernel),-(rootfs.ext4)'

# 打印bootargs环境变量,确认修改是否生效
printenv bootargs

# 启动内核
boot

等待启动完成,登录系统终端后再次确认内核接收到U-Boot传递的启动参数和当前rootfs挂载的分区

1
2
3
4
5
# 启动参数
cat /proc/cmdline

# 确认rootfs挂载的分区
findmnt -n -o SOURCE /

可以确认当前启动挂载的就是SD卡中的根文件系统分区

../../_images/sdimg09.png

6.4. 备份分区镜像

6.4.1. 挂载eMMC中rootfs分区

挂载eMMC中rootfs分区,也就是/dev/mmcblk0p4分区,以便后续操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 创建/mnt/emmc_rootfs目录
mkdir -p /mnt/emmc_rootfs

# 挂载eMMC的rootfs分区到/mnt/emmc_rootfs
mount /dev/mmcblk0p4 /mnt/emmc_rootfs

# 查看挂载情况
df -h /mnt/emmc_rootfs/

Filesystem      Size  Used Avail Use% Mounted on
/dev/mmcblk0p4   58G  1.1G   55G   2% /mnt/emmc_rootfs

6.4.2. 计算eMMC中rootfs实际占用空间

为了减小备份镜像的体积,缩短量产时的烧录时间,需要在保持足够冗余的情况下,尽可能的缩小备份的根文件系统体积。

上面的df -h命令获取的是分区中文件的总体积,除此之外还有一些文件系统相关的内容也会占用一定的空间,在备份时这部分空间也要考虑。

使用下面的命令获取rootfs分区最小可压缩的大小:

1
2
3
4
5
# 获取eMMC中rootfs可压缩的最小分区block数量
resize2fs -P /dev/mmcblk0p4

# 获取block大小
tune2fs -l /dev/mmcblk0p4 | grep "Block size"

例如图中所示,最小block数量为453122,一个block大小为4096 Byte,可以计算得到453122*4096/1024/1024约等于1771MB,这就是eMMC中rootfs可以缩小的最小值。

../../_images/sdimg11.png

6.4.3. 创建ext4备份镜像并格式化

在SD卡的根文件系统中创建一个备份镜像,镜像大小根据刚刚得到的计算值向上取整得到1780MB

注解

SD卡写入速度较慢,也可以挂载一个U盘或硬盘,将备份镜像保存在U盘或硬盘中。

1
2
3
4
5
# 创建一个1780MB的空镜像文件
dd if=/dev/zero of=/backup-rootfs.img bs=1M count=1780

# 格式化备份镜像为ext4格式
mkfs.ext4 -F -L rootfs /backup-rootfs.img

6.4.4. 挂载备份rootfs镜像并同步文件

这一步操作是将备份镜像挂载,此时同时挂载了备份镜像和eMMC的rootfs分区,就可以使用rsync命令将eMMC的rootfs分区中的全部文件同步到备份镜像中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 创建备份镜像挂载目录
mkdir -p /mnt/backup_rootfs

# 挂载备份镜像为loop设备
mount -o loop /backup-rootfs.img /mnt/backup_rootfs

# 查看挂载情况
df -h /mnt/backup_rootfs

Filesystem      Size  Used Avail Use% Mounted on
/dev/loop0      1.7G   24K  1.6G   1% /mnt/backup_rootfs

# 同步eMMC中rootfs的所有内容到备份镜像中
rsync -aHAX --numeric-ids --info=progress2 /mnt/emmc_rootfs/ /mnt/backup_rootfs/

# 如果没有rsync命令,可以使用apt命令安装
apt update
apt install rsync

备份时间与要备份根文件系统的大小和存储的读写速度有关,等待同步完成。

6.4.5. 备份的根文件系统文件后处理

备份的根文件系统中有一些系统初始化完成标志文件,为了在备份镜像烧录后完成例如分区自动扩容等功能,需要删除这些标志文件

1
2
3
4
5
6
7
8
9
# 删除根文件系统初始化完成标志
rm -rf /mnt/backup_rootfs/usr/local/first_boot_flag

# 删除rootfs自动扩展分区完成标志
rm -rf /mnt/backup_rootfs/var/lib/misc/firstrun
rm -rf /mnt/backup_rootfs/.resized

# 删除保存的网卡MAC地址
rm -rf /mnt/backup_rootfs/etc/ethaddr.txt

为了在重新烧录后验证是我们备份的镜像,这里写入当前时间到文件作为标记,正式备份时可忽略此步骤

1
2
3
4
5
6
7
# 写入备份标记
echo $(date) > /mnt/backup_rootfs/date.txt

# 打印date.txt内容
cat /mnt/backup_rootfs/date.txt

Thu Jul 23 17:40:14 CST 2026

6.4.6. 卸载分区并检查备份镜像

1
2
3
4
5
6
7
8
# 卸载eMMC的rootfs分区
umount /mnt/emmc_rootfs

# 卸载备份镜像
umount /mnt/backup_rootfs

# 检查和修复备份镜像
e2fsck -f /backup-rootfs.img

检查完成没有报错后使用u盘或网络将备份镜像传输到电脑上。

6.5. 重新烧录

重启板卡,自动恢复到挂载eMMC中的根文件系统

 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
# 确认rootfs挂载的分区
findmnt -n -o SOURCE /

# 查看根目录下的文件
root@lubancat:~# ls -hl /
total 60K
lrwxrwxrwx   1 root root    7 Sep 11  2024 bin -> usr/bin
drwxr-xr-x   2 root root 4.0K Apr 18  2022 boot
drwxr-xr-x  11 root root 4.8K Jul 23 17:49 dev
drwxrwxr-x  83 root root 4.0K Jul 23 15:59 etc
drwxrwxr-x   8 root root 4.0K Jul 23 15:52 hi_mpp
drwxr-xr-x   3 root root 4.0K Jul 22 09:56 home
lrwxrwxrwx   1 root root    7 Sep 11  2024 lib -> usr/lib
drwx------   2 root root  16K Jul 23 15:52 lost+found
drwxr-xr-x   2 root root 4.0K Sep 11  2024 media
drwxr-xr-x   2 root root 4.0K Sep 11  2024 mnt
drwxr-xr-x   2 root root 4.0K Sep 11  2024 opt
dr-xr-xr-x 133 root root    0 Jan  1  1970 proc
drwx------   3 root root 4.0K Jul 23 16:01 root
drwxr-xr-x  17 root root  540 Jul 23 17:51 run
lrwxrwxrwx   1 root root    8 Sep 11  2024 sbin -> usr/sbin
drwxr-xr-x   3 root root 4.0K Jul 22 09:53 srv
dr-xr-xr-x  12 root root    0 Jul 23 17:49 sys
drwxrwxrwt  12 root root  240 Jul 23 17:49 tmp
drwxrwxr-x  11 root root 4.0K Jul 22 08:55 usr
drwxr-xr-x  11 root root 4.0K Jul 23 15:59 var

6.6. 重新烧录

使用ToolPlatform或者SD卡烧录方式,选择backup-rootfs.img作为rootfs分区的镜像进行烧录。

烧录完成后查看rootfs分区是否自动扩展,创建的时间标志内容是否正确

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 查看分区是否自动扩展
df -h

Filesystem      Size  Used Avail Use% Mounted on
/dev/root        58G  1.1G   55G   2% /
tmpfs           230M     0  230M   0% /dev/shm
tmpfs            92M  1.1M   91M   2% /run
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           230M  4.0K  230M   1% /tmp
tmpfs            46M     0   46M   0% /run/user/0

# 查看时间标志
cat /date.txt
Thu Jul 23 17:40:14 CST 2026

根据时间标志可以确定,这就是我们刚刚备份的镜像。