qcow2快照原理

關鍵術語:
cluster 一個Qcow2 img文件由固定大小的單元組成,該單元稱爲cluster,默認大小爲65536bytes/64K
sector 數據塊讀寫的最小單元,大小爲512字節
host cluster 位於Host上qcow2 img文件的cluster管理名稱
guest cluster Guest所看到的virtual disk的cluster管理名稱
Qcow2 Header Qcow2 img的文件頭信息,佔用第一個cluster
refcount Qcow2內部用於管理cluster的分配而維護的引用計數
refcount table 用於查找refcount的第一級表
refcount block 用於查找refcount的第二級表
L1 table 用於查找guest cluster到host cluster映射的第一級表
L2 table 用於查找guest cluster到host cluster映射的第二級表
IBA image block address
VBA virtual block address數組

Qcow2 Header
typedef struct QCowHeader {
uint32_t magic;
uint32_t version;
uint64_t backing_file_offset;
uint32_t backing_file_size;
uint32_t cluster_bits;
uint64_t size; / in bytes /
uint32_t crypt_method; / 0 - 未加密;1 - AES加密 /
uint32_t l1_size; / XXX: save number of clusters instead /
uint64_t l1_table_offset;
uint64_t refcount_table_offset;//refcount table在img中的偏移
//refcount table所佔用的cluster數目
uint32_t refcount_table_clusters;app

//鏡像中快照的個數
uint32_t nb_snapshots;
uint64_t snapshots_offset;ide

/ The following fields are only valid for version >= 3 /
uint64_t incompatible_features;
uint64_t compatible_features;
uint64_t autoclear_features;
uint32_t refcount_order;
uint32_t header_length;
} QEMU_PACKED QCowHeader;函數

Qcow2 Host cluster management
Qcow2維護refcount用以管理image中cluster的分配和釋放,refcount做用等同於引用計數,表明了指定的cluster的使用狀態:
0: 表示空閒
1: 表示已使用
大於等於2:表示已使用而且寫訪問必須執行COW操做
refcounts經過二級表(相似頁表)來進行索引,第一級表稱爲refcount table,其大小可變、連續、佔用多個cluster,其表項中每個條目爲指向第二級表的指針(相對於img file的offset),每一個條目佔64bits。
第二級表稱爲refcount block,每一個refcount block佔用1個cluster,表中每一個條目爲2個字節大小的refcount。
給定一個相對於img file的offset能夠經過下面計算關係獲得refcount:
refcount_block_entries = (cluster_size / sizeof(uint16_t))
refcount_block_index = (offset / cluster_size) % refcount_block_entries
refcount_table_index = (offset / cluster_size) / refcount_block_entries
refcount_block = load_cluster(refcount_table[refcount_table_index]);
return refcount_block[refcount_block_index];優化

Qcow2在qemu中的實現是做爲塊驅動實現,主要代碼在:
block/qcow2.c
block/qcow2-refcount.c
block/qcow2-cluster.c
block/qcow2-snapshot.c
block/qcow2-cache.cui

實現原理
Qcow2 img的操做在qemu中都是做爲一種塊設備的blockdriver來實現的,qcow2對應的bdrv_create註冊的函數是qcow2_create,建立流程以下:
qcow2_create
qcow2_create2
bdrv_create_file
bdrv_create
bdrv_create_co_entry //qemu協程入口
raw_create
因爲qcow2 image是以文件形式存在的,在Qcow2的底層仍須要經過文件操做寫入實實在在的數據,在Qcow2管理結構上掛在了一個child管理結構,指向了bdrv_file的block driver,對應的API爲raw_create,raw_open等。因此在層次劃分上Qcow2 block driver完成了Qcow2內部格式的轉換,好比Guest到host的cluster mapping,l1,l2表的創建,索引查找等。
在image file的建立流程上,首先寫入header,offset爲0,大小爲cluster size
blk_pwrite(blk, 0, header, cluster_size);
接着寫入一個refcount table和一個refcount block
blk_pwrite(blk, cluster_size, refcount_table, 2 cluster_size);
分配3個cluster,講上面使用的3個cluster佔用
qcow2_alloc_clusters(blk_bs(blk), 3
cluster_size);
最後根據header的最新信息更新image的header
qcow2_update_header(blk_bs(blk));加密

