VHD結構

VHD結構是一種虛擬磁碟的實現方式,即通過檔案來模擬物理磁碟的方式來存儲數據。

基本介紹

  • 中文名:VHD結構
  • 外文名:Microsoft Virtual Hard Disk
  • 簡介:是一種虛擬磁碟的實現方式
  • 詞語定義:VHD最早由Connectix公司定義
  • 格式用於:Microoft Virtual PC 等
  • 實現方式:固定方式和動態方式。
概念,結構概要,固定方式的VHD結構,動態方式的VHD結構,差異磁碟的讀取方式,[作者及後記],

概念


如同正常的物理磁碟一樣,可以分區,格式化等。
VHD最早由Connectix公司定義,之後Connectix公司被Microsoft 收購。
VHD格式用於Microoft Virtual PC 、Microsoft Windows Server 2008 R2和Microsoft Windows 7,包括hypervisor為基礎的虛擬化技術- Hyper-V。
VHD也用於xen虛擬化,目前也是xen上虛擬磁碟的默認標準。

結構概要


VHD結構有2種實現方式:固定方式和動態方式。
固定方式就是用真實大小的檔案模擬同樣大小的一個虛擬磁碟。當然,額外的,一定要加上一些對本檔案的總的描述(在檔案尾部),否則系統無法知道就是固定磁碟還是動態磁碟。
動態磁碟是按稀疏方式,用檔案做容器,來表述一個可能大得多的虛擬磁碟,如果某個區域沒寫數據,就可能不在檔案中真正分配空間。另外,差異磁碟(快照)也基於動態磁碟的方式存儲。本文用"$VHD檔案"的說法表示VHD檔案本身,而用"$虛擬磁碟"的說法表示 VHD檔案描述的虛擬磁碟定址空間。
下面數據結構表示,如非特別提示,均為大尾(bigendian)方式。

固定方式的VHD結構


