Linux 基础体系 · 第 82/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux KVM 虚拟化:QEMU、vCPU、virtio、存储网络和隔离边界
KVM 虚拟化常被简化成一句话:“QEMU 提供设备,KVM 提供加速”。这句话方向正确,但不够解释实际系统中的关键问题:一个虚拟机进程到底如何运行,vCPU 如何与宿主机线程和物理 CPU 对应,virtio 为什么比设备全模拟更快,虚拟磁盘和网络包经过哪些层,虚拟机的隔离边界又在哪里。
本文以现代主流 Linux 发行版上的 x86-64 KVM 为主线,也会指出架构、发行版、权限和生产环境差异。
1. 先建立整体模型
KVM 是 Linux 内核中的虚拟化基础设施,QEMU 是用户空间的虚拟机监控器和设备模型。现代系统通常还会使用 libvirt、virt-manager 或云平台作为管理层,但它们不是虚拟化执行的核心。
可以先把一次虚拟机运行拆成四层:
flowchart TB
A[管理层<br/>libvirt / virt-manager / 云平台]
B[QEMU 用户空间进程<br/>设备模型、内存、I/O、管理协议]
C[KVM 内核模块<br/>KVM_RUN、虚拟 CPU、虚拟中断、二阶段地址转换]
D[Linux 调度器与设备驱动]
E[物理 CPU、内存、磁盘、网卡]
A --> B
B --> C
C --> D
D --> E
各层职责并不相同:
- QEMU 创建虚拟机地址空间,模拟或暴露虚拟设备,并负责大量用户空间 I/O。
- KVM 将一个虚拟 CPU 置于硬件虚拟化模式中运行,并处理虚拟机退出、虚拟中断和内存地址转换等内核侧工作。
- Linux 调度器 调度 QEMU 的 vCPU 线程、I/O 线程和其他宿主机进程。
- 物理设备驱动 负责真实磁盘、网卡和其他硬件。
- libvirt 通常生成 QEMU 命令行、配置 cgroup、设置网络和生命周期,但它本身不执行 guest 指令。
因此,“虚拟机”不是一个内核中的单一对象,也不是一个普通进程这么简单。通常它表现为:
- 一个 QEMU 进程;
- 一个或多个 vCPU 线程;
- 若干 QEMU I/O 线程;
- KVM 内核对象;
- 一组宿主机文件描述符、内存映射、网络接口和设备资源。
2. KVM 如何运行 guest 指令
2.1 硬件虚拟化的前提
x86 主机通常需要:
- Intel VT-x,或 AMD-V;
- 内核启用
kvm以及对应的kvm_intel或kvm_amd模块; - BIOS/UEFI 中没有关闭硬件虚拟化;
- 当前用户有权限访问
/dev/kvm。
检查方式:
$ grep -E 'vmx|svm' /proc/cpuinfo | head
看到 vmx 通常表示 Intel VT-x,看到 svm 通常表示 AMD-V。这个检查只能说明 CPU 暴露了相关能力,不保证 BIOS 已启用,也不保证当前环境允许嵌套虚拟化。
继续检查 KVM 设备:
$ ls -l /dev/kvm
crw-rw----+ 1 root kvm ... /dev/kvm
$ lsmod | grep -E '^kvm(_intel|_amd)?'
kvm_intel ...
kvm ...
不同发行版的权限组名称、udev 规则和安全策略可能不同。用户即使加入了 kvm 组,也可能还受到 SELinux、AppArmor、容器设备过滤或云平台策略限制。加入组后通常需要重新登录,且生产系统不应仅通过放宽设备权限解决启动问题。
2.2 KVM 的基本执行循环
QEMU 使用 KVM API 创建虚拟机和 vCPU。简化后的流程如下:
QEMU:
打开 /dev/kvm
KVM_CREATE_VM
创建 guest 内存映射
KVM_SET_USER_MEMORY_REGION
创建虚拟中断控制器和 vCPU
KVM_CREATE_VCPU
进入 vCPU 执行循环
vCPU 线程:
KVM_RUN
├─ guest 指令在物理 CPU 上执行
└─ 发生 VM exit,返回内核/用户空间
├─ I/O 端口访问
├─ MMIO 访问
├─ 外部中断或虚拟中断
├─ halt、关机、调试
└─ 未处理或异常状态
QEMU 根据退出原因模拟设备或更新状态
再次 KVM_RUN
这里的关键是 VM exit。guest 大部分普通计算指令不需要 QEMU 逐条解释,而是直接在 CPU 的非 root 虚拟化模式中执行。只有需要由虚拟机监控器处理的事件才退出到 KVM,再可能回到 QEMU。
这也是硬件辅助虚拟化与纯软件解释器的根本差别:
- 解释器逐条读取、解码和模拟 guest 指令;
- KVM 让硬件直接执行绝大多数 guest 指令;
- 设备访问、特权行为、地址转换异常等事件仍需要虚拟化层介入。
“直接执行”不等于“guest 获得了真实 CPU 的全部权限”。硬件通过 VMCS/VMCB 等虚拟化控制结构限制 guest 的执行模式,KVM 负责设置这些控制条件。
2.3 vCPU 不是 CPU,也不一定绑定一个物理核
vCPU 是虚拟机看到的逻辑处理器。对 Linux 宿主机而言,vCPU 通常对应 QEMU 中的一个线程;对 guest 而言,它表现为一个可被 guest 内核调度的 CPU。
因此至少存在两层调度:
guest 进程
↓ guest scheduler
guest vCPU
↓ 宿主机 Linux scheduler
QEMU vCPU 线程
↓
物理 CPU 逻辑处理器
假设虚拟机配置了 4 个 vCPU,但宿主机只给它 2 个可运行的物理 CPU:
- guest 仍然认为有 4 个 CPU;
- 4 个 vCPU 线程竞争 2 个宿主机 CPU;
- guest 内部可能同时运行 4 个任务;
- 宿主机调度器再决定哪些 vCPU 线程获得时间片。
这不会自动违反正确性,但会增加调度开销和延迟。若 guest 中有大量并行任务,虚拟 CPU 数量远大于可用物理 CPU 可能导致:
- guest 调度器认为存在更多并行度;
- vCPU 线程频繁被宿主机抢占;
- guest 内出现
%steal时间; - 锁竞争和尾延迟上升。
可以用 top、mpstat 或 guest 内的 vmstat 观察,但这些指标只反映某一层的现象。宿主机还应观察 QEMU 线程调度延迟、CPU cgroup 限制和 NUMA 位置。
2.4 vCPU 的常见状态
一个 vCPU 线程不总是在执行 guest 指令。常见状态包括:
- 运行 guest:线程进入
KVM_RUN,硬件执行 guest。 - VM exit:发生 I/O、异常、中断或其他退出事件。
- 等待中断:guest 执行
HLT或等待事件,vCPU 线程可能睡眠。 - 被宿主机调度出去:宿主机其他任务运行,或 vCPU 被 cgroup 限制。
- 等待 I/O:设备模拟、磁盘、网络或用户空间后端未完成。
- 暂停:迁移、快照、管理操作或错误处理导致虚拟机暂停。
所以“CPU 使用率低”不能直接推出“虚拟机很空闲”。如果 vCPU 大量时间在 I/O 等待、宿主机被限流或 guest 在等待锁,CPU 利用率都可能不高。
3. 内存虚拟化:从 guest 地址到主机内存
3.1 三种地址不要混淆
虚拟机中常见三类地址:
- GVA(Guest Virtual Address):guest 进程使用的虚拟地址。
- GPA(Guest Physical Address):guest 内核认为的物理地址。
- HPA(Host Physical Address):宿主机真实物理内存地址。
典型转换链:
guest 进程的 GVA
↓ guest 页表
GPA:guest 物理地址
↓ EPT(Intel)或 NPT/RVI(AMD)
HPA:宿主机物理地址
guest 内核只管理自己的 GVA 到 GPA 映射。宿主机 KVM 负责 GPA 到 HPA 的二阶段地址转换。
假设 guest 进程访问:
GVA 0x7f00_1234_5000
guest 页表将它映射到:
GPA 0x0000_4000_5000
QEMU 为虚拟机分配的内存可能由宿主机虚拟地址:
HVA 0x7f80_0000_0000
映射到宿主机物理页:
HPA 0x0000_9000_5000
KVM 通过 EPT/NPT 等硬件机制完成 GPA 到 HPA 的转换。实际地址和页表层级会因架构、页大小、内存气球、内存热插拔和内核实现而变化,但逻辑关系保持不变。
3.2 为什么内存访问仍可能退出
普通 guest 内存访问通常由二阶段地址转换处理,不需要每次访问都退出到 QEMU。但以下情况可能导致异常或退出:
- guest 页表本身未建立映射;
- GPA 没有有效的 KVM 内存槽;
- 访问被配置为 MMIO;
- 内存页尚未分配或需要用户空间处理;
- 设备内存、共享内存或特殊内存区域被访问;
- 调试、脏页跟踪或迁移机制改变了权限。
虚拟机的内存不是“自动从宿主机拿一块不可见的 RAM”。QEMU 通常先建立一段用户空间内存映射,再通过 KVM 注册为 guest 可见的内存区域。宿主机还要承担实际页分配、回收、换页和 cgroup 记账。
生产环境通常应避免让虚拟机内存被宿主机 swap。宿主机发生 swap 时,guest 可能已经在自己的内核中管理内存,双重分页会产生不可预测的延迟。使用 libvirt 时应结合内存 cgroup、锁定内存、NUMA 策略和宿主机 swap 策略进行验证,而不是简单地对所有 QEMU 进程设置无限制的 mlockall。
4. QEMU:设备模型和用户空间后端
QEMU 不是只负责“启动 CPU”。它通常负责:
- 虚拟 PCI 总线;
- 虚拟磁盘控制器;
- 虚拟网卡;
- 虚拟串口和控制台;
- 虚拟 RTC、定时器和中断控制器;
- BIOS/UEFI 固件加载;
- 磁盘镜像、快照、迁移和设备后端;
- QMP 管理接口;
- 用户空间网络和存储数据路径。
4.1 全模拟设备和半虚拟化设备
全模拟设备让 guest 看到一个真实硬件的行为模型,例如传统 IDE、某些 e1000 网卡或 LSI SCSI 控制器。guest 使用已有的硬件驱动,兼容性较好,但每次设备访问可能涉及更多寄存器模拟和 VM exit。
virtio 是半虚拟化设备。guest 不再把设备当成某个真实厂商硬件,而是使用 virtio 驱动,通过共享内存队列与宿主机后端交换请求。
两者不是简单的“旧”和“新”:
- 全模拟设备适合兼容性、救援和某些安装场景;
- virtio 通常适合性能和现代 guest;
- virtio 仍然需要 guest 驱动、正确的机器类型和后端配置;
- 设备性能受后端、存储协议、线程调度和队列配置共同影响。
4.2 QEMU 线程模型
一个 QEMU 进程可能包含:
- 每个 vCPU 一个线程;
- 主循环线程;
- 磁盘 I/O 线程;
- 网络后端线程;
- vhost 相关线程;
- migration、block job、监控和定时器线程;
- 用户配置的 iothread。
因此,给 QEMU 进程绑定 CPU 并不等于精确绑定了所有工作。若需要调优,应区分:
- vCPU 线程运行在哪些 CPU;
- I/O 线程运行在哪些 CPU;
- vhost 内核线程运行在哪些 CPU;
- 宿主机中断和软中断运行在哪些 CPU。
taskset 可以临时观察或设置进程/线程亲和性:
$ ps -L -p "$QEMU_PID" -o pid,tid,psr,comm
psr 显示线程最近运行的逻辑 CPU,但它不是永久亲和性证明。查看实际掩码:
$ taskset -pc "$QEMU_PID"
pid 1234's current affinity list: 0-7
生产环境更适合通过 libvirt XML、systemd cgroup 或专门的 CPU/NUMA 策略配置,而不是手工对运行中的线程执行一次性命令。
5. virtio 的工作机制
5.1 virtio 的基本数据结构
virtio 设备和 guest 驱动之间通常通过 virtqueue 通信。一个 virtqueue 可抽象为三部分:
- descriptor table:描述数据缓冲区的地址、长度和读写方向;
- available ring:guest 提交了哪些 descriptor;
- used ring:设备处理完哪些 descriptor。
以 virtio-blk 写请求为例:
guest 文件系统
↓
guest 块层
↓
virtio-blk 驱动
↓ 填写 descriptor
available ring
↓
QEMU 或 vhost 后端
↓
宿主机文件 / 块设备
↓
used ring
↓
virtio-blk 驱动完成请求
请求通常包含:
- 请求头:操作类型、扇区号等;
- 数据缓冲区:写请求中的待写数据,或读请求中的目标缓冲区;
- 状态字节:后端完成后写入成功或错误状态。
virtio 驱动和后端通过共享内存访问这些结构,避免每个数据字节都通过模拟硬件寄存器传递。提交和完成通常还需要通知机制,现代 virtio 会使用 event suppression、批处理和多队列减少中断与 VM exit。
5.2 virtio-blk、virtio-scsi 和 virtio-net
virtio-blk 是直接的虚拟块设备,结构简单、路径短,适合常见虚拟磁盘。
virtio-scsi 将虚拟磁盘挂在 virtio 提供的 SCSI 控制器上,适合需要多个磁盘、SCSI 命令或更复杂热插拔模型的场景。它并不因为名字中有 SCSI 就天然更快,实际性能取决于 guest 驱动、QEMU 后端、队列和存储介质。
virtio-net 提供虚拟网卡。guest 发送数据包时,virtio-net 驱动把数据放入发送队列,宿主机后端取出后写入 TAP、vhost 或其他网络后端。
5.3 vhost:把数据路径部分移入内核
传统 virtio-net 数据路径可能是:
guest virtio-net
→ KVM/QEMU
→ TAP
→ Linux bridge
→ 物理网卡
启用 vhost-net 后,数据平面的一部分由宿主机内核线程处理:
guest virtio-net
→ vhost-net 内核后端
→ TAP/bridge
→ 物理网卡
这减少了 QEMU 用户空间参与数据包搬运的开销,但不等于所有网络处理都绕过 QEMU,也不等于一定获得更低延迟。控制面、设备配置和某些异常路径仍由 QEMU 管理;vhost 线程还会消耗宿主机 CPU,并受到调度和中断亲和性影响。
检查 QEMU 是否使用 vhost,不能只看一项指标。可以结合 QEMU 命令行、线程列表、/dev/vhost-net 和实际网络统计判断。libvirt 的具体 XML 和默认值会随版本变化,应以当前生成的 QEMU 命令行与日志为准。
6. 虚拟存储:从 guest 写入到宿主机持久化
6.1 虚拟磁盘的层次
一个 guest 写请求可能经过以下层次:
guest 文件
↓
guest 文件系统
↓
guest 块层
↓
virtio-blk / virtio-scsi
↓
QEMU block layer
↓
qcow2 或 raw
↓
宿主机文件系统 / LVM / SAN / NVMe
↓
物理介质和控制器缓存
每一层都可能缓存、重排、合并或延迟请求。因此 guest 看到的“写成功”必须结合缓存模式和持久化保证理解。
6.2 raw 与 qcow2
raw 是连续或逻辑上直接表示块内容的格式,结构简单、额外元数据少,通常适合性能敏感或由 LVM、Ceph、NVMe 等后端直接提供的虚拟磁盘。
qcow2 是 QEMU 的写时复制镜像格式,支持:
- 稀疏分配;
- 快照;
- backing file;
- 压缩等功能;
- 镜像元数据和引用计数。
但 qcow2 会增加元数据访问和写放大路径。快照链过长、宿主机文件系统空间不足、backing file 被误删或镜像被多个 QEMU 进程并发写入,都可能造成严重问题。
查看镜像信息:
$ qemu-img info disk.qcow2
可能看到:
file format: qcow2
virtual size: 40 GiB (42949672960 bytes)
disk size: 6.2 GiB
cluster_size: 65536
这里:
virtual size是 guest 看到的容量;disk size是当前宿主机实际占用的大致空间;- 两者不同是稀疏分配的结果。
不要使用文本工具直接修改 qcow2。检查镜像可以使用:
$ qemu-img check --force disk.qcow2
--force 对某些检查场景有风险,尤其是镜像仍被虚拟机使用时。任何修复、转换或压缩操作前都应关闭 guest 或建立一致性快照,并确认没有其他进程持有写访问。
6.3 缓存、flush 和“写成功”
存储路径中的“完成”至少有三种含义:
- 数据已复制到某个内存缓冲区;
- 数据已被宿主机文件系统接受;
- 数据已到达具备持久性保证的设备介质。
guest 文件系统会通过 flush、FUA 或相关协议请求持久化顺序。QEMU 的 cache、AIO、直写模式和后端设备能力会影响这些请求如何落地。宿主机磁盘控制器或 SSD 如果错误报告 flush 完成,虚拟化层也无法凭空修复硬件谎报。
因此,数据库虚拟机的可靠性不能仅由“使用 virtio”推出,必须验证:
- guest 是否看到了正确的 discard、flush 和写屏障能力;
- QEMU 后端是否将请求正确传递;
- 宿主机文件系统和存储阵列是否提供持久化语义;
- 断电测试或设备文档是否支持目标保证。
6.4 一个可运行的最小启动示例
以下示例用于理解路径,不是完整生产配置。需要已安装 QEMU、KVM 和一个可启动的 guest 镜像:
qemu-system-x86_64 \
-enable-kvm \
-machine type=q35 \
-cpu host \
-m 4096 \
-smp 2 \
-drive file=disk.qcow2,if=virtio,format=qcow2 \
-nic user,model=virtio \
-nographic
各选项的因果关系:
-enable-kvm:请求使用/dev/kvm,否则可能退回纯软件 TCG,性能和能力不同;-machine type=q35:选择一种虚拟机器类型,设备拓扑由它决定;-cpu host:尽量向 guest 暴露宿主机 CPU 特征,但会降低跨主机迁移兼容性;-m 4096:给 guest 配置 4096 MiB 内存;-smp 2:创建两个 vCPU;if=virtio:使用 virtio 磁盘接口;format=qcow2:明确镜像格式,避免格式探测或格式误判;-nic user:使用 QEMU 用户网络,适合快速测试;-nographic:将控制台接到当前终端,前提是 guest 配置了串口控制台。
如果 guest 没有串口输出,-nographic 可能看起来像“启动卡住”,实际只是输出设备不匹配。测试结束可以在 guest 内关机,或从另一个管理终端通过 QMP/监控接口执行有序关机。直接杀掉 QEMU 可能导致 guest 文件系统恢复、数据库回滚或镜像损坏。
-nic user 默认通常提供出站网络和用户态 NAT,但不等同于生产网络。入站连接、DHCP、端口转发、性能和安全策略都与 bridge、macvtap、OVS 或云网络不同。
7. 虚拟网络:TAP、bridge、NAT 和数据流
7.1 TAP 是什么
TAP 是宿主机上的二层虚拟网络设备。QEMU 可以打开 TAP 文件描述符,把它当作以太网帧的读写端:
guest virtio-net
↕
QEMU / vhost
↕
tap0
↕
Linux bridge br0
↕
物理网卡 eth0
↕
外部交换网络
guest 发出的以太网帧进入 virtio-net 队列,宿主机后端从队列取出并写入 TAP;Linux 网络栈或 bridge 再决定转发到哪里。反方向则相反。
bridge 工作在二层,通常把多个端口放入同一个广播域。典型端口包括:
- 物理网卡;
- 一个或多个 TAP;
- VLAN 子接口;
- 其他虚拟网络设备。
配置 bridge 时若把宿主机 IP 错放在物理从接口而不是 bridge 上,可能导致宿主机网络中断。生产变更应使用 out-of-band 控制台或具备自动回滚的网络管理工具。
7.2 三种常见网络模式
QEMU user-mode NAT
优点是启动简单,不要求 root 创建 bridge 或 TAP。缺点是:
- 入站连接需要端口转发;
- 性能和协议能力有限;
- guest 不会直接成为宿主机物理网络中的独立二层节点;
- 某些复杂协议和广播发现不适用。
适合实验和安装环境,不应仅因“能联网”就认为满足生产需求。
Linux bridge
guest 可以通过 TAP 接入宿主机 bridge,并像独立主机一样使用上游 DHCP、静态地址或 VLAN。它的行为容易理解,但需要正确处理:
- 物理网卡与 bridge 的关系;
- VLAN 过滤;
- 防火墙;
- 生成树和链路状态;
- 网络管理服务的持久化配置。
macvtap、Open vSwitch 和云网络
macvtap 可以减少某些 bridge 配置,但宿主机与 guest 的直接通信行为、混杂模式和上游交换机能力需要单独验证。Open vSwitch 适合更复杂的 VLAN、隧道、策略和虚拟交换需求。云平台还可能使用 veth、软件交换机、eBPF、SR-IOV 或硬件卸载,数据路径不能套用单机 bridge 的假设。
8. 一个可观察的 virtio 网络数据路径
可以用 guest 和宿主机两侧的计数器交叉验证:
guest 内:
ip -s link show
ethtool -i eth0
ethtool -S eth0
宿主机:
ip -s link show tap0
bridge link
bridge fdb show
这些命令的含义不同:
- guest 网卡计数器说明 virtio-net 驱动收发了多少包;
- TAP 计数器说明包是否到达宿主机虚拟端口;
- bridge FDB 说明二层转发学习到了哪些 MAC;
ethtool -S的统计项由驱动决定,不能假设所有发行版都提供同名字段。
例如,guest 发送计数增加而 TAP 接收计数不增加,问题可能位于 virtio 后端、TAP 连接或 QEMU 配置;TAP 计数增加但 bridge 不转发,则应检查 bridge 端口状态、FDB、VLAN 和防火墙。计数器只能定位层次,不能单独证明包已经到达物理网络。
9. 中断、队列和并发
virtio 的性能不仅由“是否使用 virtio”决定,还取决于队列数量、vCPU、I/O 线程、中断和后端并发。
9.1 多队列的逻辑
单队列设备可以抽象为:
所有 guest 请求 → 一个 virtqueue → 一个主要处理路径
多队列设备则是:
guest CPU 0 → queue 0
guest CPU 1 → queue 1
guest CPU 2 → queue 2
...
这样可以减少多个 vCPU 争用同一队列锁,并让处理线程分布到多个 CPU。但队列数过多也会增加:
- 中断数量;
- 内存占用;
- 后端线程或上下文切换;
- NUMA 跨节点访问;
- 调试复杂度。
多队列不是越大越好。应根据 guest 并发度、宿主机 CPU、网卡队列、存储后端和实际负载测试。
9.2 vCPU、I/O 线程和物理 CPU的绑定
假设宿主机有两个 NUMA 节点:
node 0: CPU 0-15,内存 64 GiB
node 1: CPU 16-31,内存 64 GiB
一个 8 vCPU guest 若其 vCPU 运行在 node 0,而 QEMU I/O 线程、网卡中断和 backing file 的内存主要位于 node 1,数据可能经历跨节点访问。跨 NUMA 节点访问不一定错误,但延迟和带宽会不同。
NUMA 关联应同时考虑:
- vCPU 亲和性;
- guest 内存绑定;
- QEMU I/O 线程;
- vhost 线程;
- 物理网卡中断;
- 存储控制器和设备所在节点。
可以用以下命令观察拓扑:
numactl --hardware
lscpu -e=CPU,NODE,SOCKET,CORE
cat /sys/devices/system/node/node0/cpulist
numactl --hardware 显示的是宿主机拓扑,不是 guest 内部拓扑。guest 看到的 vNUMA 拓扑可能由虚拟机配置显式暴露,也可能根本没有暴露真实节点结构。错误的 vNUMA 配置会让 guest 调度器做出不合理的内存放置决策。
10. 虚拟机隔离边界
10.1 VM 与容器的边界不同
容器通常共享宿主机内核,通过 namespaces 隔离视图,通过 cgroups 控制资源。容器中的进程仍执行宿主机内核代码。
KVM 虚拟机通常运行自己的 guest 内核:
虚拟机:
guest 应用
↓
guest 内核
↓
虚拟设备驱动
↓
KVM/QEMU
↓
宿主机内核
这增加了一层内核边界。guest 普通进程不能直接调用宿主机内核系统调用,也不能直接读取宿主机进程地址空间。
但“有虚拟机就绝对安全”是错误的。隔离边界至少包括以下部分:
- guest 内核到虚拟 CPU 的边界;
- guest 到虚拟设备模型的边界;
- QEMU 到宿主机内核的边界;
- QEMU 进程权限和文件系统访问边界;
- 虚拟网络和宿主机网络策略边界;
- 直通设备带来的 IOMMU/DMA 边界。
10.2 QEMU 进程权限很重要
QEMU 若以 root 身份运行,QEMU 漏洞或配置错误的潜在影响远大于受限用户运行。实际部署通常通过 libvirt 为每个虚拟机使用受限用户、组、SELinux/AppArmor 标签和专用目录。
必须限制的对象包括:
- 虚拟磁盘及其 backing file;
- UEFI 变量文件;
- QMP socket;
- ISO、日志和临时文件;
/dev/kvm、/dev/vhost-net等设备;- 共享目录和宿主机网络控制接口。
QEMU 的管理接口也属于高权限接口。将 QMP socket 暴露给不可信用户,可能允许关机、挂载介质、改变设备状态甚至读取 guest 内存状态。Unix socket 的文件权限、目录权限和管理代理边界必须明确配置。
10.3 设备直通会改变边界
VFIO 设备直通允许 guest 直接驱动某个 PCI 设备,常用于高性能网卡、GPU 或存储控制器。典型路径变为:
guest 驱动
↓
VFIO / IOMMU
↓
物理 PCI 设备
这减少了 QEMU 模拟路径,但风险和约束增加:
- 设备必须处于可隔离的 IOMMU group;
- 设备 DMA 必须由 IOMMU 限制到 guest 分配的地址范围;
- 设备可能无法同时供宿主机使用;
- 迁移、快照和热插拔能力受设备限制;
- 设备固件和 guest 驱动错误可能影响稳定性;
- SR-IOV VF、PF 和同组设备的隔离质量取决于硬件设计。
不能把 VFIO 视为“只改变性能,不改变安全模型”。它恰恰改变了设备访问和 DMA 的信任边界。
10.4 共享机制会削弱隔离
以下功能需要特别审查:
- virtiofs、9p 等宿主机目录共享;
- 共享剪贴板;
- guest agent;
- 主机与 guest 之间的端口转发;
- QEMU monitor/QMP;
- 设备直通;
- 共享内存和 vhost-user;
- 将宿主机文件作为可写虚拟磁盘使用。
例如,virtiofs 不是“虚拟机读取一个普通磁盘”,而是通过宿主机文件系统服务向 guest 暴露目录。guest 中的用户可能借此读取或修改宿主机上被共享的内容。共享目录的最小权限必须小于宿主机根目录,不能依赖 guest 内部的文件权限替代宿主机权限控制。
11. cgroups、namespaces 与 KVM 的关系
11.1 cgroups 控制 QEMU 资源
QEMU 是宿主机进程,因此可以被 cgroups 管理。cgroups v2 常见控制器包括:
cpu:CPU 权重、带宽和周期限制;cpuset:允许使用的 CPU 和 NUMA 节点;memory:内存上限、回收和压力;io:块设备 I/O 权重和带宽;pids:进程数量限制。
需要区分“guest 内的资源视图”和“宿主机对 QEMU 的限制”:
guest 看到的 4 vCPU、8 GiB RAM
≠
宿主机保证给 QEMU 的 4 个物理 CPU、8 GiB 未竞争内存
例如,guest 配置 8 GiB 内存,但 QEMU 所在 memory cgroup 的 memory.max 只有 6 GiB,QEMU 可能被宿主机 OOM 终止,guest 看到的结果可能是突然断电或虚拟机消失,而不是一个优雅的内存错误。
使用 systemd 管理的 QEMU 可以检查其 cgroup:
systemctl status libvirtd
systemctl status virtqemud
systemd-cgls
不同发行版可能使用 libvirtd、拆分后的 virtqemud 或其他服务名称。不要根据某个发行版的服务名编写跨发行版自动化脚本。
11.2 namespaces 主要隔离宿主机视图
namespaces 可以隔离:
- PID 视图;
- mount 视图;
- network 设备和地址;
- IPC;
- UTS 主机名;
- user ID 映射;
- cgroup 视图。
但 KVM 虚拟机不需要依赖 namespaces 才能获得 guest 内核隔离。libvirt 可能使用 namespaces 或其他沙箱手段限制 QEMU,但这属于宿主机侧加固,不等同于 guest 自身的虚拟化边界。
一个常见误解是:“把 QEMU 放进容器,就自动拥有双重隔离。”实际上,容器必须获得 /dev/kvm、网络设备、镜像文件和可能的 VFIO 设备访问;这些权限若配置过宽,容器边界可能被削弱。还要确认容器运行时的 seccomp、设备白名单、user namespace 和 cgroup 设置是否允许 QEMU 正常运行。
12. 启动失败与故障路径
12.1 /dev/kvm 不可用
表现可能是:
Could not access KVM kernel module: Permission denied
failed to initialize KVM: Permission denied
排查顺序:
test -e /dev/kvm
id
ls -l /dev/kvm
dmesg | grep -i kvm
可能原因包括:
- 模块未加载;
- BIOS/UEFI 关闭虚拟化;
- 用户无权访问设备;
- 当前运行在不支持嵌套虚拟化的云主机中;
- 容器没有透传
/dev/kvm; - SELinux/AppArmor 或设备策略拒绝访问。
不要只看到 QEMU 能启动就认为使用了 KVM。应检查 QEMU 日志或启动参数,确认没有意外进入 TCG 软件模拟。
12.2 guest 没有网络
按数据路径逐层检查:
guest 网卡是否存在
→ virtio 驱动是否加载
→ guest 地址、路由和 DNS
→ TAP 是否存在
→ bridge 端口是否 UP
→ VLAN/防火墙是否放行
→ 物理链路和上游交换机
guest 内:
ip link
ip addr
ip route
ping -c 3 网关地址
宿主机:
ip link
bridge link
ip -s link show tap0
如果 guest 连网卡都没有,问题还未到 bridge 层;如果 guest 能访问网关但不能访问外部地址,可能是 NAT、路由或 DNS 问题;如果只有某些 VLAN 不通,应检查 bridge VLAN 过滤和物理交换机配置。
12.3 虚拟磁盘启动失败
常见表现:
No bootable device;- guest 能启动但找不到根盘;
- 磁盘容量显示异常;
- 启动后文件系统只读;
- I/O error、timeout 或 guest 卡顿。
排查:
qemu-img info disk.qcow2
file disk.qcow2
ls -lh disk.qcow2
还应检查:
- 镜像格式是否与 QEMU 参数一致;
- guest 是否含 virtio-blk/virtio-scsi 驱动;
- 固件是 BIOS 还是 UEFI;
- UEFI 变量文件是否匹配;
- backing file 是否存在;
- 镜像是否仍被其他实例写入;
- 宿主机文件系统和底层块设备是否报错。
不要在虚拟机运行期间对镜像执行 qemu-img 的修改性操作,也不要通过复制一个正在写入的 qcow2 文件来宣称获得了一致性备份。需要一致性备份时,应使用 guest 文件系统冻结、应用层备份、QEMU block job 或存储系统一致性快照,并验证恢复。
13. 性能诊断:不要只看 guest CPU 使用率
虚拟机性能问题至少可能来自五个位置:
- guest 应用和 guest 内核;
- vCPU 调度;
- QEMU 设备模拟或 I/O 线程;
- 宿主机 cgroup、NUMA 和内存压力;
- 真实存储或网络设备。
可用工具及其边界:
# 宿主机整体 CPU、I/O、内存
vmstat 1
iostat -xz 1
mpstat -P ALL 1
# QEMU 线程和 CPU
top -H -p "$QEMU_PID"
ps -L -p "$QEMU_PID" -o tid,psr,stat,pcpu,comm
# NUMA
numastat -p "$QEMU_PID"
numactl --hardware
# 内存压力
cat /proc/pressure/memory
cat /proc/pressure/io
判断示例:
- guest
%steal较高,同时宿主机 CPU 饱和:vCPU 在等待宿主机调度; - guest I/O wait 高,宿主机
iostat显示底层设备延迟高:瓶颈可能在真实存储; - guest I/O wait 高,但底层设备空闲、QEMU I/O 线程受限:问题可能在 cgroup、队列或后端线程;
- guest 网络丢包,宿主机软中断集中在少数 CPU:可能需要检查 RSS、RPS、网卡队列和中断亲和性;
- QEMU CPU 高但 guest 负载不高:可能是设备模拟、频繁 VM exit、忙轮询或异常 I/O。
这些只是推断入口,不是单指标结论。调整 vCPU、队列、CPU 绑定和缓存模式前,应先建立基线并记录 guest、QEMU、宿主机和设备四层指标。
14. 迁移、快照和时间语义
虚拟机迁移要求目标主机能够复现源主机提供的 CPU 和设备模型。-cpu host 通常能提供本机更完整的 CPU 特性,但会降低迁移兼容性;生产迁移通常使用经过验证的 CPU model,而不是无条件暴露全部 host 特性。
内存迁移的核心过程可简化为:
1. 复制 guest 内存
2. guest 继续运行并产生脏页
3. 重复复制新脏页
4. 脏页产生速度低于复制速度时短暂停机
5. 复制 vCPU 和设备状态
6. 目标端恢复运行
如果 guest 持续修改内存的速度大于网络复制速度,脏页永远追不上,迁移会变慢、暂停时间增加,甚至无法完成。存储若未共享,还必须迁移磁盘状态或使用外部共享存储。
快照也有不同含义:
- 磁盘快照;
- 内存快照;
- 崩溃一致性快照;
- guest 文件系统一致性快照;
- 应用一致性备份。
保存 qcow2 外壳文件不代表保存了 guest 的全部运行状态,更不代表数据库处于一致状态。恢复测试比“快照命令成功”更能证明备份有效。
虚拟机时间还涉及:
- guest 虚拟时钟;
- KVM paravirtualized clock;
- 宿主机时间;
- vCPU 暂停与恢复;
- 迁移和快照;
- guest NTP/chrony。
时间漂移可能导致证书、分布式锁、日志排序和数据库事务出现问题。guest 应使用合适的时钟源,并由 guest 自身时间同步机制校正;不能把宿主机当前时间等同于所有 guest 的有效时间语义。
15. 常见误解与真实边界
误解一:vCPU 数量越多越快
vCPU 增加只有在 guest 工作负载能够并行、宿主机有足够 CPU、锁竞争可接受且调度延迟不恶化时才有帮助。过量 vCPU 会增加调度和中断开销,甚至降低单线程任务的稳定延迟。
误解二:virtio 一定比模拟设备快
virtio 减少了设备模拟开销,但最终性能仍受后端影响。一个位于拥塞 NFS 上的 virtio 磁盘,不会因为接口名称是 virtio 就拥有本地 NVMe 的延迟;一个被 CPU cgroup 严格限流的 virtio-net 后端,也不能保证高吞吐。
误解三:raw 一定安全,qcow2 一定慢
raw 的格式结构简单,但若位于宿主机文件系统上,仍会经过文件系统缓存和底层存储层。qcow2 提供快照和稀疏分配,但性能取决于镜像链、cluster size、后端和负载。应测量实际工作负载,而不是只按格式名称判断。
误解四:虚拟机与宿主机完全隔离
QEMU 漏洞、KVM 漏洞、设备模拟错误、共享目录、管理 socket、直通设备和侧信道都会影响边界。虚拟机提高了隔离强度,但不能替代补丁、最小权限、网络策略和备份恢复。
误解五:QEMU 是一个普通单线程进程
QEMU 可能包含多个 vCPU、I/O、vhost 和管理线程。只观察主线程可能漏掉真正的 CPU 或延迟瓶颈。
误解六:关闭虚拟机就是结束 QEMU 进程
有序关机让 guest 文件系统和应用完成 flush。强制终止 QEMU 类似突然断电,可能触发文件系统恢复、丢失缓存数据、破坏镜像一致性或使分布式系统误判节点状态。
16. 生产配置的判断顺序
针对一个具体虚拟机,配置和诊断应按依赖关系进行,而不是先盲目调参:
- 确认硬件和 KVM 可用性:CPU 虚拟化、
/dev/kvm、内核模块和权限。 - 确认机器类型与固件:BIOS/UEFI、PCI 拓扑、guest 驱动可用性。
- 确认 vCPU 和内存模型:数量、CPU model、NUMA、内存上限和 cgroup。
- 确认设备模型:virtio 还是全模拟,队列和中断是否匹配负载。
- 确认存储语义:镜像格式、backing file、flush、discard、备份和恢复。
- 确认网络路径:user NAT、bridge、VLAN、OVS、TAP、vhost 和防火墙。
- 确认权限边界:QEMU 用户、SELinux/AppArmor、QMP、共享目录和设备访问。
- 确认故障恢复:guest 有序关机、宿主机重启、存储故障、迁移失败和备份恢复。
- 最后才做性能调优:CPU 亲和性、NUMA、队列、I/O 线程和后端参数。
这个顺序体现了虚拟化系统的因果关系:如果 /dev/kvm 不可用,调 vCPU 亲和性没有意义;如果 guest 没有 virtio 驱动,调 virtqueue 数量也不会产生预期效果;如果存储本身延迟很高,改变 QEMU 线程数通常只是把等待位置移动。
KVM 虚拟化的核心不是“启动一个带 -enable-kvm 参数的 QEMU”,而是理解一条完整路径:guest 指令由 vCPU 在线程和物理 CPU 之间被调度,guest 地址经过二阶段转换落到宿主机内存,virtio 通过共享队列降低设备虚拟化成本,存储和网络请求再经过 QEMU、vhost、TAP、bridge、文件系统和真实设备,最终受到 cgroups、namespaces、IOMMU、权限模型和内核漏洞边界的共同约束。只有把这些层次分别验证,性能、可靠性和隔离结论才有可证明的基础。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux logrotate 深入:轮转、压缩、信号、并发和磁盘保护
- 下一篇:Linux cloud-init:实例初始化、Metadata、用户数据、幂等和安全
- 延伸:Linux cgroups 与 namespaces:资源控制、隔离和容器底层机制
- 延伸:Linux NUMA 与硬件拓扑:节点、内存亲和、跨节点访问和诊断
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论