关键术语:
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
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)