Qcow2 镜像文件格式
qcow2 镜像由大小固定的主机簇组成。客户机数据及镜像元数据均以簇为单位分配。
客户机看到的虚拟磁盘同样划分为相同大小的客户机簇。
qcow2 的全部数值均采用大端字节序。
文件头
第一个簇包含文件头:
Byte 0 - 3: magic
QCOW 魔数字符串 ("QFI\xfb")
4 - 7: version
版本号,有效值为 2 和 3。
8 - 15: backing_file_offset
后备文件名在镜像内的偏移,字符串不以 NUL 终止。
无后备文件时为 0。
后备文件与原始外部数据文件不兼容,见自动清除功能位 1。
16 - 19: backing_file_size
后备文件名的字节长度,不超过 1023。
无后备文件时未定义。
20 - 23: cluster_bits
簇内偏移的寻址位数,簇大小为 1 << cluster_bits。
不得小于 9,即簇至少 512 字节。
QEMU 当前实现的最大簇为 2 MB,无法打开更大簇的镜像。
扩展 L2 条目要求至少为 14,即 16384 字节簇。
24 - 31: size
虚拟磁盘字节大小。
QEMU 最大 L1 表为 32 MB。2 MB 簇时,无法填充超过
2 EB(61 位)的虚拟簇;512 字节簇时,无法填充超过
128 GB(37 位)的虚拟空间。
L1/L2 布局还将已填充簇限制在 64 PB(56 位)以内,
也可能先遇到文件系统最大文件等其他限制。
32 - 35: crypt_method
0:不加密。
1:AES 加密。
2:LUKS 加密。
36 - 39: l1_size
活动 L1 表条目数。
40 - 47: l1_table_offset
活动 L1 表在镜像内的起始偏移,必须按簇边界对齐。
48 - 55: refcount_table_offset
引用计数表在镜像内的起始偏移,必须按簇边界对齐。
56 - 59: refcount_table_clusters
引用计数表占用的簇数。
60 - 63: nb_snapshots
镜像内的快照数。
64 - 71: snapshots_offset
快照表在镜像内的起始偏移,必须按簇边界对齐。
版本 2 的头部恰为 72 字节,到此结束。 版本 3 及以后至少 104 字节,继续包含以下直到 header_length 的字段。
72 - 79: incompatible_features
不兼容功能掩码。未知位被设置时,实现必须拒绝打开镜像。
Bit 0: 脏位。引用计数可能不一致;访问前必须
扫描 L1/L2 表并修复引用计数。
Bit 1: 损坏位。任何数据结构都可能损坏,
除恢复一致性外,不得写镜像。
Bit 2: 外部数据文件位。客户机簇存于外部文件,
外部文件簇不进行引用计数。
标准簇描述符的偏移必须匹配客户机偏移,
不支持压缩簇和内部快照。
设置时可包含 External Data File Name 头扩展。
Bit 3: 压缩类型位。压缩簇使用非默认压缩算法,
compression_type 必须存在且非零。
Bit 4: 扩展 L2 条目。采用支持子簇分配的扩展格式,
详情见扩展 L2 条目一节。
Bits 5-63: 保留,设为 0。
80 - 87: compatible_features
兼容功能掩码,未知已设置位可安全忽略。
Bit 0: 延迟引用计数。可标记镜像为脏,
推迟引用计数元数据更新。
Bits 1-63: 保留,设为 0。
88 - 95: autoclear_features
自动清除功能掩码。写入含未知功能的镜像前,
必须先清除相应位。
Bit 0: 位图扩展位,表示扩展数据一致。
不存在位图扩展却设置此位属于错误。
存在扩展但未设置此位时,必须视数据为不一致。
Bit 1: 原始外部数据位。
外部文件可直接作为一致的独立 raw 镜像读取,
无需查阅 qcow2 元数据。
此位影响部分操作性能,例如写零必须写数据文件,
而不能仅设置 L2 条目的零标志。
此功能与后备文件冲突。
只有同时设置 External Data File 位
(不兼容功能位 1)时才能设置此位。
Bits 2-63: 保留,设为 0。
96 - 99: refcount_order
引用计数块条目位宽:refcount_bits = 1 << refcount_order。
版本 2 总按 4 处理,即 refcount_bits = 16。
不得超过 6,即 refcount_bits = 64。
100 - 103: header_length
头结构字节长度。版本 2 总按 72 字节处理。
版本 3 至少 104 字节,且必须为 8 的倍数。
附加字段(版本 3 及以后)
通常这些字段可选,可安全忽略。 若需设置后面的 B 字段而不关心前面的 A,可将 A 填零,等效于不存在。 正式兼容规则如下:
字段若不能忽略,否则无法正确处理文件,就必须有对应不兼容功能位。
若没有未知的不兼容功能位,未知附加字段可安全忽略,但重写头部时必须保留其值。
未被特定不兼容位改变时,显式值 0 的行为与字段不存在相同*。
(*) header_length 小于或等于字段偏移时,视为不存在。 版本 2 的所有附加字段均不存在。
104: compression_type
压缩簇采用的压缩方法,同一镜像全部压缩簇使用相同类型。
设置 Compression type 不兼容位时,此字段必须存在且非零,
表示非 deflate;否则必须不存在或为零,表示 deflate。
可用值:
- 0: deflate <https://www.ietf.org/rfc/rfc1951.txt>
- 1: zstd <http://github.com/facebook/zstd>
QEMU 将 deflate 称为 zlib <https://www.zlib.net/>,
但此类型压缩簇不含 zlib 头。
105 - 111: 填充,内容见下文。
头部填充
header_length 必须是 8 的倍数,末尾字段未对齐时需填充。 填充必须为零,使现有或未来附加字段若落入其中,按上一节 [3.] 解释,即等效于该字段不存在。
头部扩展
镜像头后可保存可选扩展,每项结构如下:
Byte 0 - 3: 头扩展类型:
0x00000000 - 扩展区结束
0xe2792aca - 后备文件格式名字符串
0x6803f857 - 功能名称表
0x23852875 - 位图扩展
0x0537be77 - 全盘加密头指针
0x44415441 - 外部数据文件名字符串
other - 未知扩展,可安全忽略
4 - 7: 扩展数据长度。
8 - n: 扩展数据。
n - m: 填充至扩展大小为下一个 8 的倍数。
除非另有说明,同种扩展在镜像中最多一次。
存在后备文件时,其名称应保存在扩展区末尾到第一簇末尾的剩余空间。 此处不允许存其他数据,让实现修改头或添加扩展时,不损害不支持的兼容功能数据。 需要额外空间的兼容功能应使用头扩展。
字符串头部扩展
部分扩展只有一个字符串,如后备文件格式名、外部文件名。
扩展长度就是字符串长度,不以 \0 终止。
填充可能看起来像 NUL 结尾,但并不总需填充,也不保证填充为零。
功能名称表
可选头扩展,包含镜像使用的功能名称。 不认识某功能的应用,例如该功能是后来引入的,可用名称显示有用错误信息。
条目数由扩展数据长度决定,每项如下:
Byte 0: 功能类型,选择功能位图:
0: 不兼容功能
1: 兼容功能
2: 自动清除功能
1: 所选功能位图中的位号,有效范围 0-63。
2 - 47: 功能名称,零填充;达到完整长度时不一定以 NUL 终止。
位图扩展
可选头扩展,存储虚拟磁盘相关位图。 目前只有脏跟踪位图,记录某时刻以来的磁盘变化。
仅在对应自动清除位设置时才认为数据一致,见前述 autoclear_features。
字段如下:
Byte 0 - 3: nb_bitmaps
镜像内位图数量,至少 1。
QEMU 当前每镜像最多支持 65535 个。
4 - 7: 保留,必须为零。
8 - 15: bitmap_directory_size
位图目录字节大小,是全部 nb_bitmaps 个条目的总大小。
16 - 23: bitmap_directory_offset
位图目录在镜像内的起始偏移,必须按簇对齐。
全盘加密头指针
当且仅当 crypt_method 要求元数据时,必须存在此扩展。目前仅 LUKS 要求,其他方法必须不含。
提供加密附加数据的存储偏移及长度。
Byte 0 - 7: 加密头在镜像内的字节偏移,必须按簇对齐。
Byte 8 - 15: 已写加密头的字节长度。
实际分配空间可能更大,会向上舍入到簇大小整数倍,
未使用字节初始化为 0。
LUKS 方法的加密头如下。
头部簇的前 592 字节包含 LUKS 分区头,后面是密钥材料区域, 大小由密钥槽条带数和密钥大小决定。 分区头格式见 cryptsetup 源码的 docs/on-disk-format.pdf。
分区头的 payload-offset 按 LUKS 规范正常计算,即相对 LUKS 头起点的 头大小、密钥材料区和填充总和,无需按 qcow2 簇对齐。 qcow2 自己定义真实载荷位置,因此当前不使用此值,但仍应提供有效值。
密钥槽头的 key-material-offset 相对 qcow2 容器中 LUKS 头簇起点, 不是 qcow2 文件起点。
逻辑布局如下:
+-----------------------------+
| QCow2 文件头 |
| QCow2 头扩展 X |
| QCow2 头扩展 FDE |
| QCow2 头扩展 ... |
| QCow2 头扩展 Z |
+-----------------------------+
| ....其他 QCow2 表.... |
. .
. .
+-----------------------------+
| +-------------------------+ |
| | LUKS 分区头 | |
| +-------------------------+ |
| | LUKS 密钥材料 1 | |
| +-------------------------+ |
| | LUKS 密钥材料 2 | |
| +-------------------------+ |
| | LUKS 密钥材料 ... | |
| +-------------------------+ |
| | LUKS 密钥材料 8 | |
| +-------------------------+ |
+-----------------------------+
| QCow2 簇载荷 |
. .
. .
. .
| |
+-----------------------------+
数据加密
头部请求加密时,每次读写必须解密/加密镜像载荷。镜像头和元数据始终不加密。
算法随方法而异:
AES:
256 位密钥的 AES-CBC。
初始化向量以 plain64 方法生成,以虚拟磁盘扇区作为 tweak 输入。
由于多项设计缺陷影响安全,QEMU 系统仿真器不再支持。 仅命令行工具保留,用于向后兼容和取回数据。
LUKS:
算法由 LUKS 头指定。
初始化向量使用头部指定的方法,以物理磁盘扇区作为 tweak 输入。
主机簇管理
qcow2 为每个主机簇维护引用计数。0 表示空闲,1 表示使用中, 大于等于 2 表示使用中且任何写入都必须执行写时复制(COW)。
引用计数采用两级表。第一级 refcount table 大小可变,记录于头部, 可跨多簇,但在镜像文件中必须连续。
其指针指向第二级 refcount block,每块恰好一个簇。
足够大的表可保留超过 64 PB(56 位)的簇,前提是底层协议支持, 但 L1/L2 等元数据仍必须指向该界限之前的簇。
备注
QEMU 引用计数表上限为 8 MB。2 MB 簇、默认 refcount_order=4 时, 无法引用超过 2 EB(61 位)的主机资源;最坏情况下, 簇大小 512、refcount_order=6 时无法访问超过 32 GB(35 位)。
给定镜像文件偏移,其簇引用计数可这样获取:
refcount_block_entries = (cluster_size * 8 / refcount_bits)
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];
引用计数表条目:
Bit 0 - 8: 保留,设为 0。
9 - 63: 引用计数块起始文件偏移的第 9-63 位,必须按簇对齐。
为 0 表示尚未分配该块,其管理的全部引用计数均为 0。
引用计数块条目,(x = refcount_bits - 1):
Bit 0 - x: 簇的引用计数。
若 refcount_bits 表示不足一字节的位宽,此处位 0 指最低有效位。
簇映射
客户机簇到主机簇的映射同样使用两级结构,称为 L1 和 L2 表。
L1 大小可变,记录于头部,可占多簇但必须连续。L2 恰好一个簇。
两级表影响最大虚拟文件大小。L1 大小固定时,较大簇使客户机可访问更多空间。 当前虚拟簇必须映射到低于 64 PB(56 位)的主机偏移, 未来可使用保留位放宽。簇增大时,压缩簇最大主机偏移反而减小: 2M 簇要求压缩簇低于 512 TB(49 位),放宽需不兼容的布局变更。
虚拟磁盘偏移转换为镜像文件偏移的算法:
l2_entries = (cluster_size / sizeof(uint64_t)) [*]
l2_index = (offset / cluster_size) % l2_entries
l1_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)
[*] this changes if Extended L2 Entries are enabled, see next section
L1 表条目:
Bit 0 - 8: 保留,设为 0。
9 - 55: L2 表起始文件偏移的第 9-55 位,必须按簇对齐。
偏移为 0 表示 L2 表及其描述的全部簇未分配。
56 - 62: 保留,设为 0。
63: L2 表未使用或需 COW 时为 0;引用计数恰为 1 时为 1。
此信息仅在活动 L1 表中准确。
L2 表条目:
Bit 0 - 61: 簇描述符。
62: 0:标准簇。
1:压缩簇。
63: 未使用、压缩或需 COW 的簇为 0。
引用计数恰为 1 的标准簇为 1。
仅从活动 L1 可达的 L2 表保证此信息准确。
外部数据文件中,主机偏移与客户机偏移固定相等,
因而全部客户机簇隐含引用计数为 1,已分配簇此位应为 1。
标准簇描述符:
Bit 0: 为 1 时簇读取为全零。主机簇偏移可描述预分配,
但不会用于读取本簇;未分配时也不读取后备文件。
版本 2 或扩展 L2 条目时,此位始终为 0。
1 - 8: 保留,设为 0。
9 - 55: 主机簇偏移第 9-55 位,必须按簇对齐。
偏移为 0 且位 63 清除时,簇未分配。
只有外部数据文件允许偏移为 0 且位 63 设置,表示真实主机偏移 0。
56 - 61: 保留,设为 0。
压缩簇描述符,(x = 62 - (cluster_bits - 8)):
Bit 0 - x-1: 主机簇偏移,通常不按簇或扇区对齐。
cluster_bits 足够小、字段覆盖超过位 55 时,高出的位必须为 0。
x - 61: 除前字段偏移所在扇区外,压缩数据额外使用的 512 字节扇区数。
部分扇区可能位于下一个连续主机簇。
压缩数据不一定占满最后扇区,解压生成一个簇的数据后即停止。
另一个压缩簇可映射到本压缩簇最后扇区的尾部。
簇未分配时,读取应转向后备文件,标准簇描述符位 0 已设置时除外。 没有后备文件,或后备文件小于镜像时,未覆盖部分读取为零。
扩展 L2 条目
头部 incompatible_features 的位 4 设置时使用扩展 L2。
标准数据簇分为 32 个等大的连续子簇,从簇起点开始。 子簇可独立分配,L2 记录每个状态。压缩簇没有子簇,处理与未启用该功能时相同。
扩展条目为 128 位,每表条目数:
l2_entries = (cluster_size / (2 * sizeof(uint64_t)))
前 64 位与上一节标准 L2 格式相同,但标准簇描述符位 0 例外。
后 64 位为以下子簇分配位图。
标准簇子簇分配位图:
Bit 0 - 31: 分配状态,每子簇一位。
1:已分配,主机簇偏移必须有效。
0:未分配,读取转向后备文件;无后备数据时返回零。
从最低有效位开始分配,位 x 对应子簇 x。
32 - 63 子簇读取为零,每子簇一位。
1:读取为零,分配状态位必须清除,主机偏移可设置也可不设置。
0:无作用。
从最低有效位开始,位 x 对应子簇 x - 32。
压缩簇子簇分配位图:
Bit 0 - 63: 保留,设为 0。
压缩簇没有子簇,不使用此字段。
快照
qcow2 支持内部快照,基本原理是切换活动 L1,将不同的主机簇集合暴露给客户机。
创建快照时复制 L1,并增加其可达全部 L2 和数据簇的引用计数, 使后续写入触发 COW,不被其他快照看到。
加载快照时,必须从引用计数表重建新活动 L1 及其引用全部 L2 的位 63, 因为非活动 L1 无需保持这些位准确。
全部快照的目录位于连续的快照表,起点与长度由 snapshots_offset 和 nb_snapshots 给出。 各条目长度随 ID、名称和额外数据变化。
快照表条目:
Byte 0 - 7: 快照 L1 表在文件内的起始偏移,按簇对齐。
8 - 11: 快照 L1 表条目数。
12 - 13: 快照唯一 ID 字符串长度。
14 - 15: 快照名称长度。
16 - 19: 快照创建时间,自纪元起的秒数。
20 - 23: 创建时间不足一秒的部分,单位纳秒。
24 - 31: 创建快照前客户机已运行的纳秒数。
32 - 35: 虚拟机状态字节大小,未保存时为 0。
状态从首个不描述普通客户机簇的 L1 条目对应的第一簇开始。
即像客户机磁盘内容一样存储,但偏移超过客户机可见虚拟磁盘大小。
36 - 39: 条目中额外数据的大小,用于未来格式扩展。
variable: 未来扩展数据,未知字段必须忽略。
当前定义如下,偏移相对快照条目:
Byte 40 - 47: 虚拟机状态字节大小,未保存时为 0。
存在时忽略字节 32-35 的 32 位值。
Byte 48 - 55: 快照虚拟磁盘字节大小。
Byte 56 - 63: 快照时录制/重放的指令计数 icount。
未启用 icount 时设为 -1。
版本 3 必须至少包含直到字节 55 的额外数据。
variable: 快照唯一 ID 字符串,不以 NUL 终止。
variable: 快照名称,不以 NUL 终止。
variable: 将条目大小向上补齐到下一个 8 的倍数。
位图
位图扩展可保存与虚拟磁盘相关的位图,本节描述存储方式。
所有保存的位图都对应同一镜像内的虚拟磁盘,因此每个位图覆盖范围等于虚拟磁盘大小。
每位对应严格定义的磁盘范围。位 bit_nr 对应以下字节范围:
[bit_nr * bitmap_granularity .. (bit_nr + 1) * bitmap_granularity - 1]
粒度是每个位图自己的属性,见下文。
位图目录
每个保存的位图对应一个目录条目。目录在文件内连续, 偏移与长度由 bitmap_directory_offset、bitmap_directory_size 给出。 条目长度随名称和额外数据变化。
目录条目结构:
Byte 0 - 7: bitmap_table_offset
该位图的位图表在文件内的起始偏移,按簇对齐。
8 - 11: bitmap_table_size
位图表条目数。
12 - 15: flags
Bit
0: in_use
未正确保存,可能不一致。元数据对 qcow2 而言结构仍正确,
但 auto、大小等元数据或实际内容可能过时。
1: auto
必须反映任何应用对该 qcow2 虚拟磁盘的全部改变,
包括写入、快照切换等。类型必须为脏跟踪位图。
2: extra_data_compatible
软件不认识额外数据时有意义,当前 QEMU 不认识任何额外数据。
设置时可正常使用位图,但额外数据必须保持原样。
未设置时不得使用位图,但位图和额外数据都必须原样保留。
Bits 3 - 31 保留,必须为 0。
16: type
位图类型:
1: 脏跟踪位图。
0、2 - 255 保留。
17: granularity_bits
粒度位数,有效 0 - 63。
QEMU 当前仅支持 9 - 31。
粒度计算:
granularity = 1 << granularity_bits
粒度表示位图一位对应的镜像字节数。
18 - 19: name_size
名称大小,必须非零。QEMU 当前不支持超过 1023。
20 - 23: extra_data_size
类型专属额外数据大小。
当前未定义额外数据,此字段保留,应为零。
非零时行为由 extra_data_compatible 决定。
variable: extra_data
占 extra_data_size 字节。
绝不能引用簇,也不能通过其他方式分配额外簇。
variable: name
占 name_size 字节,不以 NUL 终止。
在同一位图扩展的所有名称中必须唯一。
variable: 填充至条目大小为下一个 8 的倍数,所有填充字节必须为零。
位图表
位图数据到主机簇的映射使用单级结构,称为位图表, 不同于引用计数和客户机簇映射的两级结构。
大小可变,记录于目录条目,可使用多簇,但必须在文件中连续。
位图表条目:
Bit 0: 位 9 - 55 非零时保留,必须为零。
位 9 - 55 全零时:
0:簇应读取为全零。
1:簇应读取为全一。
1 - 8: 保留,必须为零。
9 - 55: 主机簇偏移第 9 - 55 位,必须按簇对齐。
偏移 0 表示未分配,此时位 0 决定读取行为。
56 - 63: 保留,必须为零。
位图数据
位图数据存于独立簇,由位图表描述。 给定位图数据字节偏移,文件偏移计算如下:
image_offset(bitmap_data_offset) =
bitmap_table[bitmap_data_offset / cluster_size] +
(bitmap_data_offset % cluster_size)
位图表条目位 9–55 全零时,此偏移未定义,见上文。
给定磁盘字节偏移 byte_nr 和粒度,对应位在镜像文件中的位偏移为:
bit_offset(byte_nr) =
image_offset(byte_nr / granularity / 8) * 8 +
(byte_nr / granularity) % 8
位图大小不是簇大小整数倍时,最后簇有未使用尾部位,必须为零。
脏跟踪位图
type 为 1 的位图即脏跟踪位图。
虚拟磁盘使用期间,位图可启用或禁用。 启用时所有写入都应反映在位图,置位表示对应磁盘范围在启用期间被写过, 未置位表示未写过。
软件无需每次写入或元数据变化都同步磁盘位图与内存表示。 不同步期间应设置 in_use。
文件中的启用状态由 auto 表示。设置时软件必须视位图为启用, 从虚拟磁盘首次写入起跟踪变化;未设置则禁用。