結構如下圖:
檔案一開始就是和物理磁碟相同的raw image,即$虛擬磁碟的X扇區就是$VHD檔案的X扇區,一對一映射。與物理磁碟不同的是,尾部增加了一個HD footer的結構。HD footer的結構如下圖:
磁碟上的HEX數據表現如下,用WINHEX截取
xen中的原始碼是這樣表述的:
1. struct hd_ftr {
2. char cookie[8]; /* Identifies original creator of the disk */
3. u32 features; /* Feature Support -- see below */
4. u32 ff_version; /* (major,minor) version of disk file */
5. u64 data_offset; /* Abs. offset from SOF to next structure */
6. u32 timestamp; /* Creation time. secs since 1/1/2000GMT */
7. char crtr_app[4]; /* Creator application */
8. u32 crtr_ver; /* Creator version (major,minor) */
9. u32 crtr_os; /* Creator host OS */
10. u64 orig_size; /* Size at creation (bytes) */
11. u64 curr_size; /* Current size of disk (bytes) */
12. u32 geometry; /* Disk geometry */
13. u32 type; /* Disk type */
14. u32 checksum; /* 1's comp sum of this struct. */
15. vhd_uuid_t uuid; /* Unique disk ID, used for naming parents */
16. char saved; /* one-bit -- is this disk/VM in a saved state? */
17. char hidden; /* tapdisk-specific field: is this vdi hidden? */
18. char reserved[426]; /* padding */
19.};
結構解釋:
cookie:
識別標誌,為"conectix",用於判斷VHD是否有效
features:
取值如下。
1. #define HD_NO_FEATURES 0x00000000
2. #define HD_TEMPORARY 0x00000001 /* disk can be deleted on shutdown */
3. #define HD_RESERVED 0x00000002 /* NOTE: must always be set */
ff_version:
VHD版本,用處不大,用於結構判斷,似乎更多的會用到crtr_ver。
data_offset:
描述中指下一個結構的起始絕對位元組位置,如果是動態磁碟,這表明了dd_hdr(稍後會提到)的物理位元組位置。如果是固定磁碟,似乎總是0xFFFFFFFF。
timestamp:
VHD的創建時間,指2000年1月1日00:00:00起始的秒值。和HFS對時間的描述方式一致,也就是說此處數值加上0xB492F400 (即2000/01/01 00:00:00),即是標準的HFS時間方法對本值的解釋。
crtr_app:
見代碼注釋
crtr_ver:
創建版本。根據版本號可實施對應的方法。似乎目前只有當創建版本號為0x00000001時,對於bitmap的操作會有不同(具體細節請參考xen源碼)。
crtr_os:
見代碼注釋
orig_size :
創建時$虛擬磁碟大小,再強調一下,這個大小指虛擬出來的磁碟的可用定址空間。如果是固定格式的VHD,這個大小等於$VHD檔案的大小減去1扇區(尾部 hd_ftr)。
curr_size:
或許是用於vhd線上擴容後的最後大小表述,沒仔細研究過。同樣指$虛擬磁碟的大小,即虛擬出來的磁碟的可用定址空間,如果沒有擴容,和orig_size相同。
geometry:
VHD的C/H/S結構參數,兼容一些老的套用(其實估計不會用到了)。如下表示
1. #define GEOM_GET_CYLS(_g) (((_g) >> 16) & 0xffff)
2. #define GEOM_GET_HEADS(_g) (((_g) >> 8) & 0xff)
3. #define GEOM_GET_SPT(_g) ((_g) & 0xff)
type:
非常重要的,表示這個VHD的類型。如下表示
1. #define HD_TYPE_NONE 0
2. #define HD_TYPE_FIXED 2 /* fixed-allocation disk */
3. #define HD_TYPE_DYNAMIC 3 /* dynamic disk */
4. #define HD_TYPE_DIFF 4 /* differencing disk */
如果是差異磁碟,在dd_hdr中會描述父設備的設備號,但結構與動態磁碟相同
checksum:
整個扇區所有位元組(當然一開始不包括checksum本身)相加得到32位數,再按位取反。
uuid:
用於VHD識別號,如果是有差異磁碟,這個ID非常重要,決定了VHD間的主從關係。
saved:
動態中使用,見代碼注釋
hidden:
見代碼注釋
reserved:
保留空間,總是為0
上述結構中最重要的變數為VHD類型、大小、checksum。

動態方式的VHD結構

動態VHD和固定VHD相同的是,尾部也是重要的hd_ftr格式,不同的是hd_ftr會表明本VHD是動態方式的。
上圖中,位置描述部分黃色區域為數據區,其餘區域,均為元數據區(結構管理區)。
0扇區的hd_ftr mirror是對尾部hd_ftr的備份。hd_ftr在固定格式的VHD中已經詳細解釋,hd_ftr中data_offset會描述dd_hdr的位置,不過,這個位置目前總是1扇區。
dd_hdr結構用於表述整個動態vhd的概況,分配塊大小等的變數。
BAT指Block allocation table,非常重要的,表示$虛擬磁碟地址到$VHD檔案地址的塊映射表。
tdbatmap指BAT的分配點陣圖,其實就是所有分配塊的是否用滿的位描述表。在某些版本的vhd中,可能不存在此結構,xen源碼中是這樣判斷是否存在batmap的:
1. int vhd_has_batmap(vhd_context_t *ctx)
2. {
3. if (!vhd_type_dynamic(ctx))
4. return 0;
5. if (!vhd_creator_tapdisk(ctx))
6. return 0;
7. if (ctx->footer.crtr_ver <= VHD_VERSION(0, 1))
8. return 0;
9. if (ctx->footer.crtr_ver >= VHD_VERSION(1, 2))
10. return 1;
11. /*
12. * VHDs of version 1.1 probably have a batmap, but may not
13. * if they were updated from version 0.1 via vhd-update.
14. */
15. if (!vhd_validate_batmap_header(&ctx->batmap))
16. return 1;
17. if (vhd_read_batmap_header(ctx, &ctx->batmap))
18. return 0;
19. return (!vhd_validate_batmap_header(&ctx->batmap));
20.}

