news 2026/10/1 4:31:49

KVM/QEMU/qcow2/RAW/COW/ROW分层解析与磁盘选型调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
KVM/QEMU/qcow2/RAW/COW/ROW分层解析与磁盘选型调优

事情得从上周一次扯皮说起。同事在群里问"给服务器做系统用 kvm 还是 qemu",另一个回"qcow2 和 raw 到底哪个快",第三个人甩出一句"COW 和 ROW 不是一回事吗"。三个问题搅在一起,本质上是在问三个完全不同层面的东西:跑虚拟化用什么组件、磁盘镜像存成什么格式、镜像格式内部用的什么写入策略。把这三层揉成一团,就会出现"kvm、qemu、qcow2、RAW、COW、ROW 傻傻分不清"的经典混乱。

我把这套东西重新梳理了一遍,顺手在 Ubuntu 24.04 上从零跑了一台机器,踩过的坑也都记下来了。适合两类人看:一类是刚开始接触 Linux 虚拟化、想搞明白这套名词到底谁是谁的人;另一类是在生产环境里用 KVM 跑业务、需要做磁盘格式选型和性能调优的人。看完之后你至少能做到三件事:说清这几个缩写分别在栈的哪一层、根据业务场景选对镜像格式、用 qcow2 的快照链做安全的回滚和瘦身。

1. 先把这堆缩写的层级关系钉死

名词混乱的根源,是它们压根不在一个层面上。KVM 是内核里的东西,QEMU 是用户态的程序,qcow2 和 RAW 是磁盘文件的格式,COW 和 ROW 是数据写入的策略。硬要类比的话:KVM 是发动机,QEMU 是整车,qcow2/RAW 是油箱的规格,COW/ROW 是加油的方式。你不能问"发动机和油箱哪个好",同理也不能问"kvm 和 qcow2 哪个快"。

1.1 KVM:内核里的一颗模块,只负责让 CPU 跑起来

KVM 全称 Kernel-based Virtual Machine,它不是一个软件包,也不是一个能双击启动的程序。它本质上是 Linux 内核的一组模块:通用的kvm.ko加上平台相关的kvm_intel.ko或kvm_amd.ko。这三个模块加载之后,内核会多出一个字符设备/dev/kvm,用户态程序通过 ioctl 调它,就能把一段代码丢进 CPU 的虚拟化扩展(Intel VT-x 或者 AMD-V)里直接执行。

这件事的意义在于:如果没有 KVM,虚拟机里的每一条指令都得靠软件模拟翻译一遍,性能会掉到个位数百分比;有了 KVM,客户机的特权指令由 CPU 硬件直接兜住,只有真正需要"假装"的部分(比如模拟一块老式网卡)才走软件路径。所以我经常跟人说,KVM 的贡献是"让虚拟机的计算性能接近物理机",它不管磁盘、不管网络、不管设备,它只管 CPU 和内存的隔离。

验证 KVM 是否可用,两条命令就够:

grep -E -c '(vmx|svm)' /proc/cpuinfo # 大于 0 说明 CPU 支持硬件虚拟化 ls -l /dev/kvm # 存在说明内核模块已加载

vmx是 Intel 的旗标,svm是 AMD 的。如果第一条返回 0,通常要去 BIOS/UEFI 里开一下虚拟化相关选项;如果 CPU 支持但/dev/kvm不存在,检查kvm_intel是不是被别的模块占用了——嵌套虚拟化、部分安全策略、某些云厂商的实例类型都会屏蔽它。

1.2 QEMU:真正在用户态"拼出一台电脑"的整机模拟器

QEMU 是一个纯用户态的程序,它做的事是完整模拟一台计算机:主板芯片组、PCI 总线、磁盘控制器、网卡、显卡、USB 控制器、UEFI/BIOS 固件,全都由它用软件搭出来。它有两套执行引擎,一套是纯软件解释(TCG,Tiny Code Generator),另一套是接上 KVM 走硬件加速。

这就解释了为什么装了 KVM 还得装 QEMU。KVM 只提供"CPU 能跑"这个能力,至于客户机看到的是一块 virtio 磁盘还是 IDE 磁盘、网卡是 e1000 还是 virtio-net、启动固件是 SeaBIOS 还是 OVMF,这些全得由 QEMU 来构造和分发。你敲的qemu-system-x86_64就是 QEMU 的主程序,-accel kvm就是告诉它"这条虚拟机用 KVM 加速,别用 TCG 慢慢磨"。

顺带说一句,qemu-system-aarch64、qemu-system-riscv64这些也是 QEMU,只是模拟的目标架构不同。KVM 加速只在"宿主机架构等于客户机架构"时才能用(也就是 x86 上跑 x86),跨架构就必须退回 TCG 软件模拟,性能会差两个数量级。这是很多人做 arm64 模拟时最大的预期落差来源。

1.3 libvirt/virsh/virt-manager:站在 QEMU 上面的管理层

