virt-report虚拟化社区动态

Libvirt 内部机制:管理接口、运行状态与并发协调

本文由 OpenAI Codex (GPT-6-Astra) 辅助编写。

一、从控制一个进程到管理一组虚拟机

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 链。

管理请求与状态反馈的组件路径
API 表达操作,RPC 负责跨进程交接,QEMU 驱动处理资源和状态,QMP 操作具体实例。命令响应和异步事件分别处理,数据请求走虚拟机自己的 I/O 路径。
API 表达操作,RPC 负责跨进程交接,QEMU 驱动处理资源和状态,QMP 操作具体实例。命令响应和异步事件分别处理,数据请求走虚拟机自己的 I/O 路径。 打开原图
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 执行;客户机电源管理休眠、保存内存到磁盘和关闭虚拟机则是另外的操作。

一次暂停请求中的命令与事件
示例跟踪普通运行状态下的暂停。QMP 响应交付命令结果,STOP 事件驱动状态记录;图中分开展示两条反馈,不能把事件订阅当成 API 调用返回的一部分。
示例跟踪普通运行状态下的暂停。QMP 响应交付命令结果,STOP 事件驱动状态记录;图中分开展示两条反馈,不能把事件订阅当成 API 调用返回的一部分。 打开原图
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 返回的固定先后
  1. virsh 查找 demo,调用暂停 API。客户端按连接的实现编码 RPC,将虚拟机标识交给服务端。
  2. QEMU 驱动重新查找对象,检查权限,取得暂停作业,并确认实例仍然活跃。客户端之前查到的状态不能替代这次检查。
  3. 驱动进入 QEMU 监控接口,发送 stop。QEMU 协调 vCPU 停止,保留运行现场;QMP 响应返回命令结果。
  4. 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 是否返回、是否仍需客户机配合,再检查实际状态。这样能把管理链路的问题与客户机数据路径的性能问题分开,也能避免把某次成功响应当作所有后续动作的完成证明。

八、从管理接口深入到具体操作

九、参考资料