虚拟磁盘格式1:VMDK
虚拟磁盘格式:VMDK
vmware设计VMDK的文件格式来模拟物理磁盘,使得虚拟机的操作系统读写虚拟磁盘时使用与物理磁盘相应的接口
虚拟磁盘作为一个或多个文件存储在主机或远程设备上
- 在vmware workstation或mware pusion上:存在底层主机操作系统(win,Linux.,mac)提供的文件系统上
- 在数据中心平台上:存在Esxi主机存储或网络连接的存储上
- 在Exi主机上:存在VMES(虚拟机文件系统)分区上
总体结构
开始VMDK仅包含一个虚拟盘(base disk),如果拍摄虚拟机快照,会产生增量链接(delta link,可能有多个)
每个链接由多个区段(extent)组成,每个段对应一个物理存储区域,即对应一个文件(通常)
下图中link B,C的区段是开始较小,随时间变大的区段,称为稀疏区段(sparse)
linkA的区段可以是稀疏区段,也可以是预先分配好大小的区段(flat,创建时已经预先分配好空间),甚至可以直接由物理设备支持。
描述符文件
VMDK有文本描述符文件,用于描述磁盘中数据布局。该文件可以是单独文件,也可以包含到虚拟磁盘其它文件内。
下图为一个ubuntu虚拟机的描述文件。#开头的为注释信息。
header部分
更详细内容见文档Virtual Disk Format 5.0
字段 | 说明 |
---|---|
version | 表示描述文件的版本,默认为1 |
CID | 内容ID值,一个32位随机值。当磁盘被打开,第1次修改内容后就会更新 |
paratCID | 父链接内容ID值,如linkB的parantCID就是likA的CID,如果没有父链接,则值设为ffffffff |
createType | 描述虚拟碰盘的类型。如果值含有flat就表示已预先分配空间,包含sparse表示按需分配是稀疏磁盘,包含vmfs表示用于Esxi上的存储,如图中的2GbMaxExtentsparse表示2GB或更小的稀疏磁盘 |
extent部分
每行描述一个区段
如上图中第一行RW 4192256 SPARSE "ubuntu-sever-10.10-i386-s001.vmdk"
字段 | 说明 |
---|---|
RW | 访问权限,这里为可读可写 |
4192256 | 占几个扇区,一个扇区512B,这里换算下来约为2GB |
SPARSE | 区段的存储分配方式,这里为稀疏的 |
"ubuntu-sever-10.10-i386-s001.vmdk" | 文件名,表示区段相对于描述符文件的路径 |
disk database部分
存储虚拟磁盘的其它信息,格式为ddb.<名字>="值"
如ddb.adapterType="lsilogic"表示适配器类型为SCSI,用于SCSI磁盘。
以下表示由适配器初始化的磁盘的每道扇区数,磁头数,柱面数。
ddb.geometry.sectors = "63"
ddb.geometry.heads = "255"
ddb.geometry.cylinders = "2610"
搜索disk database信息时由底部向顶部开始(即由link C开始)
稀疏区段
这里主要介绍vmware workstation等主机平台的稀疏区段的格式,结构如下图所示
spare header稀疏头部分
结构以小端存储,定义为sectortype的类型是以扇区为单位
字段 | 类型 | 意义 |
---|---|---|
magicNumber | uint32 | 用于验证每个区段的有效性,vmdk文件的就初始化为0×564d444(即VMDK) |
version | uint32 | 版片号,1或2 |
flags | uint32 | 每比特为一标记。如第16比特表分页(grains)是否被压缩 |
capacity | sectortype(uint64) | 该区段的大小,必须是分页大小的整数倍 |
grainsize | sectortype | 一个页的大小、必须为2的幂次,次数大于8 |
descriptoroffset | sectortype | 嵌入在段中的描述符文件的偏移量,偏移为几个扇区,如果没都为0 |
descriptrsize | sectortype | 当描述符的偏移量不为0是才有效,描述符文件大小 |
numGTEsPerGT | uint32 | 页表中条目的数量,虚拟磁盘中为512 |
rgdOffset | sectortype | 冗余元数据第0层偏移,偏移为几个扇区 |
gdOffset | sectortype | 元数据第0层偏移(在下一小节介绍这一概念),偏移为几个扇区, |
overHead | sectortype | 元数据所占扇区数 |
uncleanShutdown | bool(uint8) | 关闭区段时设置为false,打开时设置为true,如果打开后发现为true,就要进行一致性检测 |
sigleEndLineChar | char(uint8) | 当用FTP传输时,用以下4个进行检测是否被损坏,初始值为'\n' |
nonEndLineChar | char | 初始值为' ' |
doubleEndLineChar1 | char | 初始值为'\r' |
doubleEndLineChar2 | char | 初始值为'\n' |
compressAlgorithm | uint16 | 磁盘中每一页的压缩算法 |
pad[433] | uint8 | 433B的填充,加上前面的79B,所以头信息共占512B,一个扇区 |
当vsersion=2,flags第2比特被设置时,表示采用zeroed-grain GTE,一般是快照或磁盘被压缩时设置
下面结合实际看一看,如下图,为一个区段的第一个扇区的数据
颜色 | 意义 |
---|---|
黄色 | 就是KDMV |
绿色 | 版本号 |
蓝色 | flag |
粉色 | 因为小端存储,所以为3FF800,化为10进制为4192256,因为是sectortype,再乘以512B,约为2GB,即该区段有2GB |
红色 | 页的大小,同样化下来为64KB |
紫色 | 描述符在段中的偏移量,都为0,说明该段不包含 |
白色 | 描述符文件大小也为0 |
橘色 | 页表中条目数量为512 |
灰色 | 冗余元数据第0层偏移,为1,即1个扇区 |
黑色 | 元数据第0层偏移,为258,即258个扇区 |
青色 | 元数据占扇区数640*512 |
黑框 | false |
四个红框 | '\n',' ','\r','\n'四个字符 |
红线 | 压缩算法,没有压缩 |
稀疏区段元数据(grain directory, grain table)部分
grain directory页目录(GD)是第0层元数据,grain table页表(GT)是第1层元数据,页目录中的每一个条目指向一个页表区块,如下图所示
redundancy grain directory,redundancy grain table为保存的副本
页目录中的条目(GDE)指页表在稀疏区段中的偏移量,条目的数量取决于段的大小,一个条目占32位
页表中的条目(GTE)指一个页在稀疏区段中的偏移量,每个页表有512个条目,每个条目占32位,所以一个页表占2KB
在头中的grainsize表示页的大小为64KB,区段的大小是页的大小的整数倍,所以也可知道VMDK采用的是段页式的存储结构
下面再结合实际看一看
首先找页目录
冗余页目录为1扇区处(由头信息知),即偏移512B,冗余页目录如下
再找冗余页表
只看上图冗余页目录第一个32位 02 00 00 00,小端存储,就是2,2扇区偏移1024B,所以第一个冗余页表如下图
那么冗余页表第一个32比特80 02 00 00表示第一个页在640扇区偏移处
再看看页目录和页表是否与冗余页目录页表一致:
页目录在258扇区处(由头信息知),即偏移132096B,页目录如下
页目录第一个32位 03 01 00 00,为259,即偏移132608B,第一个页表如下
那么第一个页表第一个32比特80 02 00 00表示第一个页在640扇区偏移处,可见冗余页表页目录与页表,页目录一致
下面再看看第一个数据的页,偏移在640*512,即偏移327680,这里因为是MBR的分区结构,我们可以看到阴影处为第一个分区表,在阴影中00 08 00 00表示第一个分区的起始扇区号为2048,更多见