裸敲 QEMU 参数能干所有事,但参数太长、太容易写错,而且没有统一的"虚拟机清单"概念。libvirt 就是为了解决这个:它把虚拟机的定义写成一份 XML,存到固定位置,然后用virsh或virt-manager去操作。libvirt 底层还是调 QEMU,它不替代 QEMU,只是加了一层编排和生命周期管理。

日常能感受到的差别是:裸 QEMU 启动的虚拟机,关了终端进程就没了,virsh list里也看不见;走 libvirt 的虚拟机,virsh list --all能列出全部,还能设置开机自启、做快照、热插拔磁盘、查询块设备层信息。做生产环境,我强烈建议走 libvirt 这套,不是因为它更好看,而是因为它的可观测性和可重复性完全不是一个量级。

1.4 另一个 KVM:键盘鼠标显示器共享器

这里必须插一句,因为搜"kvm"的人里有一半其实在找硬件设备。KVM Switch(KVM 切换器、KVM 共享器)是把一套键盘、鼠标、显示器接到多台主机上,用按键或按钮切换的硬件盒子,和虚拟化里的 KVM 除了缩写一样,没有任何关系。

至于热词里提到的"怎么看 KVM 的 8K 认证",判断逻辑很直白:先看带宽,8K@60Hz 在 4:4:4 无色度抽样的情况下需要 HDMI 2.1 的 48Gbps 或者 DisplayPort 2.0 的 UHBR 通道,标称 18Gbps 的老设备一定做不到完整 8K;再看它写的具体规格,很多产品标的是 8K@30Hz 或者 8K@60Hz 4:2:0,这两种和"完整 8K 60 帧"是两码事;最后看线材,主机到切换器这段如果用的还是老线,切换器再强也没用。买之前把这三个数字对一遍,比看任何宣传语都管用。

1.5 一张表把六层关系收拢

名词所属层级具体是什么少了它会怎样
KVM内核模块/dev/kvm字符设备,提供 CPU 硬件虚拟化虚拟机只能用 TCG 软件模拟,性能骤降
QEMU用户态程序整机模拟器,构造主板/设备/固件内核有能力但没人去用它组机器
libvirt管理服务XML 定义 + virsh/virt-manager 前端能跑,但没有统一清单和生命周期管理
RAW文件格式无元数据,字节即扇区无法内建快照,无法增量备份
qcow2文件格式带 L1/L2 映射表、引用计数、快照缺了灵活快照与后端文件链能力
COW / ROW写入策略数据落盘时的重定向方式决定写放大、碎片与快照成本

2. RAW 与 qcow2:磁盘格式到底怎么挑

搞清层级之后,第二个高频问题就是格式选型。RAW 和 qcow2 是 QEMU 支持的一堆格式里最常用的两个,很多人只知道"raw 快、qcow2 功能多",但不知道具体快在哪、功能多在哪,选的时候全靠感觉。

2.1 RAW 不是格式,是"没有格式"

RAW 的本质是:文件里的第 N 个字节,就对应磁盘的第 N 个字节。没有头部、没有映射表、没有元数据。好处是很实在的:QEMU 读写的时候不需要做任何地址翻译,也不需要维护额外的元数据结构,I/O 路径最短,性能上限最高,而且这文件可以直接挂给别的工具用——dd、losetup、离线取证工具都能直接读。

代价也很明显。第一,它不支持内部快照,想做快照只能在下层文件系统或者 LVM 上想办法。第二,它不支持后端文件链,做增量备份得自己搭一套。第三,如果底层文件系统不支持稀疏文件,创建 40G 的 RAW 就真占 40G。

这里有个容易被忽略的细节:qemu-img create -f raw disk.img 40G默认会创建稀疏文件,实际占用接近 0。你可以用du -h disk.img和ls -l disk.img对比一下,前者是实际占用,后者是逻辑大小。但在 ext4 上,稀疏文件写入过程中会不断分配新块,长期运行之后碎片会比较厉害,顺序读性能会衰减。生产环境里我一般会配fallocate预分配,把碎片问题提前消掉。

2.2 qcow2 的目录结构:L1/L2 与引用计数

qcow2 全称 QEMU Copy-On-Write 格式,它做的核心事情是间接寻址。客户机发出的是"读第 12345 号扇区",qcow2 要把它翻译成"读宿主文件里的第 X 个字节"。这套翻译靠三级结构完成:文件头里有 L1 表的指针,L1 表指向若干张 L2 表,L2 表里存的是真正数据簇的位置。

