最近有遇到对镜像文件(.img)进行扩容的需求,踩了一些坑,在此记录一下。

背景是要在仿真器上跑一个在挂载额外disk.img的全系统测试,需要用到很大的数据。但是一开始用的磁盘镜像空间不够大,所以需要扩容然后才够放下数据。

一种类似的场景是,在虚拟机上启动,挂载某个disk.img作为linux启动之后的rootfs,disk.img内有自己的分区表和文件系统。

这种需求不能仅仅只扩大外部host的img文件,而是要剥洋葱。

具体从外到内有三个层次步骤:

  • 扩大hostFS内的disk.img文件容量
  • 扩大disk.img内使用的分区容量
  • 扩大disk.img内guestFS的容量

hostFS的文件容量

这一层是最直观也最好理解的一层。因为容量不够肯定先想到扩大手里的disk.img文件本身。

操作之前要对disk.img进行备份

现成的工具有qemu-imgdd:

qemu-img resize --preallocation=full disk.img +18G
# or
dd if=/dev/zero of=disk.img seek=2GiB count=18GiB

--preallocation在支持sparse file的文件系统下是必须的,比如ext2,3,4。否则扩容后的disk.img由于增加的容量是被认为empty的,从而不会实际占据空间。当在虚拟机或者仿真器中挂载时,扩容出来的空间依然不可用。具体可以参考Wikipedia:Sparse file

目前个人认为这是由于仿真器和虚拟机对img的操作,并不经过外部的hostFS,从而没办法完成对sparse的支持。

第二种方法,直接通过dd往disk.img后面写入无用的0,从而扩大文件大小。seek表示在写入of文件时要跳过前多少比特,一般需要指定为原disk.img大小,以免破坏原有内容。count表示将多少大小的if内容复制到of中。具体可以参考dd man

需要注意的是seek可以接收以B结尾的具体大小,也可以接收以obs大小的block个数。
例如obs=1M seek=1024就是1GiB大小。
count同样可以接收两种,后者以ibs作为block的大小。
ibsobs默认都是512比特,也可以用bs统一设置。

disk.img的分区容量

这一层就是常见的分区操作了,但是在玩虚拟机和仿真器的时候很容易忽略。

先将disk.img挂载到loop设备上,然后就可以操作分区表。

(root) losetup -P /dev/loopX disk.img

加上-P是为了直接扫出分区表,然后可以直接刷新出已有分区。

之后就是对想要扩容的分区操作。现成的工具太多了,例如parted和gparted。

guestFS的容量

这一层是洋葱最里层,就是要告诉guestFS扩容了,有新空间可以用了。

这里以ext3为例,需要用到e2fsckresize2fs

(root) e2fsck -f /dev/loopXpY
(root) resize2fs /dev/loopXpT 20G

e2fsck先检查guestFS,然后报一个section使用情况。resize2fs扩大guestFS的使用容量。


至此,disk.img在仿真器里可以使用的容量成功从2GiB扩大到20GiB。