如何创建一个不包括空闲空间的磁盘(SD卡).IMG镜像?

54
在Linux中,我们可以进行如下操作:
dd if=/dev/sdb of=bckup.img

但如果磁盘容量为32GB,仅使用了4GB,则32GB的镜像文件就是浪费空间和时间。是否有任何方法或工具可以创建仅包含有效数据的映像文件?

7个回答

60

解决这个问题的一个非常好而简单的方法是通过gzip进行管道传输,类似于以下内容:

# dd if=/dev/sdb | gzip > backup.img.gz

这样,您的图像将被压缩,可能未使用的空间将被挤压至几乎为零。

您可以使用此方法来恢复此类图像:

# cat backup.img.gz | gunzip | dd of=/dev/sdb

需要注意的一点是,如果你最近删除了很多文件,图像大小可能仍然很大(删除文件并不一定会将基础扇区置零)。您可以通过创建并立即删除包含零的大文件来擦除空闲空间:

# cd /media/flashdrive
# dd if=/dev/zero of=bigfile bs=1M     # let it run and quit by disk full error
# rm bigfile

2
全新SD卡的空闲空间是否为零? - Necktwi
7
您的解决方案仍然会向SD卡写入32GB的数据,这是一个非常耗时的过程。 - Necktwi
2
好的,你可以简单地使用命令 tar cvfz mybackup.tar.gz /media/flashdrive 进行备份。它会捕获除可启动的USB驱动器之外的所有内容,它们将失去可启动状态。 - mvp
2
是的,tar 会丢失 MBR,您需要使用 boot-repair 来使驱动器再次可引导。但是,除此之外,tar 应该会保留一切并且操作系统应该是完全可用的(但请确保以 root 用户身份运行 tar!)。 - mvp
4
现代SD卡通常可以使用"fstrim"清零剩余空间,这样做的优点是速度快且避免了闪存磨损。如果要在VFAT分区上支持fstrim,请至少使用Linux内核版本4.19。不幸的是,许多USB SD卡读卡器/写卡器不支持discard/trim。如果您想尝试,请尝试“fstrim -v /路径到已挂载文件系统/”。我在从事专业嵌入式空气质量传感器的生产图像编写时使用此方法。 - Tim Small
显示剩余8条评论

17