理解这个结构,两个参数才算真的懂了:

  • cluster_size(簇大小):默认 64K。L2 表每一项占 8 字节,所以一张 64K 的 L2 表能装 8192 项,每项覆盖一个簇,也就是一张 L2 表覆盖 8192 × 64K = 512MB 的客户机地址空间。如果你把簇改成 1M,单张 L2 表就能覆盖 8GB,元数据开销显著下降,但小文件写入的放大也会变严重。
  • refcount_bits(引用计数位宽):qcow2 v3 默认 16 位。引用计数用来记录每个簇被多少个快照/后端文件共享着,共享计数归零才能回收。位数越高,能同时容纳的快照层数越多,但元数据占用也越大。

这两个参数在qemu-img info里都能看到,qemu-img check还能校验引用计数有没有算错——快照删不干净、文件删了空间不释放,八成是引用计数出了问题。

2.3 预分配四种模式的真实取舍

qemu-img create -o preallocation=有四个取值,很多人随手写metadata但说不清为什么。我在下面这张表里把它们的实际行为写清楚:

预分配模式元数据是否预分配数据块是否预分配创建耗时适用场景
off否否秒级临时测试机、开发环境
metadata是否秒级到十秒级生产主力,兼顾创建速度和运行稳定性
falloc是是(用 fallocate 快速占位,内容为空洞)秒级需要固定占用、避免中途扩容抖动
full是是(并且逐字节写零)分钟级,取决于盘大小需要严格保证空间、审计场景

full和falloc的差别值得单独说:full会真把每个字节写成 0,创建 1T 的盘可能要十几分钟;falloc只是让文件系统把这些块"预留"下来,不写内容,速度快得多。除非有合规审计要求"磁盘必须写零",否则falloc就够了。

一个实际的经验:用metadata预分配之后,qcow2 在客户机第一次写某个簇的时候仍然要分配新簇并更新 L2 表,这段时间的写入延迟会有轻微抖动。如果你的业务对延迟敏感(比如数据库),用falloc更稳。我做过一组对比,同样跑随机写,metadata模式的前 30 秒写入吞吐波动比falloc明显大一些,之后就趋于一致了。

2.4 选型表:什么业务配什么格式

业务特征推荐格式理由
单机数据库、高 IOPS 场景RAW + LVM 快照无间接寻址,I/O 路径最短
需要频繁做快照回滚的测试环境qcow2 + 外部快照秒级创建,回滚成本低
需要在线迁移、增量备份qcow2 + 后端文件链只传变更簇,带宽省得多
磁盘一次性交付、只读挂载RAW(或qemu-img convert后转出)格式通用,任何工具都能读
桌面虚拟化、镜像模板分发qcow2 + 压缩镜像 + 后端文件模板共享,增量盘极小

我的默认建议是:没有特殊理由就上 qcow2。RAW 的性能优势在纯 SSD、virtio、cache=none的组合下,实际差距往往在个位数百分比,而 qcow2 带来的快照、克隆、迁移便利性,在运维层面的价值远超这点性能差。只有在明确测出瓶颈落在存储路径时,才值得为 RAW 放弃那些能力。

3. COW 和 ROW:被混淆最多的一对概念

这两个词是混乱的重灾区。很多人以为它们是 qcow2 和 RAW 的别称,其实不是。COW 和 ROW 描述的是"当你要修改一份被共享的数据时,怎么处理旧数据"这件事,它是文件系统、存储快照、甚至内存管理里通用的一套策略。

3.1 COW 的操作顺序:先抄旧的,再写新的

写时复制(Copy-On-Write)的流程是:数据块 A 被两个快照共享,现在要改 A。系统先把 A 的原始内容完整复制到新位置 B,然后对 B 做修改,最后把指针从 A 指向 B,A 的引用计数减一。全程 A 的内容始终不变,所以任何还指向 A 的快照都还能读到改动前的状态。

这个顺序决定了 COW 的代价:写操作变成"读旧 + 写新 + 改指针"三步。在随机小写入的场景下,这个放大效应很明显,而且长期运行会产生碎片,因为新块的位置是随机的。为什么很多文件系统在开了快照之后写入性能会明显下滑,原因就在这里——只要底层还有快照引用,每次写都得走这套流程。

放在 qcow2 里看,这个机制就体现在:客户机第一次写某个未分配的簇时,QEMU 要在宿主文件里找一个空闲簇,写入数据,更新对应的 L2 表项,同时更新引用计数。这不是"复制旧数据"那么严格,但逻辑内核是一致的——不改共享结构,改了就新建。

3.2 ROW 换了个思路:新数据写新地方,旧块直接还给分配器

重定向写(Redirect-On-Write)的动作顺序不一样:数据要改,直接把新内容写到全新的位置 C,把指针指过去,然后立刻把旧块 A 的引用计数减一;如果减到零,A 可以被回收。整个过程中,旧数据不需要被复制,只需要被保留到引用归零为止。

省掉"复制旧数据"这一步,就是 ROW 相对 COW 的核心优势:写放大更低,写路径更短,快照创建几乎是纯元数据操作,速度极快。代价是碎片化更严重——每次写都跑到新位置,数据在物理介质上的连续性会越来越差;而且空间回收依赖后台的垃圾回收机制,如果 GC 跟不上写入速度,就会出现"空间明明释放了但可用容量涨不回来"的现象。

