如何缩小由 dd 创建的 img 文件的分区?

如何缩小由 dd 创建的 img 文件的分区?

我已使用以下命令将 USB 驱动器复制到一个.img文件中dd

dd if=/dev/sdc of=myimage.img

我想减小映像中分区的大小。我尝试了几种方法,但最终得到的都是回送挂载映像,其分区仍然是 USB 的完整大小。

  • 一旦回送安装完毕,我该如何修改myimage.img以获得更小的分区?

  • 在执行此操作之前,我是否需要将零复制到分区的空白部分?

  • 我是否需要进行碎片整理,以便在减小图像大小时删除空字节?(据我所知,Linux 扩展到整个分区,因此我不期望图像末尾的所有数据都是零字节。即使写入全零,也只会消耗空字节,无论它们位于何处。)

注意:我并不想节省磁盘空间,因此压缩对我没有帮助。

背景

我使用 把 Linux 安装在 USB 驱动器上ext4。我打算为多个设备复制安装。我已经成功完成了,但我想在同一个 USB 驱动器上创建一个只读分区,其中包含系统和一个允许持久存储的小分区。我不想破坏我的 USB,而是试图修改 USB 的副本。我希望我们不要被这个背景分散注意力。

简而言之,我做了以下事情:

# Create mount point in current directory
sudo mkdir mnt
# Loopback mount the image
fdisk -l myimage.img
sudo mount -o offset=<partion_block_start * block_size> myimage.img mnt
# Copy all zeros to remaining space of the image
cd mnt
sudo dd if=/dev/zero of=filler conv=fsync bs=1M
rm filler
cd ..

parted首先,我尝试使用这个超级用户的回答qemu-img如所述其他超级用户的回答

sudo umount mnt
parted myimage.img
# At parted command prompt
(parted) resizepart 1
# Entered my end <target size>. Note that parted uses zero-based 
# indexing. This could be your final image size. In my case, the way the
# Linux installer worked, the partition started at 1M.
(parted) print
# I see that the partition is now sized as I expect
(parted) exit
# Just another sanity check
sudo parted -m esp3_007.img unit B print
# I see that the partition is now sized as I expect

当我尝试再次安装映像时,它工作正常,但df仍然显示分区大小相同。所以我尝试

qemu-img resize myimage.img <target_size>

现在,当我尝试挂载映像时,出现“挂载:错误的 fs 类型、错误的选项、错误的超级块......”错误消息。

然后我尝试gparted按照描述使用这篇站外帖子parted。除非我首先运行,否则 GUI 会显示已满分区。即便如此,GUI 也不会让我调整分区大小。

为了强制分区大小变小,并从myimage.img我尝试过的新的副本开始fdisk,在这个 AskUbuntu 答案

sudo fdisk myimage.img
Command (m for help): d
Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4, default 1): 1
# defaults on the rest seemed to be correct in my case.

当我安装该分区时,它仍然显示为相同的大小。

答案1

循环播放图像

首先,忘记offset=,使用losetup --partscan并仅通过挂载分区/dev/loop0p1

# losetup --partscan /dev/loop0 myimage.img
# lsblk
# mount /dev/loop0p1 /mnt

为了高效地清除分区内的空白空间,请fstrim在循环挂载的文件系统上运行,就像在 SSD 上运行一样。(这实际上会使映像文件变得稀疏。)

# fstrim -v /mnt

关于调整分区大小

但目前,您无需清除空白区域或执行任何类似操作。即将被截断的区域是否用零或大块旧数据填充完全无关紧要。

相反,您需要执行的操作与在真实磁盘上使用 ext4 完全相同的操作 - 您需要从内到外缩小每一层。您不能因为它是一个图像而跳过步骤。

要缩小包含文件系统的分区,您必须首先告诉文件系统来缩小自身。对于 ext2/3/4,这是使用 来完成的resize2fs。这将重新定位可能位于您要切断的区域中的数据,并将新边界存储为文件系统元数据的一部分。(我想这就是您所说的“碎片整理”的意思。)

只有在文件系统缩小后,您才能缩小包含它的分区。这可以通过 parted 或 fdisk 完成,只需更改分区的结束地址即可。

附注:你应该能够使用 GParted 在一个步骤中调整文件系统和分区的大小 - 如果它支持在循环设备上工作的话。这可能取决于 GParted 的版本。(但是,CLI分开无法缩小文件系统,它只会截断分区。)

最后,一旦文件系统和分区都调整了大小,您就可以截断包含它们的整个映像。为此,首先分离循环设备并truncate --size=...在映像文件上使用。

(为了安全地执行此操作而不必进行仔细的计算,我会将文件系统缩小一点,以创建一些“缓冲区”空间;例如,如果我想要一个 4 GB 的图像,我会将文件系统缩小到 3 GB,将分区缩小到 3.5 GB,然后将图像截断到 4 GB。然后以相反的顺序增加所有内容以填充“缓冲区”空间。)

答案2

为了完整起见,我根据这两个答案添加了用于调整图像大小的半自动脚本[1], [2]。此脚本调整分区大小,以便将图像写入较小的设备。

该脚本假定磁盘只有一个ext分区。

  • 从...创建图像/dev/disk
    sudo dd if=/dev/disk of=/tmp/disk_image.img status=progress bs=16M oflag=sync
    sudo chown $USER disk_image.img
    
  • 将文件系统大小调整为最小并计算其大小
    sudo losetup --partscan /dev/loop0 disk_image.img
    sudo e2fsck -f /dev/loop0p1
    sudo resize2fs /dev/loop0p1 -M
    block_count=$(sudo tune2fs -l /dev/loop0p1 | grep 'Block count' | sed 's/[^0-9]//g')
    block_size=$(sudo tune2fs -l /dev/loop0p1 | grep 'Block size' | sed 's/[^0-9]//g')
    fs_size=$((block_count * block_size))
    sudo losetup --detach /dev/loop0
    
  • 调整分区大小以适合底层文件系统
    sudo losetup --partscan /dev/loop0 disk_image.img
    start=$(sudo parted /dev/loop0 unit B print --json | jq -r '.disk.partitions[0].start[0:-1]')
    end=$((start + fs_size))
    sudo parted /dev/loop0 resizepart 1 ${end}B
    blocks=$(sudo parted /dev/loop0 unit s print --json | jq -r '.disk.partitions[0].end[0:-1]')
    block_size=$(sudo parted /dev/loop0 unit s print --json | jq -r '.disk["physical-sector-size"]')
    end=$((blocks * block_size))
    sudo losetup --detach /dev/loop0
    truncate -s $end disk_image.img
    
  • 制作稀疏文件
    sudo losetup --partscan /dev/loop0 disk_image.img
    mkdir /tmp/disk_image
    sudo mount /dev/loop0p1 /tmp/disk_image
    sudo fstrim -v /tmp/disk_image
    sudo umount /tmp/disk_image
    sudo losetup --detach /dev/loop0
    

相关内容