最好的做法是:

  1. 复制所有分区中的文件,并保留元数据

    mkdir -p myimage/partition1

    mkdir myimage/partition2

    sudo cp -rf --preserve=all /media/mount_point_partition1/* myimage/partition1/

    sudo cp -rf --preserve=all /media/mount_point_partition2/* myimage/partition2/

  2. 提取MBR

    sudo dd if=/dev/sdX of=myimage/mbr.img bs=446 count=1

    用相应的设备替换 /dev/sdX

  3. 使用 gparted 将目标磁盘分区,分区大小应大于复制的数据,并且应具有相同的格式和相同的标志。Google如何分区硬盘。

  4. 挂载刚刚格式化和分区的磁盘。在大多数计算机上,只需要连接磁盘即可在 /media 文件夹中找到挂载的分区。

  5. 使用以下命令将之前复制的数据复制到目标分区

    sudo cp -rf --preserve=all myimage/partition1/* /media/mount_point_partition1/

    sudo cp -rf --preserve=all myimage/partition2/* /media/mount_point_partition2/

  6. 将MBR复制回来

    sudo dd if=myimage/mbr.img of=/dev/sdX bs=446 count=1

现在,享受您的新磁盘吧!


1
有人尝试过这个并确认它有效吗?否则,我将只提供cp命令中的-r-R之一:它们是相同的开关。 - Jealie
1
我尝试过这个方法,它有效。我克隆了我的树莓派启动SD卡。该卡有2个分区,一个是fat格式,另一个是ext4格式。我使用提供的命令转储了这些分区,然后用零dd整个SD卡,创建了新的分区表,创建了具有不同参数(大小、间距、标签)的分区。之后我从转储中恢复了数据,一切正常。 - CoderFF
这个方法很有效,但你也可以使用gparted一步调整分区大小。备份仍然是个好主意。 - Scott S
当你说 MBR 时,你可能指的是 Bootloader,它的大小为446个字节 - 这是你正在提取的数量。实际上,MBR 的大小是512个字节。 - Ryan Amaral

7
使用ddbscount参数,你可以限制图像的大小,如答案1665017中的步骤2所示。
如果你不知道要创建什么大小的图像,可以从df中得到一个好的想法。
df -H --total /

/ 替换为与磁盘分区相关的挂载点列表,用空格分隔。

更准确的方法可能是使用 fdisk 或您喜欢的分区编辑器,并使用计算器进行操作。

$ fdisk -l /dev/mmcblk0

Disk /dev/mmcblk0: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00057540

Device         Boot  Start     End Sectors  Size Id Type
/dev/mmcblk0p1        2048  186367  184320   90M  c W95 FAT32 (LBA)
/dev/mmcblk0p2      186368 3667967 3481600  1.7G  5 Extended
/dev/mmcblk0p5      188416 3667967 3479552  1.7G 83 Linux

总使用空间(以字节为单位)=最后一个分区的结束扇区 X 扇区大小(这里是 3667967 x 512)。

总使用空间(以GB为单位)= 总使用空间(以字节为单位)/ 10243 (这里是1.749023 GB)。

如果您决定,例如,您的图像应该正好是2 GB,则以下命令将执行此操作:

dd if=/dev/mmcblk0 of=/path/to/pi_updated.img bs=1M count=2048

生成的图像还将包括最后一个分区范围之外的随机噪声。
如果您的帐户没有足够的权限,请不要忘记使用sudo上述命令。
对于我的目的,我不需要将图像完全修剪到数据的最后一位,因此当实际大小为1.75 GB时,2 GB的图像就足够了。这样可以删除图像中的其他6 GB(或设备可用的30 GB或更多)未使用空间。
我在许多地方看到过建议不要在已挂载的分区上执行dd,我遵循了这个建议,因为它似乎是直觉上正确的;这确实有点像试图在镜子里画自己正在画的素描,并且你正在制作的素描也在素描中可见。这有点靠不住。

2
在我看来,在计算时,扇区大小应该基于End+1而不是End。在您的情况下,它应该是3667968。 - scegg
如果分区不按顺序排列,则此方法将无法正常工作。例如,如果空白空间出现在分区之前。 - xZero

6

在尝试多种不同的方法后,我找到了以下文章:

https://medium.com/platformer-blog/creating-a-custom-raspbian-os-image-for-production-3fcb43ff3630

它被创建用于缩小和调整大小(在第一次启动时)树莓派镜像,但可以轻松调整为任何其他Linux发行版。我成功地在基于自定义ARM芯片的Debian 9上使其工作。由pishrink脚本创建的rc.local首先使用raspi-config调整根文件系统大小,然后退回到使用parted的方法(我不得不提前在我的机器上安装)。我注释掉了代码部分,其中使用了raspi-config。我的SD卡映像从15 GB缩小到了1.1 GB。我使用etcher刷写了缩小后的SD卡映像。只需要不到5分钟,而完整的15 GB映像需要半个多小时。

4

稀疏(收缩)图像克隆指南:

将带有映像文件的可引导Ubuntu持久化Live USB插入单板电脑(例如UP² Board)。启动到USB(默认持久化Live选项)。 启动终端并输入以下命令(一行):

sudo dd if=/media/ubuntu/usbdata/ubuntu.img of=/dev/mmcblk0 bs=1M status=progress

等待15-20分钟。完成后,关闭并断开USB。启动内部eMMC驱动器。

创建映像:

从内部eMMC创建未压缩的稀疏映像文件,并保存到闪存驱动器中。

sudo dd if=/dev/mmcblk0 bs=1M status=progress | cp --sparse=always /dev/stdin /media/ubuntu/usbdata/ubuntu.img

提取未压缩的图像文件到内部 eMMC
sudo dd if=/media/ubuntu/usbdata/ubuntu.img of=/dev/mmcblk0 bs=1M status=progress

从内部 eMMC 创建压缩图像文件并保存到桌面
sudo dd if=/dev/mmcblk0 bs=1M status=progress | gzip > /home/user/Desktop/ubuntu.img.gz

将压缩的镜像文件提取到内部 eMMC 中

sudo gzip -cd /home/user/Desktop/ubuntu.img.gz | dd of=/dev/mmcblk0 bs=1M status=progress

要修改现有的Linux镜像或驱动器,需要在Linux中插入驱动器或挂载镜像:
来源: http://dustymabe.com/2012/11/15/create-a-disk-image-without-enough-free-space/ https://forum.up-community.org/discussion/1197/solved-cloning-the-image-of-an-up-board
sudo mkdir /media/root
sudo mount -t ext4 -o loop,offset=537919488 /media/user/usbdata/ubuntu.img  /media/root

要确定您的图像或驱动器的偏移量,请运行:

fdisk -l /media/user/usbdata/ubuntu.img

然后,将512字节的块大小乘以Linux分区的起始块1050624。512*1050624 = 537,919,488。

同时挂载其他系统文件夹:

for i in /dev /dev/pts /proc /sys /run; do sudo mount -B $i /media/root$i; done

然后进入img的根目录并使用chroot命令:

cd /media/root
sudo chroot .

然后您可以更新文件、密码等。要卸载,请运行:

for i in /dev /dev/pts /proc /sys /run; do sudo umount /media/root$i; done
sudo umount /media/root

创建Ubuntu持久化Live USB镜像 按照以下步骤使用持久化live Ubuntu安装程序来刷写USB驱动器:https://www.howtogeek.com/howto/14912/create-a-persistent-bootable-ubuntu-usb-flash-drive/

3

如果您有一张大的SD卡,比如16GB、32GB等,但想要通过备份节省空间,您可以使用:

sudo apt-get install gnome-disk-utility

打开磁盘工具检查你的USB驱动器实际上是哪个字母:

gnome-disks

在我的情况下,一张带有Raspbian镜像的32GB SD卡被识别为:/dev/sde

因此我使用 /dev/sde 运行:

sudo dd bs=4M status=progress if=/dev/sde | gzip > \
/you-selected-full-path-here/raspberry-images/`date +%Y%m%d`_rpi_image_backup.gz

status=progress gives you progress bar indication
| gzip > compresses the 32 GB total size and not writing the empty space from the 32 GB
`date +%Y%m%d` writes today date in the filename

输出文件名: 20190529_rpi_image_backup.gz

而且大小只有3.5GB。 如果你想将这个映像写入新的SD卡,请使用:

https://www.balena.io/etcher/

此外,你可以将这个32 GB的映像写入16 GB或8 GB的磁盘中,不再提示映像太大的错误。


2
我尝试了您提供的步骤,用于我的64 GB SD卡,该卡有2个分区。它们的总大小约为7GB。然而,几分钟后我发现进程并未完成,创建的映像大小超过了25GB。我又试了一遍,并使用了“gzio -9”,结果几乎相同。为什么我没有一个7GB的映像? - ywiyogo

1

我的解决方案是制作一个定制化的Raspios镜像:

DIR="./img"
DEVICE=/dev/sdb

# declare custom copy functions
copy() {
  echo "[$1] copy $2 to $3..."
  if [ "$1" == 'ext' ]; then
    sudo rsync -aHAXX \
      --no-inc-recursive \
      --numeric-ids \
      --filter='-x security.selinux' \
      --delete \
      --exclude={"/lost+found"} \
      --info=progress2 \
      $2 \
      $3
  else
    sudo rsync -rtD \
      --modify-window=1 \
      --no-inc-recursive \
      --delete \
      --exclude={"/lost+found"} \
      --info=progress2 \
      $2 \
      $3
  fi
}

# create working directories
mkdir -p $DIR/{mount,image}/{boot,rootfs}

# mount initial device partitions
sudo mount ${DEVICE}1 $DIR/mount/boot
sudo mount ${DEVICE}2 $DIR/mount/rootfs

# preserve partitions UUIDs
PTUUID=0x`sudo blkid -p $DEVICE | sed -e 's/.*PTUUID="\([0-9a-f]*\)".*/\1/'`
BOOTUUID=`sudo blkid -p ${DEVICE}1 | sed -e 's/.* UUID="\([0-9a-zA-Z\-]*\)".*/\1/' | tr -d '-'`
ROOTUUID=`sudo blkid -p ${DEVICE}2 | sed -e 's/.* UUID="\([0-9a-zA-Z\-]*\)".*/\1/'`