3.3 两者对照

对比项COW(Copy-On-Write)ROW(Redirect-On-Write)
写操作顺序先复制旧块到新位置,再改新块直接写新位置,旧块引用减一
首次写延迟较高,包含一次读取较低,只写不回读
空间回收相对及时,旧块立即可释放依赖后台 GC,可能滞后
碎片倾向中等偏高
快照创建成本低极低
典型落地qcow2 簇分配、多数文件系统快照部分存储阵列的快照、日志结构文件系统

回到最初的问题上:qcow2 名字里带 COW,指的是它采用写时复制的分配策略;而 ROW 是另一条技术路线,不是 qcow2 的子集,也不是 RAW 的别名。搞清这个,以后再看到"COW 和 ROW 哪个好"这种问法,就知道问题本身少了限定条件——得先问是哪一层的 COW。

3.4 qcow2 快照链就是 COW 的落地形态

qcow2 的实际用法里,COW 最直观的体现就是后端文件链(backing file chain)。做法是:先做一个只读的基础镜像,然后基于它派生一个可写的增量盘,增量盘只记录相对于基础镜像的变更。开十台测试机,共用一份 20G 的基础镜像,每台的增量盘可能只有几百兆。

创建方式很简单:

qemu-img create -f qcow2 -b base.qcow2 -F qcow2 vm01.qcow2

-b指定后端文件,-F指明后端文件的格式。读的时候,QEMU 从最上层往下找,找到哪个簇在顶层有映射就用顶层,没有就往下层找。写的时候,只有被修改的簇才会分配在顶层文件里。

这套机制的坑在于链条不能太长。每多一层,一次未命中就要往下遍历一层,元数据缓存压力也会累加。我见过有人用脚本每天派生一层,跑了三个月叠了九十层,读性能掉到没法用。经验值是链条深度控制在三层以内,超过就做一次blockcommit把中间层合并掉。

virsh blockcommit vm01 vda --active --pivot --verbose

--active表示合并当前活跃层,--pivot表示合并完成后把虚拟机切换到新文件上,不用关机,也不用重建虚拟机定义。

4. 在 Ubuntu 24.04 上从零建一台 KVM 虚拟机

前面讲原理,这一节全是操作。我用的环境是 Ubuntu 24.04 Server,物理机开了 VT-x,网络是普通千兆。整个过程大约十分钟。

4.1 装包:三个包解决 90% 需求

sudo apt update sudo apt install -y qemu-system-x86 qemu-utils libvirt-daemon-system \ libvirt-clients virtinst bridge-utils ovmf sudo usermod -aG libvirt,kvm $USER newgrp libvirt

包的分工要说清:qemu-system-x86提供qemu-system-x86_64主程序,qemu-utils提供qemu-img、qemu-nbd这些工具,libvirt-daemon-system是守护进程,libvirt-clients提供virsh,virtinst提供virt-install,ovmf是 UEFI 固件。usermod那行是把自己加进 libvirt 和 kvm 两个组,否则每次virsh都得 sudo。

装完之后检查服务状态:

systemctl is-active libvirtd virsh version virsh net-list --all

virsh version会打印两行版本号,一行是 libvirt 库,一行是 QEMU 二进制。这两个版本号要记住,后面遇到兼容性问题经常要从这里对。

4.2 建磁盘:参数不是随便填的

sudo mkdir -p /var/lib/libvirt/images sudo qemu-img create -f qcow2 \ -o preallocation=metadata,cluster_size=64K,lazy_refcounts=on \ /var/lib/libvirt/images/ubuntu24.qcow2 40G

三个参数我逐个解释。preallocation=metadata把 L1/L2 表提前建好,避免运行中第一次写的时候还要分配元数据;cluster_size=64K是默认值,如果你的客户机主要是大文件顺序读写(比如视频转码、大数据临时盘),可以调到1M减少 L2 表数量,但小文件随机写的放大也会变大;lazy_refcounts=on让引用计数延迟更新,配合cache=writeback能提高写入性能,代价是掉电时可能要做一次qemu-img check修复。

建完验证一下:

qemu-img info --output=json /var/lib/libvirt/images/ubuntu24.qcow2 qemu-img check /var/lib/libvirt/images/ubuntu24.qcow2

check的输出里如果corruptions是 0,说明元数据是干净的。以后每次虚拟机异常关机之后都建议跑一遍。

4.3 建机:BIOS 还是 UEFI

老规矩用 SeaBIOS(传统 BIOS)能跑,但 Ubuntu 24.04 的安装镜像对 UEFI 支持更好,而且 UEFI 支持 Secure Boot。我这边直接用 UEFI:

