qcow2快照原理

余生长醉 提交于 2020-02-26 05:30:51

关键术语:
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快照原理

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!