1. 程式人生 > >qcow2快照原理

qcow2快照原理

user mapping 二級 copy pre its valid trie 處理

關鍵術語:
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;

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

uint64_t snapshots_offset;

/ 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.c

實現原理
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;
為了將磁盤鏡像地址映射到鏡像文件偏移,需要經歷以下幾步:

  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快照原理