文档章节

Qemu虚拟机QCOW2格式镜像文件的组成部分及关键算法分析

LastRitter
 LastRitter
发布于 2017/09/22 23:02
字数 1721
阅读 197
收藏 1

数据结构组成

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;
    uint32_t l1_size; /* XXX: save number of clusters instead ? */
    uint64_t l1_table_offset;
    uint64_t refcount_table_offset;
    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;

跟随这个字段后面的还有一些可选的TLV格式扩展数据,如外部快照的格式。(对于QCOW3,后面还有新特性字段)

L1表

L1,由Header中的“l1_table_offset”和“l1_size”定位。每个表项描述一个L2表项的相关信息,大小为64比特。

[00-08] 保留为0;
[09-55] 为L2表的偏移地址,如果为0则表明L2表未被分配;
[56-62] 保留为0;
[63]    为0则表明未被使用或者是COW,1则表明L2表被正确分配。

L2表

L2表,每个L2表占用1个簇。每个表项描述一个簇的属性,大小为64比特。

[00-61] 簇描述符,根据是否加密而不同;
[62]    未加密则为0,加密则为1;
[63]    为0则表明未被使用或者是COW,1则表明簇被正确分配。

refcount表

用于保存cluster的一级分配表,由Header中的“refcount_table_offset”和“refcount_table_clusters”定位。每个表项描述一个“refcount块”的起始地址,为0则表明未被使用,表项大小为64比特。

一个或多个“refcount块”

用于保存cluster的二级级分配表,每个refcount块占用一个cluster。每个表项的大小为refcount_bit(qcow2必须为16),0表明簇未被使用,1表明被使用,大于等于2则表明会被COW。

快照表

用于保存快照头, 由Header中的“nb_snapshots”和“snapshots_offset”定位,每个表项描述一个快照相关的信息,长度可变。

数据簇(cluster)

整个镜像文件都以cluster为单位进行管理,包括qcow2格式头,refcount表等元数据全部都被纳入cluster。

主要算法

偏移地址计算(从客户机的磁盘设备虚拟偏移地址转换为宿主机的镜像文件中的真实偏移地址)

// 每个cluster包含的L2表个数
l2_entries = (cluster_size / sizeof(uint64_t));

// offset在L2中的索引
l2_index = (offset / cluster_size) % l2_entries;

// offset所属的L2在L1中的索引
l1_index = (offset / cluster_size) / l2_entries;

// 从L1中获取L2的起始地址”并加载到内存
l2_table = load_cluster(l1_table[l1_index]);

// 从offset所在的L2中获取所在簇的起始地址
cluster_offset = l2_table[l2_index];

// offset在镜像中的真实地址 = 簇起始地址 + 簇内偏移
return cluster_offset + (offset % cluster_size);

偏移地址所在簇的refcount获取

// 每个refcount块容纳的refcount表项个数
refcount_block_entries = (cluster_size * 8 / refcount_bits)

// offset所在的refcount块索引
refcount_block_index = (offset / cluster_size) % refcount_block_entries

// offset所在refcount块在refcount表中索引
refcount_table_index = (offset / cluster_size) / refcount_block_entries

// 加载offset所在的refcount块到内存
refcount_block = load_cluster(refcount_table[refcount_table_index]);

// 获得offset所在簇的refcount值
return refcount_block[refcount_block_index];

内部快照生成(一个新快照的初始大小主要就是创建时的L1表大小)

  1. 复制一份L1表,将所有已被分配的L1和L2表项的最高位置位0,以及对应的refcount计数加1;

  2. 在快照表中新分配个表项,把L1属性指向新创建的L1表,Header中的nb_snapshots加1;

  3. 当向镜像写入数据时,对应的L1或者L2表项为0,对应的refcount大于或等于2,则需要重新分配对应的L2表项和簇,并让L1表项指向新的L2表,L2表项执行新分配的簇。

  4. 簇分配(不预先分配时的算法,写入时进行分配,读取时不分配,直接填充0)

  5. 当要向偏移地址offset处写入数据时,从Header的l1_table_offset中获得L1表的起始偏移地址;

  6. 用前偏移地址offset的前“64 - l2_bits - cluster_bits“位作为索引从L1表中获取对应L2表的描述符(l2_bits为每个簇中保存的L2表项个数,cluster_bits为每个簇大小的比特数);

  7. 如果L2表的表述符的最高位为0(未被分配或者是COW),则需要新分配L2表;

  8. 从refcount表和refcount块中查找2块未被使用的簇,标记为1;

  9. 初始化第一个簇为L2表,并使用它的起始地址初始化对应的L1表项;

  10. 初始化第二个簇,并使用它的地址初始化对应的L2表项;

  11. 使用第二个簇的首地址加上偏移地址的簇内偏移得出其真实地址;

  12. 在此地址进行数据写入。

镜像的大小计算

  • cluster_size -- 簇大小,默认为65536字节(64K)
  • total_size -- 要分配的镜像大小
  • refcount_bits -- refcount占用bit数,qcow2必须为16
  1. 按字节对齐的镜像大小
aligned_total_size = align_offset(total_size, cluster_size);
  1. Header 大小
header_size = cluster_size;
  1. L2表项个数
l2_num = aligned_total_size / cluster_size;
l2_num = align_offset(l2_num, cluster_size / sizeof(uint64_t));
  1. L2表大小
l2_size = l2_num * sizeof(uint64_t);
  1. L1表项个数
l1_num = l2_num * sizeof(uint64_t) / cluster_size;
l1_num = align_offset(l1_num, cluster_size / sizeof(uint64_t));
  1. L1表大小
l1_size = l1_num * sizeof(uint64_t);
  1. 每个refcount的大小,以及一个refcount块包含的refcount个数
