在树莓派上做开发,难免会弄出各种版本的系统,加上有时候还需要拿给客户自己烧录或者demo,总是要clone一下TF卡,做系统做到烦躁。于是想想有没有什么办法能做跟官方release一样的烧录img出来,基本要求就是全系统克隆,但做出的img跟卡的容量无关,只跟系统占用的存储大小有关。
简单全卡备份其实如果只是简单备份,可以直接将TF卡插入Linux电脑,用dd命令来备份和恢复(设备号不固定这里只是例子,还是用fdisk -l先查看一下比较保险):
#BackupthesystemtoimgfileinLinux ddif=/dev/mmcblk0of=raspberrypi-bak.imgbs=1M #AlittledifferenceifMac ddif=/dev/rdisk2of=raspberrypi-bak.imgbs=1M # #Restoresystemfromimg ddif=raspberrypi-bak.imgbs=1Mof=/dev/mmcblk0 #OrinMac ddif=raspberrypi-bak.imgbs=1Mof=/dev/rdisk2
不过这样做,16G的卡哪怕你只用了1G,整个备份文件也有16G,占用空间耗时间是小事,想想拿这么大文件给客户不方便,也显得太不专业了。
在网上搜了一下,已经有朋友先行做过类似的事情:树莓派 Raspberry Pi SD卡系统备份与还原,看了一下按照他的步骤实践了一遍,但出现了一些问题,折腾了很久最终才解决。
首先介绍一下RaspberryPi的文件系统。树莓派的官方系统是基于Debian的,主要是两个分区:启动分区boot和根分区。boot分区为fat32格式,挂载在/boot,存放一些系统启动需要的基本文件,包括内核、驱动、firmware、启动脚本等;根分区文件系统是ext4格式,挂载于/,存放一些安装的软件和库文件、系统配置、用户数据等等;另外当系统启动时会自动生成和挂载一些必要的其他文件夹,包括temfs、sysfs、proc、debugfs、configfs等(使用mount可以看到他们),这些都是虚拟文件系统,由操作系统自动管理,备份时不需要关注。日常使用时,修改的文件包括安装的软件都是在根分区中,而如果自行编译内核,需要更新的文件都在/boot中。
所以备份一个系统,实际上是要备份这两个分区,官方发布的烧录镜像,也是包含了这样的两个分区,并保证通过dd的操作,能将其完整写入目标TF卡。首次烧录完毕后,不论你的TF卡容量为多少,启动后的boot和/分区大小都是固定的,然后可以使用raspi-config来扩展根分区的大小,boot分区不变,来达到使用所有卡内容量的目的。
相对应的备份步骤,大致为:创建img,把img当作一个磁盘分区和格式化,mount各个分区,将文件备份至对应的分区中,umount分区结束备份。
既然是备份到文件,那么首先需要创建一个备份文件,并且把这个文件看作一个虚拟设备,对其进行分区。
需要用到的工具有_parted, kpartx, dosfstools, rsync_,使用apt-get安装:
sudoapt-get-yinstallrsyncdosfstoolspartedkpartx创建新的文件
我们用dd命令来创建一个新的img,img的大小和当前RaspberryPi已使用的存储空间有关。使用df -P来查看磁盘空间状态(以1K为单位),会看到如下的信息:
其中红框的部分就是我们需要关注的,将它们对应的Used数目取出,计算得到需要创建的img大小:
img=rpibackup.img
#sudorm$img bootsz=df-P|grep/boot|awk'{print$2}' rootsz=df-P|grep/dev/root|awk'{print$3}' totalsz=echo$bootsz$rootsz|awk'{printint(($1+$2)*1.3)}' sudoddif=/dev/zeroof=$imgbs=1Kcount=$totalsz
这里的totalsz是基于两个分区使用总和的1.3倍,一方面是分区表、格式化等操作造成的空间损失,另一方面是系统对剩余空间的要求。我曾经尝试过使用1.1,然而系统启动后在终端大部分命令都报错,说空间不足。
将文件分区工具parted可以用来把一个img文件当作一个磁盘来分区。使用fdisk -l能够看到系统中各个磁盘分区的情况,在我的树莓派上系统TF卡的分区情况如下:
我们把boot分区设计为跟原分区大小一样,root分区则是扩展到img文件的末尾。对应的脚本:
bootstart=sudofdisk-l/dev/mmcblk0|grepmmcblk0p1|awk'{print$2}' bootend=sudofdisk-l/dev/mmcblk0|grepmmcblk0p1|awk'{print$3}' rootstart=sudofdisk-l/dev/mmcblk0|grepmmcblk0p2|awk'{print$2}' echo"boot:$bootstart>>>$bootend,root:$rootstart>>>end" rootend=sudofdisk-l/dev/mmcblk0|grepmmcblk0p2|awk'{print$3}' sudoparted$img--script--mklabelmsdos sudoparted$img--script--mkpartprimaryfat32${bootstart}s${bootend}s sudoparted$img--script--mkpartprimaryext4${rootstart}s-1
分别格式化fat32和ext4分区
然后对分区分别进行格式化,我们可以通过loop来建立虚拟的磁盘挂载点,进行后续的操作:
loopdevice=sudolosetup-f--show$img device=/dev/mapper/sudokpartx-va$loopdevice|sed-E's/.*(loop[0-9])p.*/\1/g'|head-1 sleep5 sudomkfs.vfat${device}p1-nboot sudomkfs.ext4${device}p2
在中间的这个sleep是我在实践中发现,创建loop的设备节点需要一些时间,如果手动在终端贴命令则没有问题,而若是用脚本执行,则在format时节点还没有创建好,从而造成格式化失败。
备份Boot分区Boot分区是Fat32格式且数据不多,直接mount然后copy数据即可,注意权限问题。
mountb=$usbmount/backup_boot/ mkdir-p$mountb sudomount-tvfat${device}p1$mountb sudocp-rfp/boot/*$mountb sync sudoumount$mountb备份根(root)分区
根分区的备份是我折腾了很久的部分。因为它文件众多,不止一个文件夹,而且在系统运行时有一些文件、文件夹是临时生成或者mount的,不能全部直接copy。这就需要有一种方法能区分真正写在磁盘上的文件并将其备份。
失败的经验:dump&restore在开头提到的道友那篇文章里,是采用了针对ext4备份和恢复的工具dump和restore。
sudomount-text4$partRoot/media/ cd/media sudodump-0uaf-/|sudorestore-rf- cd;sudoumount/media
但在关键的步骤出现了 Broken pipe错误,经过重复测试多次,虽然每次报错的文件和inode不一样,但每次都无法顺利完成。这个问题在原博的评论中也有人遇到了:
restore:./lost+found:Fileexists ./tmp/rstdir1445584846:(inode159534)notfoundontape ./tmp/rstmode1445584846:(inode161527)notfoundontape DUMP:Brokenpipe DUMP:TheENTIREdumpisaborted. ………………
针对这个问题我做了一些测试。首先是把sudo dump -0uaf - / | sudo restore -rf -分开,取消管道,先dump到一个文件再restore。发现dump总是成功的,而问题出在restore上,所以就broken pipe了。开始怀疑是restore的时候权限有问题,后来把dump出来的文件拿到PC上做restore,也同样是失败。这样就很有可能是dump的文件有问题,仔细看看每次出错的文件都是隐藏或者临时文件,怀疑跟这个有关,但找了一大圈dump相关的参数尝试,依然是没有解决。
使用rsync备份由于每次测试dump和restore花的时间很长,项目又比较急,我实在是不能再耽搁下去了,于是决定放弃这种方式。想了一下,曾经用过rpi-clone来做卡与卡之间的备份,那么备份成文件也应该是一样的。我于是去看了下它的源码,把根文件系统备份相关的部分提出来就能直接用起来了。这里用的是备份工具rsync,其本质上是基于文件系统之上的的直接拷贝,因为没有用到增量备份,所以跟cp其实是一回事。只是rsync能很好的保留各种权限、时间戳、软链接和文件信息,避免了一些用cp的问题。保险起见,我依然沿用了它。
首先还是mount一下:
mountr=$usbmount/backup_root/ mkdir-p$mountr sudomount-text4${device}p2$mountr
然后要对存在swap分区的情况进行特殊处理:
if[-f/etc/dphys-swapfile];then SWAPFILE=`cat/etc/dphys-swapfile|grep^CONF_SWAPFILE|cut-f2-d=` if["$SWAPFILE"=""];then SWAPFILE=/var/swap fi EXCLUDE_SWAPFILE="--exclude$SWAPFILE" fi
接着就是所有文件的备份,跳过某些临时、系统自动生成的文件和文件夹:
sudorsync--force-rltWDEgopt--delete--stats--progress$EXCLUDE_SWAPFILE--exclude'.gvfs'--exclude'/dev'--exclude'/media'--exclude'/mnt'--exclude'/proc'--exclude'/run'--exclude'/sys'--exclude'/tmp'--exclude'lost\+found'--exclude'$usbmount'//$mountr
完成后新建一些特殊文件夹,用来做系统启动时某些文件的自动挂载点,例如proc和mnt等等:
foriindevmediamntprocrunsysboot;do if[!-d$mountr/$i];then sudomkdir$mountr/$i fi done if[!-d$mountr/tmp];then sudomkdir$mountr/tmp sudochmoda+w$mountr/tmp fi
到这里基本上就完成了,有一点小小的修改就是把网络配置文件删掉,以免换了新的网络环境启动系统无法自动配置:
sudorm-f$mountr/etc/udev/rules.d/70-persistent-net.rules sync
最后umount,清理loop设备,结束备份:
sudoumount$mountr #umountloopdevice sudokpartx-d$loopdevice sudolosetup-d$loopdevice sudoumount$usbmount rm-rf$mountb$mountrMount USB device
使用dump/restore的时候,有一个跳过特定文件的参数,在前文提到的教程中,作者是将img放到用户目录,并且跳过这个文件的。虽然使用rsync备份的时候,依然可以这么做,但考虑到通用性和调试的方便,我把文件直接备份在了外接的U盘上,以避免考虑各种“自己备份自己”可能带来的问题。顺便把相关的USB mount脚本和判断也贴一下供参考:
usbmount=/mnt mkdir-p$usbmount if[-z$1];then echo"noargument,assumethemountdeviceis/dev/sda1?Y/N" readkey if["$key"="y"-o"$key"="Y"];then sudomount-tvfat-ouid=1000/dev/sda1$usbmount else echo"$0[backupdestdevicename],e.g.$0/dev/sda1" exit0 fi else sudomount-tvfat-ouid=1000$1$usbmount fi if[-z"`grep$usbmount/etc/mtab`"];then echo"mountfail,exitnow" exit0 fi
当然,如果你有兴趣,大可以自己改一改尝试直接备份在用户目录,只是我感觉依然是需要copy出来,意义不大。??
恢复备份备份img做好了,恢复备份就无需多谈了。既然是用树莓派的小伙伴,自然有一百种方法将镜像烧录到卡上。实在不知道的,放官方链接:Installing images,或者看这篇:树莓派初始配置指南(2代B型)。
最后,我再补充一点,恢复备份的系统可能会出现系统盘只有几G的情况
1.扩容步骤:使用树莓派自带的工具可以快速将树莓派挂载所有的 SD卡空间,将系统分区扩展到 SD卡的最大容量
(1)打开raspi-config系统配置工具
sudoraspi-config
(2)选择Advancd Options:
整理后完整的script我已经更新到GitHub:https://github.com/conanwhf/RaspberryPi-script/blob/master/rpi-backup.sh,如果对你有所帮助,欢迎Star!??有什么疑问和质疑,也欢迎砸过来!??
顺便提一下,写这篇文章的时候,我发现官方已经release了一个备份工具,piclone,看上去是在两个TF卡之间备份,跟rpi-clone功能一样。我还没有测过,有兴趣的小伙伴可以试一下,看看源码,也许/的备份能有新的思路和方法。
有话要说...