想做个树莓派的img镜像,然而对SD卡进行全盘复制很浪费空间,且不能恢复到比现有SD卡容量小的卡上,因此探索制作小img的方法,网上看了大神制作的脚本,比如https://github.com/conanwhf/RaspberryPi-script/blob/master/rpi-backup.sh和https://github.com/elespec/rpi-backup/blob/master/rpi-backup.sh,然而做好之后在我的树莓派上无法启动。最终参考了这篇文章的方法:手动一步一步来制作备份Raspberry Pi树莓派SD卡的img映像文件(不用dd命令),感谢原作者分享。结合这些修改成了一个脚本。我的树莓派是Raspbain Stretch系统。 使用时将树莓派的SD卡插到PC上,用PC制作img镜像,最后img文件会放在~/bakcup_img/文件夹下,测试所用PC的操作系统是Ubuntu16.04。


目录

  • 查看SD卡挂载信息
  • 脚本自动备份
  • 手动备份
  • 准备工作
  • 创建空白img文件
  • 格式化img文件分区并挂载
  • 备份/boot
  • 备份"/"
  • 相应修改PARTUUID设定
  • 修改 cmdline.txt 文件
  • 修改fstab文件
  • 收尾


查看SD卡挂载信息

PC端SD卡已经自动挂载好了,使用df -h查看SD卡对应的设备名和使用的空间大小

df -h

创建一个raw镜像 创建img镜像_设备名


设备名/dev/mmcblk0p1/dev/mmcblk0p2,根据空间大小判断/dev/mmcblk0p1/boot/dev/mmcblk0p2/(根)

根据原文步骤,简化了一些写成脚本,可以先按照脚本自动备份这一节提供的脚本运行,也可以按照手动备份的方法一步步执行。

脚本自动备份

将脚本复制到一个文本文件中,假设名字为rpi-backup.sh,需要赋可执行权限,脚本执行有两个参数,第一个参数是树莓派SD卡/boot分区的设备名,第二个参数是/分区的设备名。最终img文件会生成在~/backupimg/文件夹下。

sudo chmod +x rpi-backup.sh
./rpi-backup.sh /dev/mmcblk0p1 /dev/mmcblk0p2

脚本内容如下:

#!/bin/sh