# copy contents from physical device to local folder
copy fat $DIR/mount/boot/   $DIR/image/boot/
copy ext $DIR/mount/rootfs/ $DIR/image/rootfs/
umount ${DEVICE}1
umount ${DEVICE}2

# show contents size
BOOTSIZE=`sudo du -hs $DIR/image/boot`
ROOTSIZE=`sudo du -hs $DIR/image/rootfs`
echo "boot is $BOOTSIZE"
echo "root is $ROOTSIZE"

# allocate the final .img file
# change size as needed
SIZE="5"
echo "allocating ${SIZE}G image..."
rm $DIR/$IMAGE
fallocate -l ${SIZE}G $DIR/$IMAGE

# create partitions
echo "formatting image..."
sed -e 's/\s*\([\+0-9a-zA-Z]*\).*/\1/' << EOF | fdisk $DIR/$IMAGE > /dev/null
o          # create MBR partition scheme
n          # new partition
p          # primary
1          # partition #1
8192       # start block
532479     # end block
t          # define type
0c         # W95 FAT32 (LBA)
n          # new partition
p          # primary
2          # partition #2
532480     # start block
           # last end block
t          # define type
2          # for partition #2
83         # linux
x          # advanced menu
i          # change disk identifier
$PTUUID    # identifier from original fstab
r          # return to normal menu
w          # write changes
EOF