sudo virt-install \ --name ubuntu24 \ --memory 4096 --vcpus 4 \ --cpu host-passthrough \ --machine q35 \ --disk path=/var/lib/libvirt/images/ubuntu24.qcow2,format=qcow2,bus=virtio,cache=none,io=native,discard=unmap \ --cdrom /data/iso/ubuntu-24.04-live-server-amd64.iso \ --network network=default,model=virtio \ --graphics vnc,listen=127.0.0.1,port=5901 \ --boot uefi \ --os-variant ubuntu24.04 \ --noautoconsole

几个参数值得展开。--cpu host-passthrough把宿主 CPU 的指令集特性直接透传给客户机,性能最好,代价是这台虚拟机不能再迁移到 CPU 型号不同的宿主机上——如果你要做集群迁移,改用--cpu host-model。--machine q35是模拟现代芯片组(支持 PCIe),比默认的 i440fx 更贴近真实硬件,UEFI 场景下基本是必选项。

磁盘那一串里,bus=virtio用的是 virtio-blk 半虚拟化驱动,比模拟 IDE 快得多;cache=none让 I/O 绕开宿主页缓存直接走 O_DIRECT,避免双重缓存;io=native启用原生异步 I/O,只有cache=none时才生效;discard=unmap让客户机发来的 TRIM 指令能真正传导到宿主文件,这对 qcow2 瘦身至关重要,后面还会细说。

--graphics vnc,listen=127.0.0.1是只监听本地回环,装系统的界面通过 SSH 端口转发看。这样不用把 VNC 端口暴露出去,安全性更好。

首次启动之后,在另一个终端里把安装界面转出来:

ssh -L 5901:127.0.0.1:5901 user@host # 本地用任意 VNC 客户端连 127.0.0.1:5901

4.4 网络:默认 NAT 够用,桥接才进生产

libvirt 默认会创建一个叫default的网络,宿主上多出一块virbr0,客户机通过它做 NAT 上网,能出不能进。开发和测试完全够用,但生产环境通常要客户机和宿主机在同一个二层网络里。

桥接的配置在不同发行版和网络管理方式下差别很大。用 NetworkManager 的话,命令行是:

sudo nmcli connection add type bridge ifname br0 con-name br0 \ ipv4.method manual ipv4.addresses 192.168.1.50/24 \ ipv4.gateway 192.168.1.1 ipv4.dns 223.5.5.5 sudo nmcli connection add type ethernet ifname enp3s0 master br0 con-name br0-slave sudo nmcli connection up br0

然后把 libvirt 的默认网络切到桥接,或者在virt-install时直接指定--network bridge=br0,model=virtio。注意桥接会把物理网卡的 IP 挪到桥上,中间会短暂断网,远程操作的话最好配个定时恢复脚本兜底,别问我怎么知道的。

如果不想动宿主机网络,还有一个折中方案是 macvtap,客户机直接借用物理网卡的 MAC 直通,配置量小,代价是宿主机和客户机之间不能直接通信(这是 macvtap 的固有特性),且部分交换机对多 MAC 场景有限制。

4.5 日常管理:几个我会背下来的命令

virsh list --all # 所有虚拟机状态 virsh dominfo ubuntu24 # 配置概览 virsh domblklist ubuntu24 # 磁盘列表 virsh domiflist ubuntu24 # 网卡列表 virsh vncdisplay ubuntu24 # 当前 VNC 端口 virsh console ubuntu24 # 串口控制台(需客户机配好 ttyS0) virsh autostart ubuntu24 # 开机自启 virsh domstats ubuntu24 --block # 块设备实时统计

virsh domstats --block这个命令我强烈建议记住。它会打印每个磁盘的读写次数、字节数、延迟,做性能排查的时候,能一眼看出瓶颈是在客户机内部还是在存储路径上。

5. 不用 libvirt 的裸 QEMU,以及跨架构模拟

有时候就是需要裸跑 QEMU:排查 libvirt 层的问题、做一次性的系统安装、或者跑一个临时环境。这时候理解 QEMU 的参数结构就很有价值。

5.1 一条裸 QEMU 命令逐段拆

qemu-system-x86_64 \ -accel kvm \ -machine q35,accel=kvm \ -cpu host \ -smp 4,sockets=1,cores=2,threads=2 \ -m 4G \ -drive file=disk.qcow2,if=virtio,format=qcow2,cache=none,aio=io_uring \ -netdev user,id=n0,hostfwd=tcp::2222-:22 \ -device virtio-net-pci,netdev=n0 \ -vnc 127.0.0.1:1 \ -monitor stdio

-accel kvm是加速开关,老教程里的-enable-kvm已经废弃了。-smp 4,sockets=1,cores=2,threads=2这种写法比单纯写-smp 4更明确,某些客户机操作系统会根据拓扑调整调度策略。-netdev user是 QEMU 内置的用户态网络,hostfwd把宿主的 2222 端口映射到客户机的 22 端口,做临时调试最方便,不需要任何 root 权限和网络配置改动。

