Libvirt 内部机制:管理接口、运行状态与并发协调
一、从控制一个进程到管理一组虚拟机
Libvirt 为虚拟机、网络和存储提供统一的管理接口。在 QEMU/KVM 环境中,它负责保存虚拟机定义、检查操作权限、准备宿主资源、启动 QEMU,并通过 QMP 管理运行中的实例。运行配置、异步事件和异常清理也由相应驱动协调。
直接使用 QEMU 命令行与 QMP 可以管理单个实例;管理多台虚拟机时,还需处理持久配置、资源分配以及并发操作。Libvirt 把这些工作组织成对象与操作接口:上层请求暂停或启动指定虚拟机,服务端查找实例、检查条件、执行动作并维护状态。本文以暂停与恢复为例,介绍接口分层、作业协调和事件处理。
| 组件 | 运行位置 | 保留的主要状态 |
|---|---|---|
| virsh 与 Libvirt 客户端库 | 发起命令的进程,可位于管理机或宿主机 | 连接、调用参数与虚拟机句柄 |
| virtqemud / libvirtd 的 QEMU 驱动 | 宿主机管理守护进程 | 虚拟机定义、资源分配、作业与管理状态 |
| QEMU 实例与 KVM | 宿主用户态进程与宿主内核 | 客户机 RAM、设备运行状态及 vCPU 执行状态 |
源码版本:Libvirt 12.0.0、QEMU 10.2.0。
二、API、连接、RPC 与驱动的分层
1. API 表达操作,连接确定管理范围
应用调用 virDomainSuspend() 表达暂停;virsh 是使用这些 API 的命令行客户端。这里的 domain 指受管理的虚拟机,客户端句柄保存名称、UUID 和所属连接等标识。它不包含完整的客户机内存,也不等同于 QEMU 的进程号。
连接确定操作目标与管理范围。qemu:///system 访问本机系统级 QEMU 管理服务,qemu:///session 访问当前用户的会话级服务;两者可见的实例和权限不同。远程连接还要选择传输方式并验证身份。请求指向同一个名称,并不代表在不同连接里操作的是同一台机器。连接 URI
2. RPC 跨越进程,驱动落实具体实现
管理应用与服务端通常不在同一进程。客户端把操作、对象标识和参数编码为消息,由服务端解码并分发,这就是 RPC。调用序号用来把响应交回原来的等待者;传输成功只说明消息到达,服务端仍需检查权限与对象状态。
服务端的 QEMU 驱动知道如何准备宿主资源、启动进程以及发送 QMP 命令。于是同一个公开 API 可以保持稳定,而底层处理随具体后端变化。一项 API 可能需要多条 QMP 命令,也可能只读取服务端已有记录。Libvirt 属于管理路径,客户机日常的读盘和发包不逐项经过这条 RPC 链。
Mermaid 源码
flowchart TB
accTitle: Libvirt 请求、状态与事件的分工
accDescr: 客户端 API 经 remote RPC 到达服务端 QEMU 驱动。驱动通过虚拟机对象与作业协调 QMP 命令,响应和异步事件沿不同路径反馈;客户机数据不经过 Libvirt RPC。
APP["应用 / virsh<br/>API 与 virDomain 句柄"] --> RPC["连接与 remote 驱动<br/>RPC 编码 · 请求序号"]
RPC --> DAEMON["libvirtd 或 virtqemud<br/>认证 · 分发 · 权限检查"]
DAEMON --> DRIVER["QEMU 驱动<br/>按 UUID 查找 virDomainObj"]
DRIVER --> JOB["对象状态与作业协调<br/>定义 · 运行状态 · 操作约束"]
JOB -->|"QMP 命令"| QEMU["QEMU 进程<br/>机器与设备的实际状态"]
QEMU -.->|"命令响应"| JOB
QEMU -.->|"异步事件"| EVENT["事件处理<br/>记录结果 · 唤醒等待者 · 清理"]
EVENT -.-> JOB
JOB -.->|"API 结果或事件通知"| APP
QEMU -->|"客户机 I/O 数据路径"| BACKEND["磁盘、网络或外部后端<br/>不经 Libvirt RPC"]
三、示例:暂停 demo,再从原处继续
假设 demo 已在系统级服务中运行,执行 virsh -c qemu:///system suspend demo。暂停保留 QEMU 进程和客户机 RAM,停止客户机 vCPU 执行;客户机电源管理休眠、保存内存到磁盘和关闭虚拟机则是另外的操作。
Mermaid 源码
sequenceDiagram
accTitle: Libvirt 暂停虚拟机的调用与事件反馈
accDescr: virsh 的暂停 API 经 RPC 进入 QEMU 驱动。驱动查找对象、取得作业后发送 stop,QEMU 暂停 vCPU。命令结果和 STOP 事件分别沿响应与状态处理路径反馈。
participant A as virsh / 管理应用
participant L as Libvirt 服务端<br/>QEMU 驱动
participant Q as QEMU 实例 demo
A->>L: RPC:暂停 demo 的对象标识
L->>L: 权限检查 · 取得暂停作业<br/>确认实例仍活跃
L->>Q: QMP stop
Q->>Q: 协调 vCPU 停止<br/>保留进程、内存与执行现场
par 命令响应路径
Q-->>L: QMP 命令结果
L-->>A: RPC 返回 API 结果
and 状态事件路径
Q-->>L: STOP 事件
L->>L: 记录 PAUSED 与原因<br/>保存运行状态
L-->>A: 生命周期通知<br/>仅发送给已订阅客户端
end
Note over A,Q: 应用不应假设事件回调与 API 返回的固定先后
- virsh 查找 demo,调用暂停 API。客户端按连接的实现编码 RPC,将虚拟机标识交给服务端。
- QEMU 驱动重新查找对象,检查权限,取得暂停作业,并确认实例仍然活跃。客户端之前查到的状态不能替代这次检查。
- 驱动进入 QEMU 监控接口,发送
stop。QEMU 协调 vCPU 停止,保留运行现场;QMP 响应返回命令结果。 - Libvirt 收到
STOP事件后记录暂停状态和原因,保存运行状态,并向已订阅的客户端发送生命周期通知。管理程序也可查询当前状态。
恢复时,virsh -c qemu:///system resume demo 经恢复 API 和 QEMU 驱动发送 cont,相关宿主资源也要恢复到允许运行的条件。QEMU 继续执行,Libvirt 的 RESUME 事件处理更新运行状态。事件与命令响应有各自处理路径,应用不应假设自己的事件回调一定早于或晚于 API 返回。暂停与恢复入口、状态事件处理
在宿主机另开终端,先订阅 virsh -c qemu:///system event --domain demo --event lifecycle --loop,再执行暂停与恢复,可以观察该连接收到的生命周期通知。暂停前后分别查询 virsh -c qemu:///system domstate demo --reason:成功暂停后应报告 paused,恢复后应报告 running。查询展示当前状态,事件展示订阅期间的变化;晚建立的订阅不会补齐此前所有历史事件。
四、持久定义、运行配置与运行状态
给 demo 保存“2 个 vCPU、4 GiB 内存和一块磁盘”的定义后,它可以尚未启动。启动时,Libvirt 根据定义与宿主能力建立本次运行配置;进程运行后,还要记录它处于运行、暂停或关机等状态。这三类信息分别描述下次启动配置、本次实例资源和当前执行状态。
| 信息 | 含义 | 例子 |
|---|---|---|
| 持久定义 | 下次创建机器使用的配置 | 关机后仍保留的 CPU、内存、设备配置 |
| 运行配置 | 本次实例实际使用的资源 | 已热插入、但未写入持久定义的网卡 |
| 运行状态 | 当前执行状态及变化原因 | 运行中;因用户请求暂停;因 I/O 错误暂停 |
部分修改接口允许分别指定 live 和 config。只改持久定义,当前实例可能继续保持原配置;只改运行配置,下次重新创建又可能消失。暂停操作主要改变执行状态,既不删除定义,也不重新创建机器。服务端用 virDomainObj 联系这些信息,实际 CPU 与设备状态仍由 QEMU/KVM 持有。
五、并发作业、异步事件与完成条件
1. 短时保护对象,长操作限制冲突
如果一个请求正删除网卡,另一个请求同时修改其后端,仅让各次字段读写互斥还不够:删除动作可能跨过多次命令和等待。Libvirt 用对象锁保护短时访问,用作业机制约束整项操作可与哪些工作并发。迁移这样的长作业也能允许查询或取消,而排斥不兼容的修改。
等待 QMP 响应时,驱动释放虚拟机对象锁,使事件处理能继续取得对象并更新状态;作业约束仍保留。如果一直拿锁等待,所等的事件可能恰好也需要这把锁。重新获得执行机会后还要复查实例,等待期间它可能已经退出。对象锁与作业规则
2. 以热拔网卡区分受理和完成
发送 device_del 后,QEMU 可以先返回成功,客户机驱动随后才停止使用设备;最终完成用 DEVICE_DELETED 表达。Libvirt 保留移除标记,等待事件或交给后续处理,再更新配置并清理资源。客户机拒绝、迟迟不响应和管理连接断开,都需要区别处理。
因此,“没有收到响应”与“没有执行”并不相同。超时后直接重复修改可能与仍在推进的操作冲突;应根据操作语义结合事件、状态查询与错误信息决定后续动作。命令响应回答这次调用怎样返回,事件反映运行过程中发生了什么,查询则帮助重新建立当前视图。移除等待与事件交接、QEMU 移除语义
六、从单体守护进程到按驱动拆分
传统 libvirtd 在同一个常驻进程中提供虚拟机、网络、存储等有状态驱动。入口集中,部署较直观;但不同驱动也共享进程生命周期和运行环境。模块化架构用 virtqemud、virtnetworkd、virtstoraged 等分别承载职责,以减少这类耦合。
拆分后,必要时由 virtproxyd 提供兼容或远程入口,上层继续使用原有 API。代价是服务、socket、配置和启动依赖更多,排查也需要识别请求实际经过哪些进程。单体与模块化布局要按部署确认,不能从 qemu:///system 一个 URI 推断全部进程结构。守护进程演进
七、沿请求、状态与资源检查管理过程
Libvirt 把用户操作落实为可检查的资源与状态变化:连接确定范围,API 表达意图,RPC 传递调用,驱动执行具体动作,作业和事件处理保证并发时仍能协调。它减少管理程序重复实现这些机制的负担,也引入需要观察的传输、排队和状态同步环节。
遇到操作慢或结果不确定,依次确认请求是否到达、是否在等待作业、QMP 是否返回、是否仍需客户机配合,再检查实际状态。这样能把管理链路的问题与客户机数据路径的性能问题分开,也能避免把某次成功响应当作所有后续动作的完成证明。
八、从管理接口深入到具体操作
- Libvirt RPC 编解码与分发:请求如何编码,响应怎样找到原调用。
- Libvirt 虚拟机启动:资源准备、QEMU 进程与运行状态的交接。
- PCIe 热插拔:客户机协作与移除完成事件。
- 预拷贝内存迁移:管理操作怎样联系实际状态传输。
- Libvirt 热迁移的阶段协调与失败恢复:五阶段交接、结果丢失及双端状态确认。