我已使用以下命令将 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