if [  $# != 2 ]; then
  echo "argument error: Usage: $0 boot_device_name root_device_name"
  echo "example: $0 /dev/mmcblk0p1 /dev/mmcblk0p2"
  exit 0
fi
dev_boot=$1
dev_root=$2
mounted_boot=`df -h | grep $dev_boot | awk '{print $6}'`
mounted_root=`df -h | grep $dev_root | awk '{print $6}'`
img=rpi-`date +%Y%m%d-%H%M`.img

#install tools
sudo apt-get install dosfstools dump parted kpartx

echo =====================   part 1, prepare workspace    ===============================
mkdir ~/backupimg
cd ~/backupimg

echo ===================== part 2, create a new blank img ===============================
# New img file
#sudo rm $img

bootsz=`df -P | grep $dev_boot | awk '{print $2}'`
rootsz=`df -P | grep $dev_root | awk '{print $3}'`
totalsz=`echo $bootsz $rootsz | awk '{print int(($1+$2)*1.3/1024)}'`
sudo dd if=/dev/zero of=$img bs=1M count=$totalsz
#sync
echo "...created a blank img, size ${totalsz}M "

# format virtual disk
bootstart=`sudo fdisk -l | grep $dev_boot | awk '{print $2}'`
bootend=`sudo fdisk -l | grep $dev_boot | awk '{print $3}'`
rootstart=`sudo fdisk -l | grep $dev_root | awk '{print $2}'`
echo "boot: $bootstart >>> $bootend, root: $rootstart >>> end"
#有些系统 sudo fdisk -l 时boot分区的boot标记会标记为*,此时bootstart和bootend最后应改为 $3 和 $4
#rootend=`sudo fdisk -l /dev/mmcblk0 | grep mmcblk0p2 | awk '{print $3}'`

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

echo =====================  part 3, mount img to system  ===============================
loopdevice=`sudo losetup -f --show $img`
device=/dev/mapper/`sudo kpartx -va $loopdevice | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1`
sleep 5
sudo mkfs.vfat ${device}p1 -n boot
sudo mkfs.ext4 ${device}p2 -L rootfs
#在backupimg文件夹下新建两个文件夹,将两个分区挂载在下面
mkdir tgt_boot tgt_Root
#这里没有使用id命令来查看uid和gid,而是假设uid和gid都和当前用户名相同
uid=`whoami`
gid=$uid
sudo mount -t vfat -o uid=${uid},gid=${gid},umask=0000 ${device}p1 ./tgt_boot/
sudo mount -t ext4 ${device}p2 ./tgt_Root/


echo ===================== part 4, backup /boot =========================
sudo cp -rfp ${mounted_boot}/* ./tgt_boot/
sync
echo "...Boot partition done"

echo ===================== part 5, backup / =========================
sudo chmod 777 ./tgt_Root
sudo chown ${uid}.${gid} tgt_Root
sudo rm -rf ./tgt_Root/*
cd tgt_Root/
# start backup
sudo dump -0uaf - ${mounted_root}/ | sudo restore -rf -
sync 
echo "...Root partition done"
cd ..

echo ===================== part 6, replace PARTUUID =========================

# replace PARTUUID
opartuuidb=`sudo blkid -o export $dev_boot | grep PARTUUID`
opartuuidr=`sudo blkid -o export $dev_root | grep PARTUUID`
npartuuidb=`sudo blkid -o export ${device}p1 | grep PARTUUID`
npartuuidr=`sudo blkid -o export ${device}p2 | grep PARTUUID`
sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_boot/cmdline.txt
sudo sed -i "s/$opartuuidb/$npartuuidb/g" ./tgt_Root/etc/fstab
sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_Root/etc/fstab
echo "...replace PARTUUID done"

echo "remove auto generated files"
#下面内容是删除树莓派中系统自动产生的文件、临时文件等
cd ~/backupimg/tgt_Root
sudo rm -rf ./.gvfs ./dev/* ./media/* ./mnt/* ./proc/* ./run/* ./sys/* ./tmp/* ./lost+found/ ./restoresymtable
cd ..

echo ===================== part 7, unmount =========================
sudo umount tgt_boot tgt_Root
sudo kpartx -d $loopdevice
sudo losetup -d $loopdevice
rmdir tgt_boot tgt_Root


echo "==== All done. img file is under ~/backupimg/ "

手动备份

准备工作

安装工具软件

sudo apt-get install dosfstools dump parted kpartx

建立工作目录

mkdir ~/backupimg
cd ~/backupimg

创建空白img文件

源SD卡已使用空间大概3.5G,就创建了4600M大小的img文件

sudo dd if=/dev/zero of=raspberrypi.img bs=1M count=4600

原博客中有提示:

特别注意这里 bs=1M,千万不要写成1MB
1M是1024*1024 Bytes, 而1MB是1000*1000 Bytes,会造成img文件的大小不是512 bytes的整数倍,后面会报错。

创建一个raw镜像 创建img镜像_创建一个raw镜像_02


查看分区表

sudo fdisk -l

创建一个raw镜像 创建img镜像_创建一个raw镜像_03


给img文件分区

按照原来的起始位置设置分区

注:分区的起始扇区数都是 8192 的倍数,保证4K对齐

sudo parted raspberrypi.img --script -- mklabel msdos
sudo parted raspberrypi.img --script -- mkpart primary fat32 8192s 96042s
#第二个分区的终止点为img文件的末尾
sudo parted raspberrypi.img --script -- mkpart primary ext4 98304s -1

检查分区是否成功

sudo parted raspberrypi.img

创建一个raw镜像 创建img镜像_创建一个raw镜像_04


在(parted)后面输入print free,最后输入quit退出。

格式化img文件分区并挂载

查看img文件对应的Loop device的设置

sudo losetup -f --show raspberrypi.img

创建一个raw镜像 创建img镜像_树莓派_05

下面的命令中相应输入/dev/loop0,如果不是 loop0 请做相应调整(以及以后的各个步骤里的loop0都要改变)

sudo kpartx -va /dev/loop0

终端输入ls /dev/mapper/loop0p*应该是有/dev/mapper/loop0p1 /dev/mapper/loop0p2两个设备。
其中loop0p1是/boot,loop0p2是根。
格式化、挂载:

sudo mkfs.vfat -n boot /dev/mapper/loop0p1
sudo mkfs.ext4 -L rootfs /dev/mapper/loop0p2
#在backupimg文件夹下新建两个文件夹,将两个分区挂载在下面
mkdir tgt_boot tgt_Root
sudo mount -t vfat -o uid=zk,gid=zk,umask=0000 /dev/mapper/loop0p1 ./tgt_boot/
sudo mount -t ext4 /dev/mapper/loop0p2 ./tgt_Root/

上面的uid和gid根据自己系统的用户名有所变化。

备份/boot

直接拷贝
下面命令中的/media/zk/boot/*要根据自己系统中树莓派SD卡的挂载位置进行修改。

sudo cp -rfp /media/zk/boot/* ./tgt_boot/

备份"/"

原文中提供了两种方法,本人尝试时只用了第一种:dump/restore方法
对目标挂载点设置合适的权限,并清空

sudo chmod 777 ./tgt_Root
#下面的zk.zk与上文提到的uid,gid设置一致,需要根据系统的用户名去修改
sudo chown zk.zk tgt_Root
sudo rm -rf ./tgt_Root/*
cd tgt_Root/

开始备份
#下面命令中的/media/zk/rootfs/要根据自己系统中树莓派SD卡的挂载位置进行修改

sudo dump -0uaf - /media/zk/rootfs/ | sudo restore -rf -
#返回上层目录
cd ..

创建一个raw镜像 创建img镜像_设备名_06


耗时几分钟,运行完后可以查看下tgt_Root下是否已把文件备份过来

相应修改PARTUUID设定

这时候整个备份就已经完成了。不过此时的img文件即使写入到空白SD卡里面也是无法启动的,因为Raspbian启动要对应分区的PARTUUID,所以我们还要修改目标img文件里的如下两个文件:

./tgt_boot/cmdline.txt 
./tgt_Root/etc/fstab

首先查看img文件对应的loop device的两个分区的PARTUUID

sudo blkid

在输出的信息里查看到:
/dev/mapper/loop0p1PARTUUID是“961e1351-01”,
/dev/mapper/loop0p2PARTUUID是“961e1351-02”。

修改 cmdline.txt 文件

将其中root=PARTUUID=af2f8761-02部分改为/dev/mapper/loop0p2(即根分区)的PARTUUID,修改后如下:

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=961e1351-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet splash plymouth.ignore-serial-consoles

修改fstab文件

proc            /proc           proc    defaults          0       0
PARTUUID=961e1351-01  /boot           vfat    defaults          0       2
PARTUUID=961e1351-02  /               ext4    defaults,noatime  0       1
# a swapfile is not a swap partition, no line here
#   use  dphys-swapfile swap[on|off]  for that

收尾

卸载各个挂载的分区,删除loop device,删除挂载点目录

sudo umount tgt_boot tgt_Root
sudo kpartx -d /dev/loop0
sudo losetup -d /dev/loop0
rmdir tgt_boot tgt_Root

完成之后可以用dd或者Etcher烧写img文件到其他SD卡中,注意烧写到新卡中在树莓派中运行后,要用raspi-config先把分区空间expand一下,否则可用空间会很小。