-monitor stdio把 QEMU 的监视器接到当前终端,进去之后能敲info block、info network、snapshot这些命令。这个监视器不依赖客户机,哪怕系统已经卡死了,也能从宿主机侧看到状态,是排查"虚拟机毫无响应"这类问题的第一现场。

5.2 qemu 模拟 arm64:预期要放低

跨架构的场景很常见,比如在 x86 开发机上验证 arm64 的镜像构建。QEMU 能做到,但要走 TCG 软件模拟,性能大概只有原生的十分之一到五分之一。

qemu-system-aarch64 \ -machine virt \ -cpu cortex-a72 \ -smp 4 -m 4G \ -bios /usr/share/AAVMF/QEMU_EFI.fd \ -drive file=arm64-rootfs.qcow2,if=virtio,format=qcow2 \ -netdev user,id=n0,hostfwd=tcp::2223-:22 \ -device virtio-net-pci,netdev=n0 \ -nographic

-machine virt是 arm64 下最通用的虚拟平台,-bios指向 AArch64 的 UEFI 固件(Debian/Ubuntu 系在qemu-efi-aarch64包里,路径通常是/usr/share/AAVMF/QEMU_EFI.fd)。注意-nographic会把串口输出直接接到终端,arm64 的virt平台默认会在串口上输出启动日志,比折腾图形界面省事得多。

这里有个必须强调的点:arm64 的 TCG 模拟无论怎么调参都不可能快。如果你的目的是大规模构建 arm64 镜像,正确做法是找一台真的 arm64 机器,或者用支持原生 arm64 的构建服务。QEMU 模拟适合做功能验证,不适合做性能测试。

5.3 qemu-user-static:另一条完全不同的路

上面说的是 full-system 模拟,还有一条路是用户态模拟,只模拟指令集不模拟整机。装qemu-user-static之后,配合 binfmt_misc,可以直接在 x86 上运行 arm64 的单个二进制:

sudo apt install -y qemu-user-static binfmt-support sudo systemctl restart systemd-binfmt # 验证 docker run --rm --platform linux/arm64 arm64v8/alpine uname -m

这条路比整机模拟轻量得多,适合跑单进程、构建镜像、跑测试套件。代价是没有内核,涉及内核模块、io_uring、特定系统调用的程序可能跑不起来。判断标准很简单:只要能跑成容器镜像里的普通应用,用这条路;需要装系统、改内核参数、做网络实验,走 full-system 那条路。

6. 空间回收与性能调优:qcow2 的三个后续动作

虚拟机跑起来之后,真正让人头疼的是 qcow2 文件"只涨不跌"。客户机里删了 20G 数据,宿主的 qcow2 大小一点没变。这不是 bug,是 COW 机制的必然结果——被删掉的簇在宿主文件里仍然占着位置,只是客户机的文件系统不再引用它们了。

6.1 让 TRIM 指令真正传导下去

第一条要配的链路是discard。客户机的文件系统发出 TRIM,virtio-blk 传到 QEMU,QEMU 再通过unmap告诉宿主文件系统"这块可以还回去了"。任何一个环节断掉,空间都收不回来。

客户机侧确认挂载参数:

mount | grep discard

如果没带discard,可以手动跑一次全盘 TRIM:

sudo fstrim -av

我个人的习惯是不用挂载参数里的discard(它在每次删除时都触发,有额外开销),而是配一个定时任务每周跑一次fstrim。这样开销可控,空间也不会无限涨。

宿主机侧确认discard=unmap已经写进虚拟机定义:

virsh dumpxml ubuntu24 | grep -i discard

如果没有,编辑一下:

virsh edit ubuntu24 # 在 <driver .../> 里加上 discard='unmap'

改完之后需要重启虚拟机才生效。

6.2 压缩矩阵:三种压缩算法的实测取舍

qcow2 支持在转换时压缩,QEMU 5.0 之后还支持 zstd。压缩只在qemu-img convert时发生,运行中的虚拟机不会实时压缩。

# 转换成带压缩的 qcow2,用 zstd qemu-img convert -p -O qcow2 -c -o compression_type=zstd \ /var/lib/libvirt/images/ubuntu24.qcow2 \ /data/img/ubuntu24-zstd.qcow2

三种压缩方式的取舍大致是这样:zlib 兼容性最好,任何版本的 QEMU 都能读,压缩率中等;zstd 压缩速度快、压缩率也不错,但要求 QEMU 5.0 以上且镜像 compat 版本为 1.1;lzo 压缩和解压都很快,压缩率略低。如果镜像要分发给不确定环境的同事,用 zlib 最稳妥。

注意 zstd 有个前置条件:镜像必须是 compat 1.1(qcow2 v3)。老镜像是 1.0 的话,先升一下:

qemu-img amend -o compat=1.1 disk.qcow2

6.3 快照链整理:别让层数无限长

