让Windows的KVM虚拟机能识别宿主机Linux上未进行分区的硬盘

今天学习Linux的储存相关知识的时候,意外翻到了个很有意思的求助问题:求助者其有个30TB大的未分区的硬盘,本来是给Linux用的。这时他想让在这个Linux系统上的KVM虚拟机的Windows也能识别这个盘,但却不知道如何操作。

不过作者自己倒是折腾出来了,还写了个博客。巧了!这种问题我也不会,然而这种问题,又是在各种奇怪需求中可能会邂逅的问题,遇到了吧,还不好立马找到资料,于是翻译了下原文,给自己留个底。

原文:Heinous uses for Device Mapper (jiphex.github.io)

正文:

最近我遇到一个奇怪的情况,然而这个情况却让我领会到了Linux Device Mapper有趣性。虽然这可能永远不会用于生产环境,但是新奇的展示了Linux在块设备上到底是有多灵活。

总结一下在ServerFault(译者注:这个linux - Can Windows read an unpartitioned NTFS volume? (Single large partition) - Server Fault)的贴文,问题是这样的:

  • 你有一个十分大的Linux logical voloume,但是它没有分区,只是直接放置了一个文件系统。

  • 你要想把这个块设备作为一个磁盘给在同一机器上运行的应用,然而这个应用因为某些原因并不能解析没有分区过的卷(比如,Windows KVM 虚拟机)

  • 并且,你已经用这个Linux logical voloume存了不少数据了,你可不想再从头拷贝了。

在理想情况下,Windows应该能够识别卷的大小,当然这个卷里面是有文件系统的(译者注:ext4 or ntfs那些),并且能够像一个普通的硬盘一样来读取,但是现实却不是这样的。

你有一个十分大的Linux logical voloume,但是它没有分区,只是直接放置了一个文件系统。

你要想把这个块设备作为一个磁盘给在同一机器上运行的应用,然而这个应用因为某些原因并不能解析没有分区过的卷(比如,Windows KVM 虚拟机)

并且,你已经用这个Linux logical voloume存了不少数据了,你可不想再从头拷贝了。 所以呢显然的解决方案是:希望提供一个分区表(译者注:比较老的规范是mbr,较新是gpt)来解决这个问题

然而不幸的是,块设备自己有内在的顺序,并且我们如今常用的两个分区表格式(译者注:就指mbr和gpt)需要一个存在卷开头位置的分区表头(用来描述磁盘上分区布局的几KB信息)(译者注:我们知道卷这种存储设备内部都是一个个储存块,数据是按照一定顺序放进储存块中的,前面的储存块一旦被使用记录了数据,就没法再使用了。我们在给卷做文件系统的时候,文件系统一般都会在当前卷的头部写入文件系统需要的头部信息,因此我们现在想把分区头也写在开头,会发现已经被文件系统的头占用了)

在Linux中,仅仅在LVM卷的开头添加空间是不可能的,你可以通过在卷的末尾添加空间来扩展卷,但这并不是特别有用。你可以从末尾开始遍历设备,将所有数据向后移动N个扇区,以便在开始时腾出空间。但这对于大量数据来说,实际上是不可行的。

幸运的是,Linux为我们提供了 Device Mapper, 这是一个被LVM本身、kpartx和Linux多路径驱动程序等软件所使用的框架。我们可以操纵Device Mapper来达到我们的目的,神奇地来创建虚拟块设备。

在这样的情况下,我们的需求可太微不足道了:

  • 我们需要在卷的开头有一些空间来储存分区表

  • 在同一卷上,我们需要显示我们试图访问的实际数据

实际上要实现上面的需求,在实践上来说也不算太难(假设 /dev/vgstuff/data就是包含了我们数据的逻辑卷)

[root@server]# dd if=/dev/zero of=/root/gpt.bin bs=68K count=1
[root@server]# losetup -f /root/gpt.bin
[root@server]# cat > /root/dmap.txt
0 128 linear /dev/loop0 0
128 2099200 linear /dev/vgstuff/data 0
[root@server]# cat dmap.txt | dmsetup create compositedevice

(译者注:我来注释一波这些命令)

# dd命令:用来从指定文件中读取内容复制到指定文件中。那么下面这行就是从/dev/zero
# 文件的开头开始读取1次68KB长度的数据,粘贴到gpt.bin这个文件中
[root@server]# dd if=/dev/zero of=/root/gpt.bin bs=68K count=1
# losetup: 用来控制loop设备的命令。可以自行搜索什么是loop设备,简而言之就是能实现
# 将一个文件能伪装成一台虚拟硬盘设备,来给Linux读写。iso文件就用到了这个技术。
[root@server]# losetup -f /root/gpt.bin
# 写入配置文件,等会给dmsetup读入,这里直接把dmsetup命令是干嘛的也介绍下。
# 该命令就是上文提到的Device Mapper的一个操作工具,能将两个硬盘设备合成成一个设备
# dmsetup create 会从输入中读取你想要怎么拼接两个硬盘设备,在下面命令中,配置被写进了dmap.txt文件中
# 配置文件格式: 逻辑起始扇区 逻辑扇区的长度 类型 设备位置 物理起始扇区
# 逻辑起始扇区就是创建成新的设备,当前设备在新设备的哪个地方开始
# dmsetup只能创建逻辑扇区512Byte大小的新设备
# 所以 128 * 512Byte / 1024 = 64KB, 这也就是为啥上面的gpt.bin是68KB因为,为了保险要稍微比实际要用到的大小大点。
[root@server]# cat > /root/dmap.txt <<EOF
0 128 linear /dev/loop0 0
128 2099200 linear /dev/vgstuff/data 0
EOF
[root@server]# cat dmap.txt | dmsetup create compositedevice

这将在/dev/mapper/compositedevice中创建一个复合块设备(名称可以是你想要的任何名称,而不仅仅是’ compositedevice '),其中应该包含前68K的空白文件,然后是逻辑卷。

只要底层卷是可读可写的,文件就应该是可读可写的。您可以使用dmsetup remove删除它,并使用dmsetup infodmsetup status查看详细信息。错误可用dmesg查看。

你现在所需要做的就是在文件开始的区域内创建一个分区表,我使用testdisk完成了这一点,它能够通过卷本身搜索文件系统头部,并创建一个与磁盘上的实际数据匹配的分区表。写完这个,导出/dev/mapper设备,就完成了!

其实也可以不使用测试盘,而是执行以下操作:

  • 创建一个与目标卷大小相同(或略大)的稀疏文件。

  • 使用parted创建一个分区标签,并添加一个分区,该分区的开始和结束位置与最终卷所在的位置相同(包括由GPT表引起的偏移)。

  • 导出这个分区表(在parted中备份或计算出它的大小并使用dd),并将其直接写入gpt.bin文件。

    在我的例子中,由于所讨论的卷的大小(译者注:高达30TB),我不得不使用GPT分区表,但是使用MBR分区表就更容易了。MBR不是创建任意大的分区表文件(在上面的例子中是68K),而是总是512字节长,所以您可以利用这一点(只要您的卷本身<4TB)。