每個數據塊都由塊bitmap和塊本身組成,塊bitmap用於描述塊中每個扇區是否占用的點陣圖表,塊本身就是數據區。
我們依次詳細分析上述所有結構:
1、hd_ftr:
固定格式vhd中已詳細分析,在動態磁碟中,不同的是data_offset總為0x200,類型為3或4。
2、dd_hdr:
結構如下圖:
WINHEX中的磁碟表現如下:
xen中的原始碼是這樣表述的:
1. struct dd_hdr {
2. char cookie[8]; /* Should contain "cxsparse" */
3. u64 data_offset; /* Byte offset of next record. (Unused) 0xffs */
4. u64 table_offset; /* Absolute offset to the BAT. */
5. u32 hdr_ver; /* Version of the dd_hdr (major,minor) */
6. u32 max_bat_size; /* Maximum number of entries in the BAT */
7. u32 block_size; /* Block size in bytes. Must be power of 2. */
8. u32 checksum; /* Header checksum. 1's comp of all fields. */
9. vhd_uuid_t prt_uuid; /* ID of the parent disk. */
10. u32 prt_ts; /* Modification time of the parent disk */
11. u32 res1; /* Reserved. */
12. char prt_name[512]; /* Parent unicode name. */
13. struct prt_loc loc[8]; /* Parent locator entries. */
14. char res2[256]; /* Reserved. */
15.};
結構解釋:
cookie:
識別標識,為"cxsparse",用於是否dd_hdr的校驗。
data_offset:
未使用,總設定為0xFFFFFFFF。
table_offset:
很重要,BAT結構在$VHD檔案中的絕對位元組位置。幾乎總是0x600
hdr_ver:
dd_hdr的版本
max_bat_size:
BAT條目的最大數量,實際上每個bat條目,就相當於一個塊。
block_size:
塊大小,幾乎總是2MB。
checksum:
同hd_ftr中 checksum的計算方式相同。計算範圍為從dd_hdr開始的1024位元組。
ptr_uuid:
差異磁碟中非常重要,表示其父vhd的uuid。這樣才可以實現快照穿透。
prt_ts:
父磁碟的修改時間,時間表示方法參考hd_ftr中timestamp的表示方式。
res1:
保留
ptr_name:
父磁碟的unicode名稱。可以更快地找到父磁碟,但找到後,還需通過uuid校驗。
loc:
用來記錄在不同平台上的父磁碟的名稱,並不很重要。可見dd_hdr結構圖解釋,本結構的每個條目會指向一個存儲檔案名稱稱的$vhd檔案的絕對位元組位置。
res1:
保留。
上述結構最重要的變數為:塊大小,BAT位置,BAT數量,父磁碟的uuid(對於差異磁碟)
3、BAT:
結構如下圖:
WINHEX中的磁碟表現如下:
結構解釋:
BAT表中,每4個位元組表示一個bat entry,從BAT的0位元組開始,以4位元組為單位,第x個條目(條目從0開始編號),表示$虛擬磁碟中第x塊在$vhd中的扇區位置。如果第x個條目的值為0xFFFFFFFF,表示$虛擬磁碟的第x塊為稀疏,返回一整塊0。
假定塊大小為2MB,對應上面winhex的磁碟表現圖,從0x600位置起(按左上角offset定位),前4行全部為0xFFFFFFFF,表明整個$虛擬磁碟的前16個2MB塊是全0,並未在$vhd檔案中分配空間。位於0x640處的第16個bat entry(從0開始編號的序號)的值為0x00D6CC27,表示$虛擬磁碟的第16塊(塊大小為2M)的數據流存儲於$vhd檔案的第0x00D6CC27扇區。
4、tdbatmap
bat entry的bitmap,也可理解為塊的分配點陣圖。每一位表示一個block,如果位為1,表示block已經用滿。如果為0,表示未使用,或未用滿。
tdbatmap由頭結構和batmap內容部分組成。
頭結構如下圖:
在WINHEX中的磁碟表現(連同其後的bitmap區域)如下:
xen中的原始碼是這樣表述頭結構的:
1. struct dd_batmap_hdr {
2. char cookie[8]; /* should contain "tdbatmap" */
3. u64 batmap_offset; /* byte offset to batmap */
4. u32 batmap_size; /* batmap size in sectors */
5. u32 batmap_version; /* version of batmap */
6. u32 checksum; /* batmap checksum -- 1's complement of batmap */
7. };
頭結構說明:
cookie:
識別標識,為" tdbatmap ",用於是否dd_batmap_hdr的校驗。
batmap_offset:
batmap的起始物理位置,如上面winhex磁碟圖中表示的起始位置為0x400800。
batmap_size:
batmap的大小,以扇區為單位 。其實等於bat entry數量除以8,再對齊扇區大小的扇區數。
batmap_version:
batmap結構的版本號。
checksum:
參考hd_ftr中checksum的計算方法。計算範圍為包括batmap頭和內容區的整個batmap區域
batmap內容區結構說明:
如上面winhex磁碟圖中偏移為0x400800起始的數據,每一位表示一個塊的分配情況,開始的0x004000....表示整個$虛擬磁碟的第9塊(塊從0開始編號)是用滿了的。而第0、1、2、3等塊是未用滿或未使用的塊。
5、數據塊區:
如果塊大小為2M,其實每個bat entry所指向的空間大小為512byte+2MB。最前面的512位元組是本塊內的扇區點陣圖,如果位為1,表示代指的扇區已使用,如果為0,表示代指的扇區為全0,更多的意義用於差異磁碟。舉個例子,如果bitmap的開始2個位元組為0x80和0x12,表示第0、11、14扇區已分配。
緊跟在塊內512位元組bitmap後的就是塊數據本身,可按bitmap的表述,直接映射到$虛擬磁碟中。
VHD動態磁碟格式總結:
對一個動態vhd磁碟的定址過程大致為:
1、通過讀取hd_ftr結構,確定是否動態磁碟,以及dd_hdr的位置。
2、讀取dd_hdr,確定塊大小,bat的位置,bat的數量,以及是否差異磁碟(差異磁碟的處理方式後面講到)
3、定位bat區域,可隨機確定任何一個 block的位置,如果bat的值為0xFFFFFFFF,則返回全0。
4、確定block位置後,先讀取1扇區的bitmap區域,本block的某個扇區是否已使用,可以通過此bitmap確定。

差異磁碟的讀取方式


差異磁碟是建立在一個固定或動態磁碟上的快照。其本意是差異磁碟中僅存儲自創建備份點以來的所有改動。本質上,差異磁碟就是個動態磁碟。
如何在數據層面合併差異磁碟和其父磁碟呢?
如果要讀取某個數據塊x,系統會首先讀取差異磁碟x塊的bat entry。如果其值為 0xFFFFFFFF,表明差異磁碟中未記錄數據,接下來就應該讀取本差異磁碟的父磁碟的第x塊;如果bat entry其值不為0xFFFFFFFF,則可以通過batmap中x塊是否用滿(或分析塊數據區中的bitmap),來確定是否需要穿透進父磁碟進行補差,如果已用滿,則不需再處理父磁碟;如果未用滿,再看本塊數據區中bitmap哪些位為0,為0的穿透進父磁碟進行補差,為1則直接讀取。

[作者及後記]


作者:張宇,北亞數據恢復中心創始人
本文也做為目前北亞招聘數據恢復工程師和C++開發工程師的背景材料。
本文僅首發於51CTO,如需轉載,請保留完整信息。

相關詞條

熱門詞條

聯絡我們