用virsh snapshot-list能看到当前有哪些快照,用virsh snapshot-create-as建一个,virsh snapshot-revert回滚。qcow2 内部快照会直接生成一个新的层,快照多了链就长了。

整理的方式前面提过blockcommit,还有一个是blockpull,把后端文件的内容拉到当前盘里,这样可以把基础镜像独立出去:

virsh blockpull ubuntu24 vda --wait --verbose

整理完成之后,用qemu-img map看一下实际分配的块分布,确认没有大量细碎的空洞:

qemu-img map --output=json /var/lib/libvirt/images/ubuntu24.qcow2 | head -20

如果看到大量很短的 extent,说明碎片化比较严重,这时候做一次离线整理:

virt-sparsify --in-place /var/lib/libvirt/images/ubuntu24.qcow2

--in-place是在原文件上操作,省空间但风险高,一定要先关机并备份。要更保险就用输出到新文件的方式:

virt-sparsify --compress --convert qcow2 \ /var/lib/libvirt/images/ubuntu24.qcow2 \ /data/img/ubuntu24-slim.qcow2

7. 常见问题与排查实录

前面讲的都是"正常路径",实际运维里遇到的多半是异常路径。这一节我把遇到过的问题整理成速查表,再挑三个典型场景展开说。

7.1 问题速查表

现象大概率原因排查动作处理方式
虚拟机启动报Could not access KVM kernel module/dev/kvm不可用或权限不足ls -l /dev/kvm,groups加载 kvm 模块,把用户加进 kvm 组
qcow2 删除数据后体积不降discard 链路断掉virsh dumpxml查 discard 参数,客户机跑fstrim配discard=unmap,定期 fstrim
写入延迟周期性抖动元数据预分配不足或 cache 模式不当qemu-img info看 preallocation,virsh domstats看延迟改preallocation=falloc,cache=none
快照回滚后磁盘变得很大内部快照层叠加过多virsh snapshot-list,qemu-img info --backing-chain做 blockcommit 合并
qemu-img check报引用计数错误异常掉电导致 lazy refcount 未落盘qemu-img check输出详情qemu-img check -r all修复
迁移到另一台机器失败CPU 型号不兼容对比两台宿主virsh capabilities改--cpu host-model并重启虚拟机
客户机网络时通时断MAC 地址冲突或网桥配置问题virsh domiflist看 MAC,brctl show看端口重新生成 MAC,检查网桥成员

7.2 实录一:qcow2 从 40G 涨到 180G 的真相

有台跑日志分析的虚拟机,分配的盘只有 40G,跑了两周之后宿主上的 qcow2 文件涨到了 180G,看起来完全不合理。第一反应是"是不是被攻击了",但qemu-img info显示虚拟大小还是 40G,只是实际占用异常。

排查路径是这样的:先在客户机里df -h,看到根分区只用了 12G。再用du -sh逐层扫,发现/var/log/journal下面有个将近 100G 的目录——日志系统在磁盘满的时候反复写入失败、又反复重试,写进去了大量无效数据然后被删除,但 qcow2 里对应的簇已经分配出去了,客户机删除并不会自动还给宿主。

根因是discard链路没配。修复动作有三步:先限制 journal 的最大占用,避免再爆;然后在宿主的虚拟机定义里加上discard=unmap;最后在客户机里跑一次sudo fstrim -av。执行完之后,宿主上的 qcow2 从 180G 掉到了 26G。

经验教训是:只做客户机侧的容量监控是不够的,宿主文件的实际占用必须单独监控。我现在的告警规则里,qcow2 实际占用与虚拟大小的比值超过 1.5 就会触发提醒,这时候去看一眼基本都能提前发现问题。

7.3 实录二:快照链叠了 27 层之后

测试环境有个脚本,每天给虚拟机打一个内部快照。跑了将近一个月,有人反馈"这台机器启动要三分钟,跑起来也卡"。我用qemu-img info --backing-chain一看,链深度到了 27 层。

原理层面说,每读一个没在顶层命中的簇,都要一层一层往下查 L2 表;QEMU 会做缓存,但缓存容量有限,链一深,缓存命中率就掉下来,随机读的延迟会变得很难看。写入更麻烦,每次写都要定位到当前活跃层,元数据压力全堆在这一层上。

处理方式是blockcommit,但要注意顺序:不能一次性把 27 层全合掉,那样单次操作耗时太长,中途出错风险大。我的做法是分批合并,每次合并 5 到 8 层,中间观察virsh domstats --block的延迟指标。

virsh blockcommit testvm vda --active --pivot --verbose --wait

之后我把脚本改成保留最近 3 个快照,更早的自动删除。这里有个细节:删除内部快照不会自动释放宿主空间,得配合一次fstrim或者qemu-img convert重写一遍文件,空间才会真正掉下来。

7.4 实录三:arm64 模拟环境里的诡异超时