# locally mount the .img
echo "mounting image..."
LOOP=`sudo losetup -fP --show $DIR/$IMAGE`
echo "mounted image under $LOOP"
ls -la $LOOP*

echo "creating partition boot with FAT32"
sudo mkfs.fat \
  -F 32 \
  -S 512 \
  -i $BOOTUUID \
  -n boot \
  ${LOOP}p1

echo "creating partition rootfs with ext4"
sudo mkfs.ext4 \
  -b 4096 \
  -U $ROOTUUID \
  -L rootfs \
  ${LOOP}p2

echo "mounting image loops"
sudo mount ${LOOP}p1 $DIR/mount/boot
sudo mount ${LOOP}p2 $DIR/mount/rootfs

echo "copying contents to image"
copy fat $DIR/image/boot/   $DIR/mount/boot/
copy ext $DIR/image/rootfs/ $DIR/mount/rootfs/

echo "unmounting image"
unmount "$LOOP+" > /dev/null
echo "removing loop"
sudo losetup -d ${LOOP}

我要试一下这个 :) 但是为什么rsync不会复制符号链接等内容呢? - Dumbo
1
它是做什么的:https://explainshell.com/explain?cmd=rsync+-aHAXX++--no-inc-recursive+--numeric-ids++--filter%3D%27-x+security.selinux%27+--delete++--exclude%3D%7B%22%2Flost%2Bfound%22%7D++--info%3Dprogress2 - Pierre-Alexis Ciavaldini
这个脚本最好的情况下是有错误的,最糟糕的情况下非常危险。它可能会覆盖数据。至少要确保你有足够的空间,并且注意这个脚本需要大约两倍的空间。 - Isius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接