上一次我们说到系统配置的一些工作,在那些工作完成之后我突然想到之前从来没有做过的一个工作:系统备份。这一次我们就研究一下系统备份是怎么做的。
系统备份其实很简单,你总是可以把树莓派上的SD卡拔下来,在电脑上用Win32DiskImager来备份,但是这样备份出来的文件会和SD卡容量一样大,这种备份方法不可持续。那么参考网上的使用linux下的dd指令来完成这个工作。
我看了一下,在使用linux电脑备份的时候是把树莓派的卡插到电脑上,然后用dd把SD卡内容拷贝到本地。我手上只有树莓派作为linux电脑,那就先用我的4B测试一下,看看效果。
害怕树莓派上空间不够,先挂个U盘备份到U盘里试试。一只没弄明白U盘是怎么挂载的,借此机会研究一下。
挂载U盘
U盘直接插上是读不了内容的,所以必须要使用mount
指令进行挂载。虽然mount
天然支持FAT文件系统,然而FAT有个文件尺寸上限,不是很好。因此考虑使用exFAT文件系统。安装新的工具包来使mount
支持exFAT。
sudo apt install exfat-fuse
好下一步将我的U盘格式化为exFAT格式,在里面放一个文件以备后续测试。
把U盘插到树莓派上。
使用上一次学的指令查看USB设备。
奇怪,U盘是16GB的,为什么这里显示是64GB?问题不大,下一步查看一下/dev
下的设备。这里目录下的sd表示存储设备,sda表示第一个存储设备,sdb表示第二个,以此类推。sda1就表示这个设备的第一个分区,sda2表示第二个分区,以此类推。
可以看到已经连接上了,我的U盘没有分区所以只有一个sda1。
下面创建挂载点,一般在/mnt
或者/media
目录下创建挂载点。 我现在没有挂载东西所以这俩目录底下都是空的。这里我就在mnt下挂载了。注意指令里面的uid和gid是权限设置,后面是你的用户名。
sudo mkdir /mnt/backup_flash
sudo mount -o uid=pi,gid=pi /dev/sda1 /mnt/backup_flash
挂载成功了,下面看一下我放在U盘里的文件都有啥。
我在电脑上整文件的时候没有搞换行,导致这么一个效果,整体来看没啥问题。
尝试修改这个文件,然后取消挂载,再到电脑上看看。
成功。
习得新指令:mount
和umount
功能:挂载和卸载移动存储设备
系统备份1
我现在想备份的系统正在树莓派上跑着,没法搞,现在先用另一个有系统的sd卡测试一下备份功能。
还是查看一下设备
注意到Device 006和/dev
下的sdb系列。在烧录系统得时候sd卡被分成了两个分区,所以这里有sdb1和sdb2。
可以使用df -h
指令来查看挂载情况。
这里由于sdb没有挂载,所以这里没有显示。使用上面的指令进行挂载。
注意这里的区别。烧录到sd卡后较大的分区是ext4文件系统,挂载的时候要强调这个。另外由于系统烧录后所属权限是root,所以这里的uid和gid设置要去掉,否则挂载会不成功。
挂载成功,下一步进行备份测试。
sudo dd if=/dev/sdb of=/mnt/backup_flash/RPi_backup_64GB_test.img bs=4M
使用dd指令进行拷贝,拷贝到U盘下的RPi_backup_64GB_test.img
下。注意:这里的if
表示输入文件(input file),of
表示输出文件(output file),bs
表示分包大小(bag size?)。如果想查看进度可以新建一个终端并输入如下执行查看进度。
sudo pkill -USR1 -n -x dd
到这一步可能按道理就应该完成了,但实际上并不是这样。经过漫长的等待(半个小时),终于拷贝完成的时候,系统提示空间不足,备份失败。
这和我想象的不太一样啊,又查阅了更多的资料后发现直接对SD卡使用dd
指令的结果和在windows上用Win32DiskImager的结果是一样的:镜像文件和SD卡一样大。
此路不通,换道。
习得新指令:dd
功能:文件拷贝和创建
系统备份2
试错之后发现了新的方法,这个方法需要更多的工具,也更复杂,但网上有人写好了脚本我可以直接使用。与直接使用dd
拷贝SD卡不同,这里我们要先使用dd
创建一个合适尺寸的镜像文件,然后将这个镜像文件划分为两个分区, root区为FAT32文件系统,Linux区为ext4文件系统。分区完成后需要把这个镜像文件作为虚拟磁盘挂载,挂载完成以后就可以进行格式化。此后就可以进行备份了。
工具准备
这个方法里面需要三个工具:
- dosfstools:fat32分区格式化工具
- dump:dump& restore 备份工具
- parted & kpartx:虚拟磁盘工具
使用如下指令安装。
sudo apt install dosfstools dump parted kpartx
创建镜像
根据系统尺寸决定使用的空间大小。
使用指令df -P
的时候,显示的数据是以1K(1024字节)为单位,注意到sdb1和sdb2分别使用的空间,下面要进行img空间分配。
应该注意到,boot分区其实是很小的,不过几十兆,分配的时候不应该用boot分区已经使用的空间,而是boot分区整个的大小,也就是表中第二列。root分区就不一样了,里面存放的是系统各项文件,这个分区在空间划分的时候应该照顾已经使用的空间,所以要注意的是表中第三列。
使用下列指令获取上面的参数。
bootsz=`df -P | grep /dev/sdb1 | awk '{print $2}'`# 注意到最后的那个$2
rootsz=`df -P | grep /dev/sdb2 | awk '{print $3}'`# 注意到最后的那个$3
然后利用下面指令计算镜像文件尺寸。
totalsz=`echo $rootsz $bootsz | awk '{print int(($1+$2)*1.3)}'`
注意到上面的参数1.3,这个参数是网上一个同学经过测试测出来的。使用原始大小的1.3倍是极好的,兼顾尺寸和完备程度。
利用下面的指令申请镜像空间。
img=/mnt/backup_flash/PRi_64GB_test.img
sudo dd if=/dev/zero of=$img bs=1k count=$totalsz
申请空间这一步会花不少时间,像我这个9GB多大概一小时吧。
至此,空间分配完成,下面开始分区。
空间分区
使用fdisk
指令查看分区情况。
sudo fdisk -l /dev/sdb
在镜像文件分区的时候是根据扇区编号来确定的。和分配空间类似,sdb1分区应该完整划分,剩下的都是sdb2分区。
使用以下指令获得分区始末位置。
bootstart=`sudo fdisk -l /dev/sdb | grep sdb1 | awk '{print $2}'`
bootend=`sudo fdisk -l /dev/sdb | grep sdb1 | awk '{print $3}'`
rootstart=`sudo fdisk -l /dev/sdb | grep sdb2 | awk '{print $2}'`
奇怪,为什么分区始末是不连续的?暂时不管,使用下面的指令查看数据。
echo "boot: $bootstart -> $bootend, root: $rootstart -> end"
使用下面的指令进行分区。
sudo parted $img --script -- mklabel msdos
sudo parted $img --script -- mkpart primary fat32 ${bootstart}s ${bootend}s
sudo parted $img --script -- mkpart primary ext4 ${rootstart}s -1
注意,这里第三个指令最后一个参数是负一,不是英文字母l
,表示的含义是到最后一个,千万不要搞错了。
分区完毕之后进行格式化操作。
loopdevice=`sudo losetup -f --show $img`
device=`sudo kpartx -va $loopdevice | sed -E 's/.*(loop[0-9])p.*/1/g' | head -1`
device="/dev/mapper/${device}"
sudo mkfs.vfat ${device}p1 -n boot
sudo mkfs.ext4 ${device}p2
挂载并备份
使用下面的指令挂载虚拟磁盘的boot分区。
sudo mkdir -p /mnt/vir_img/boot
sudo mount -t vfat ${device}p1 /mnt/vir_img/boot
注意,boot区是FAT32格式,可以直接用cp
指令进行拷贝。使用下面指令即可。
sudo cp -rfp /mnt/rpi_sd/boot/* /mnt/vir_img/boot/
从要备份的SD卡,拷贝到目标文件挂载的目录。
然后挂载root分区。
sudo mkdir -p /mnt/vir_img/root
sudo mount -t ext4 ${device}p2 /mnt/vir_img/root
注意,root分区是exf4格式,需要使用dump & restore进行备份。
cd /mnt/vir_img/root
sudo chattr +d $img
sudo dump -0uaf - /mnt/rpi_sd/root/ | sudo restore -rf -
cd
sudo sync
经过漫长的拷贝过程,完成之后就可以卸载虚拟磁盘了。
sudo umount /mnt/vir_img/*
sudo kpartx -d $loopdevice
sudo losetup -d $loopdevice
sudo umount /mnt/backup_flash
好的,现在把得到的镜像拷贝到电脑上,烧到一个新的SD卡里用旧的树莓派测试一下。
emmmmm卡住不动了,备份失败。
把上面的参数1.3改一下,改成1.5,新写一个自动化脚本测试一下。
#!/bin/bash
# 时间
now=`date +%Y-%m-%d-%H%M%S`
exho "time:%t $now"
# 备份文件写入位置
target_file_dir=/home/pi/Documents/backup_test
sudo mkdir -p $target_file_dir
img=${target_file_dir}/backup-${now}.img
echo "target file" $img
# 虚拟磁盘挂载位置
vir_dir=/mnt/vir_img
# 目标文件文件系统和目录
bootfs=/dev/sdb1
rootfs=/dev/sdb2
bootdir=`df -P | grep ${bootfs} | awk '{print $6}'`
rootdir=`df -P | grep ${rootfs} | awk '{print $6}'`
echo $bootfs "mounted on" $bootdir
echo $rootfs "mounted on" $rootdir
# 计算目标空间尺寸
bootsz=`df -P | grep $bootfs | awk '{print $2}'`
rootsz=`df -P | grep $rootfs | awk '{print $3}'`
totalsz=`echo $rootsz $bootsz | awk '{print int(($1+$2)*1.5)}'`
echo "total size" ${totalsz}KB
# 获取分区始末位置
bootstart=`sudo fdisk -l | grep $bootfs | awk '{print $2}'`
bootend=`sudo fdisk -l | grep $bootfs | awk '{print $3}'`
rootstart=`sudo fdisk -l | grep $rootfs | awk '{print $2}'`
echo "boot: $bootstart -> $bootend, root: $rootstart -> end"
# 申请镜像空间
sudo dd if=/dev/zero of=$img bs=1k count=$totalsz
# 目标镜像分区
sudo parted $img --script -- mklabel msdos
sudo parted $img --script -- mkpart primary fat32 ${bootstart}s ${bootend}s
sudo parted $img --script -- mkpart primary ext4 ${rootstart}s -1
# 创建虚拟磁盘
loopdevice=`sudo losetup -f --show $img`
device=`sudo kpartx -va $loopdevice | sed -E 's/.*(loop[0-9])p.*/1/g' | head -1`
sleep 5 # 暂停来避开bug
device="/dev/mapper/${device}"
# 格式化
sudo mkfs.vfat ${device}p1 -n BOOT
sudo mkfs.ext4 ${device}p2
# 挂载虚拟磁盘
vir_boot=${vir_dir}/boot
vir_root=${vir_dir}/root
sudo mkdir -p $vir_boot $vir_root
sudo mount -t vfat ${device}p1 $vir_boot
sudo mount -t ext4 ${device}p2 $vir_root
# 备份boot分区
sudo cp -rfp ${bootdir}/* ${vir_boot}/
# 备份root分区
cd $vir_root
sudo chattr +d $img
sudo dump -0uaf - $rootdir | sudo restore -rf -
cd
sudo sync
# 卸载虚拟磁盘
sudo umount ${vir_dir}/*
sudo kpartx -d $loopdevice
sudo losetup -d $loopdevice
保存,执行,还是不行。
咋回事呢?我一步步按照教程来的啊,怎么就不好使呢?
莫非是备份目标的问题?教程里是把正在运行的树莓派固件备份,我这里是把一个挂载的卡备份,会不会不一样呢?会不会是刚才不连续的问题造成的呢?
修改脚本,重新测试,依旧失败,出现了教程里面一个broken pipe的问题。
时间有点晚了,暂时到这里,系统备份失败,下次再继续研究。
总结
虽然系统备份是失败的,但是我挂载U盘的指令由于反复使用已经烂熟于心,另外df
和fdisk
指令也是经常使用,也熟练了不少。
参考文献
树莓派挂载U盘或移动硬盘:http://shumeipai.nxez.com/2013/09/08/raspberry-pi-to-mount-the-removable-hard-disk.html
linux下mount指令详解:https://blog.csdn.net/daydayup654/article/details/78788310
linux下uid和gid详解:https://blog.csdn.net/36/article/details/86573899
dd
指令系统备份:https://blog.csdn.net/just_s0so/article/details/88825716
系统备份:https://www.cnblogs.com/javazhu/articles/9234097.html
系统备份(蜜汁1.3):http://www.360doc.com/content/17/0805/03/40492717_676751545.shtml