有次在 x86 机器上用 QEMU 模拟 arm64 跑 CI 构建。单次构建本地跑 20 分钟没问题,但放进 CI 之后频繁超时。一开始怀疑是资源不足,加了 CPU 和内存,改善不明显。

后来把-smp拆成明确的拓扑-smp 4,sockets=1,cores=4,threads=1,并且关掉了宿主的 CPU 节能策略,把电源模式固定成性能模式,超时率明显下降。根本原因是 TCG 模拟对宿主的单核性能极其敏感,宿主一旦降频,模拟出来的客户机时间就直接被拉长,客户机里的超时检测很容易被触发。

顺带说清一个容易误解的点:QEMU 的 TCG 在多核场景下的扩展性并不好,加 vCPU 不等于线性提速,有时反而因为跨核同步开销导致更慢。跨架构模拟场景下,我一般建议 vCPU 数量不超过 4,把预算花在提高单核主频上。

8. 我踩过之后总结的几个判断口径

写到这里,前面所有内容其实都能压缩成几个判断口径,遇到类似问题时直接套用就行。

第一个口径是先分层再选型。看到一个名词,先问它在栈的哪一层。如果它属于内核层(KVM),那就去谈硬件支持;属于用户态程序层(QEMU),那就去谈参数和版本;属于格式层(RAW/qcow2),那就去谈元数据和预分配;属于策略层(COW/ROW),那就去谈写放大和空间回收。分完层,很多看似矛盾的说法就自动和解了。

第二个口径是格式跟着业务走,不要跟着习惯走。需要快照回滚和模板分发就用 qcow2,需要极限 IOPS 并且能接受运维复杂度就用 RAW,两者不是非此即彼,一台宿主机上混用完全正常——模板盘用 qcow2 方便克隆,核心数据库盘用 RAW 保性能。

第三个口径是空间问题永远先看回收链路。qcow2 涨了不降,十次里有八次是discard没配或者客户机没跑fstrim,剩下两次是快照层没合。先把这两件事确认掉,再去怀疑别的。

最后一个我自己用得很顺手的习惯:每次给虚拟机做重大变更之前(改格式、合快照、调预分配),先跑一次qemu-img check,把输出存下来。变更之后再跑一次,两份输出对比一下,元数据有没有被改坏一目了然。这个小动作救过我至少两次,比事后从备份里恢复省事得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 4:31:42

YOLO溺水检测数据集:874张图训练与避坑全攻略

简介&#xff1a;这是面向目标检测开发者和游泳场所安全监控场景的数据集&#xff0c;主要用于溺水行为识别模型的训练与验证。包内共包含2000个文件&#xff0c;以874份xml标注文件与874份txt标签为主体&#xff0c;并配套251张jpg图像与1个yaml配置&#xff0c;压缩包大小约2…

作者头像 李华
网站建设 2026/10/1 4:31:38

Linux磁盘挂载从入门到实战:原理、fstab配置与NAS网络存储详解

搞Linux这些年&#xff0c;磁盘挂载算是遇到频次最高的操作之一了。不管你是刚入行的运维新人&#xff0c;还是自己折腾NAS、装双系统的玩家&#xff0c;都会发现一个问题&#xff1a;网上教程东一榔头西一棒子&#xff0c;这个帖子讲临时挂载&#xff0c;那个帖子讲开机自动挂…

作者头像 李华
网站建设 2026/10/1 4:30:10

C++输入缓冲区的换行符陷阱:cin与getline混用排查指南

1. 输入缓冲区里的那个"吃不完"的换行符很多人学C学到输入输出的时候&#xff0c;都会遇到一个特别诡异的场景&#xff1a;明明代码写得没毛病&#xff0c;逻辑也对&#xff0c;可程序跑起来就是不按套路出牌&#xff0c;输入完一个数字以后&#xff0c;后面的字符串…

作者头像 李华
网站建设 2026/10/1 4:30:09

从命令行到脚本:掌握Shell编程的核心逻辑与避坑指南

先问个问题&#xff1a;你在Shell里敲过的最长一条命令是什么&#xff1f;是一长串管道&#xff0c;还是带了一堆awk的ps aux | grep xxx&#xff1f;如果你能熟练拆解这些命令&#xff0c;却始终写不出一份像样的Shell脚本&#xff0c;那大概率卡在同一个小坎上——你一直把Sh…

作者头像 李华
网站建设 2026/10/1 4:28:58

macOS下原生MAME模拟器完全配置指南:从编译到ROM管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 4:28:12

配电网N-1扩展规划建模:约束线性化与Matlab实现

干过配电网规划的朋友应该都有体会&#xff1a;光做"负荷预测网架校验"远远不够&#xff0c;真正让方案落地的挑战在于安全准则的约束&#xff0c;尤其是N-1校验。近两年各省配电网规划逐步从"缺什么补什么"转为"全要素扩展规划"&#xff0c;新建…

作者头像 李华