下面是snapshot的header信息,每個snapshot都有一個header,而header中的l1_table_offset標示了該snapshot所使用的l1表。
typedef struct QEMU_PACKED QCowSnapshotHeader {
/ header is 8 byte aligned /
uint64_t l1_table_offset;//該snapshot所使用的l1表
uint32_t l1_size;
uint16_t id_str_size;
uint16_t name_size;
uint32_t date_sec;
uint32_t date_nsec;
uint64_t vm_clock_nsec;
uint32_t vm_state_size;
uint32_t extra_data_size; / for extension /
/ extra data follows /
/ id_str follows /
/ name follows /
} QCowSnapshotHeader;
爲了將磁盤鏡像地址映射到鏡像文件偏移,須要經歷如下幾步:3d

  1. 經過qcow2 header中的l1_table_offset字段獲取L1 table的地址;
  2. 使用高(64 - l2_bits - cluser_bits)位的地址來索引L1 table,L1 table是一個數組,數組元素是一個64位的數;
  3. 經過L1 table中的表項來獲取L2 table的地址;
  4. 經過L2 table中的表項來獲取cluster的地址;
  5. 剩餘的cluster_bits位來索引cluster內的位置。
    若是找到的L1 table或L2 table的地址偏移爲0,則表示磁盤鏡像對應的區域還沒有分配。

qcow2_co_preadv
a. qcow2_get_cluster_offset:根據offset獲取cluster內的數據,根據offset獲取L1表的索引,再獲取L2表,繼續在獲取L2 table表裏的存放數據的地址,而後根據該值返回不一樣的類別。
enum {
QCOW2_CLUSTER_UNALLOCATED, //該cluster爲分配
QCOW2_CLUSTER_NORMAL,
QCOW2_CLUSTER_COMPRESSED, //壓縮類別
QCOW2_CLUSTER_ZERO //內容爲全0
};指針

b.根據qcow2_get_cluster_offset的返回內別作不一樣處理:
case QCOW2_CLUSTER_UNALLOCATED:若是存在於back file中則從backfile中獲取
case QCOW2_CLUSTER_NORMAL:bdrv_co_preadv直接讀取文件對應位置
case QCOW2_CLUSTER_ZERO:直接設爲全0
case QCOW2_CLUSTER_COMPRESSED:用qcow2_decompress_cluster讀取
c bdrv_co_preadv讀取數據
d. 循環a-c直到讀取全部cluster
qcow2_co_pwritev
a. qcow2_alloc_cluster_offset:獲得一個cluster,對已存在的cluster直接返回文件中的位置,對未分配的
cluster會先分配在返回其位置
|--> handle_alloc:爲末分配的區域分配新的cluster或者須要copy-on-write
|-->do_alloc_cluster_offset:根據guest的地址分配cluster
|-->qcow2_alloc_clusters:分配地址,按照cluster偏移
|-->alloc_clusters_noref:分配虛擬地址,若是對應cluster的refcount爲0,表示已找到末使 用的cluster
|-->update_refcount:更新索引
b. 若爲加密方式則調用qcow2_encrypt_sectors
c. bdrv_co_pwritev寫數據
d. 更新L2 Table qcow2_alloc_cluster_link_l2
e. 循環a-d直到寫完全部cluster協程

ref table的管理
qcow2_get_refcount
refcount_block_cache字段的引入在於優化refcount的管理,當cache中數據已存在時不須要在讀磁盤

Qcow2 Cluster mapping(Guest->Host)
Guest OS看到的只是virtual disk,操做的是Guest Cluster,因此Qcow2鏡像另個重要功能就是管理Guest Cluster到Host Cluster的映射。
Guest Cluster到Host Cluster的映射關係也是經過一個二級表來管理,稱爲L1表和L2表,L1表大小可變、連續、佔用多個cluster,其表項中每個條目爲指向L2的指針(相對於img file的offset),每一個條目佔64bits。
L2表佔用一個cluster,每一個條目佔64bits.

給定一個相對於virtual disk的offset,能夠經過下面計算關係獲得Host Cluster offset:

l2_entries = (cluster_size / sizeof(uint64_t))
l1_index = (offset / cluster_size) / l2_entries
l2_index = (offset / cluster_size) % l2_entries
l2_table = load_cluster(l1_table[l1_index]);
cluster_offset = l2_table[l2_index];
return cluster_offset + (offset % cluster_size)

qcow2快照原理

相關文章
相關標籤/搜索