refcount_size = refcount_bits / 8;
refcount_num = cluster_size / refcount_size;
	
    /* total size of refcount blocks
*
* note: every host cluster is reference-counted, including metadata
* (even refcount blocks are recursively included).
* Let:
*   a = total_size (this is the guest disk size)
*   m = meta size not including refcount blocks and refcount tables
*   c = cluster size
*   y1 = number of refcount blocks entries
*   y2 = meta size including everything
*   rces = refcount entry size in bytes
* then,
*   y1 = (y2 + a)/c
*   y2 = y1 * rces + y1 * rces * sizeof(u64) / c + m
* we can get y1:
*   y1 = (a + m) / (c - rces - rces * sizeof(u64) / c)
*/
  1. refcount块个数
refcount_block_num = (aligned_total_size + header_size + l1_size + l2_size) / 
(cluster_size – refcount_size - refcount_size * sizeof(uint64_t) / cluster_size)
  1. refcount块大小
refcount_block_size = DIV_ROUND_UP(refcount_block_num, refcount_num) * cluster_size;
  1. refcount表个数
refcount_table_num = refcount_block_num / refcount_num;
refcount_table_num = align_offset(refcount_block_num, cluster_size / sizeof(uint64_t));
  1. refcount表大小
refcount_table_size = refcount_table_num * sizeof(uint64_t);
  1. 总大小

使用默认参数(簇大小为65526字节,8字节地址,refcount为16位,2字节),不考虑字节对齐,不考虑快照的情况下,镜像为10G的近似计算如下:

t = total_size;
c = cluster_size;
header_size = c;
l2_size = t/c * 8;
l1_size = t/c / (c/8) * 8;
rb_size = t/c * 2;
rt_size = t/c / (c/2) * 8;
image_size ≈ t + c + t/c * 8 + t/c / (c/8) * 8 + t/c * 2 + t/c / (c/2) * 8
		= t + c + t/c*10 + t/(c*c)*80
		≈ 10G + 1.63M

因此使用默认参数时,元数据的大小大概只占磁盘数据大小的0.02%不到。新分配快照时,初始需要的空间为当时的L1表大小,约为160字节,几乎可以忽略不计(在修改后,所需空间会大幅增长,具体跟写入的量有关)。

© 著作权归作者所有

LastRitter
粉丝 44
博文 51
码字总数 241686
作品 0
武汉
高级程序员
私信 提问
qemu 磁盘格式cow、raw

在介绍cow与raw格式前,先看如下操作,通过ll -l与 du 查看一个磁盘镜像的大小,结果如下 ll 显示的是1G,而du显示的是0。 这与我们通常的理解有点偏差,一般我们会认为ls的结果会比du的结果...

ovirtKg
2016/11/02
262
1
KVM guest磁盘扩容

一,KVM常用的两种磁盘格式的比较: raw格式: raw (default) the raw format is a plain binary image of the disc image, and is very portable. On filesystems that support sparse fil......

leejia1989
2014/11/17
0
0
1、qemu-kvm创建虚拟机并设置网络

一:介绍 Hypervisor:是一种“虚拟机监视器(VMM)”,它介于裸机硬件和操作系统之间的组件,通过它, 可以在物理服务器上安装多个操作系统。 VNC:是一个在UNIX和Linux平台上的远程控制开源...

刘付kin
2016/12/10
729
0
KVM-QEMU, QCOW2, QEMU-IMG and Snapshots

介绍: QCOW2格式是KVM-QEMU推出的一种镜像格式,它不预先分配全部的磁盘空间,支持快照功能,并且你可以用以前的镜像作为基础,创建出基于它的新镜像,这样可以加快创建虚拟机的速度。 这篇文...

Firxiao
2015/01/29
1K
1
KVM虚拟化笔记(九)------kvm虚拟机快照备份

kvm虚拟机默认使用raw格式的镜像格式,性能最好,速度最快,它的缺点就是不支持一些新的功能,如支持镜像,zlib磁盘压缩,AES加密等。 要使用镜像功能,磁盘格式必须为qcow2。下面开始kvm虚拟机...

科技小能手
2017/11/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

 介绍一款优秀的通用管理权限快速开发框架

这是一套以权限管理为主的轻量化快速开发框架,配置有流程、专业表单、权限、app、企业微信等基础功能模块,在开发通用软件的效率上很有优势。 软件平台常用研发需求分析 《那些年我们一起做...

我想造火箭
31分钟前
9
0
ElasticDL:蚂蚁金服开源基于 TensorFlow 的弹性分布式深度学习系统

9 月 11 日,蚂蚁金服在2019谷歌开发者大会上海站上开源了 ElasticDL 项目,这是业界首个基于 TensorFlow 实现弹性深度学习的开源系统。 开源地址为:https://github.com/sql-machine-learni...

SOFAStack
38分钟前
7
0
CSS--渐变

一、什么是渐变 多种颜色平缓变化的一种显示效果 二、渐变的主要元素 色标:一种颜色,以及出现的位置,一个渐变至少两种色标 三、渐变的分类 1、线性渐变 以直线的方式来填充渐变色 backgr...

wytao1995
54分钟前
13
0
Java通过模板生成PDF再转换为图片

1、添加maven依赖 <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13.1</version></dependency><dependency> <g......

醉美閑聖
今天
8
0
SpringBoot-MVC RequestBody中LocalDateTime的自适应配置

请求的json报文中可能会出现 一下几种: ['2019-01-01','2019-01-01 12:03:34','20190101120334'] 但是接收的Request实体类日期字段是LocalDateTime类型 LocalDateTime applyDate; 希望的情况......

汉堡OSC
今天
17
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部