QEMU Machine Protocol(QMP)协议规范实战指南:从 JSON 消息格式到源码级实现解析
2026/9/23 1:15:07 网站建设 项目流程

QEMU Machine Protocol(QMP)协议规范实战指南:从 JSON 消息格式到源码级实现解析

【免费下载链接】qemuOfficial QEMU mirror. Please see https://www.qemu.org/contribute/ for how to submit changes to QEMU. Pull Requests are disabled. Please only use release tarballs from the QEMU website.项目地址: https://gitcode.com/gh_mirrors/qe/qemu

QMP(QEMU Machine Protocol)是 QEMU 提供的基于 JSON 的机器级控制协议,供上层应用(如 libvirt、OpenStack 等管理工具)以结构化方式驱动 QEMU 进程,同时也被 QEMU Guest Agent(QGA)用于宿主机与客户机操作系统之间的交互。本文将基于仓库中的官方规范文档 docs/interop/qmp-spec.rst,结合 QEMU 源码(monitor 层、qapi 层)与命令定义(qapi/*.json),系统讲解 QMP 的协议格式、能力协商、命令执行、响应结构、异步事件、OOB 带外执行、解析器复位、QGA 同步、兼容性约束与下游扩展规范,帮助读者完整掌握 QMP 的协议细节,并能够直接编写出符合规范的 QMP 客户端。

QMP 协议总览

QMP 是一个以换行符为边界、基于 JSON 文本行的请求/响应协议。它的定位是"机器级"(machine-level)接口,与面向人机交互的 HMP(Human Monitor Protocol)不同,QMP 的所有消息都是结构化 JSON 对象,便于程序化解析与自动化运维。

根据 docs/interop/qmp-spec.rst,该协议同时服务两类角色:

  • Server(服务端):QEMU 进程本身,或 QEMU Guest Agent(QGA);
  • Client(客户端):任何通过 QMP 与 Server 通信的应用程序。

具体命令与数据结构的详细参考分别见 docs/interop/qemu-qmp-ref.rst(QEMU QMP 参考)与 qemu-ga-ref(QGA 参考),本文聚焦协议本身的通用格式规范。

协议的基本数据约定

规范中提到的所有 JSON 数据结构均以如下形式表示:

json-DATA-STRUCTURE-NAME

其中DATA-STRUCTURE-NAME是任意合法的 JSON 数据结构(依据 JSON 标准 RFC 8259 定义)。协议层的核心约定如下:

  • 编码:Server 期望输入为 UTF-8 编码,输出为 ASCII 编码;
  • 成员顺序:文档中为了阅读方便会按固定顺序列出 json-object 的成员,但真实协议中成员可以是任意顺序,客户端不能假设任何特定顺序;
  • 数组顺序:json-array 的元素顺序默认是重要的(除非另行说明),客户端应保留数组顺序语义;
  • 重复键:在同一个 json-object 中重复出现同一个键会产生不可预测的结果,属于未定义行为;
  • 单引号扩展:为方便起见,Server 接受用'单引号'替代标准"双引号"的 json-string;两种输入形式都额外理解转义序列\'(表示单引号)。但 Server 在输出时只会使用双引号

消息的通用形态

Server 发送的所有交互消息都是 json-object,并且总是以 CRLF(\r\n)结尾。除非特别说明,json-object 的所有成员都是必需的。

连接建立:Server Greeting(服务端问候)

当客户端刚刚连接到 QMP 时,Server 会立即发送一条问候消息(greeting),其作用有两点:

  1. 确认连接成功建立;
  2. 宣告 Server 已就绪,可以进行能力协商(见下文"Capabilities Negotiation")。

问候消息格式:

{ "QMP": { "version": json-object, "capabilities": json-array } }

各成员含义:

  • version:Server 的版本信息,格式与query-version命令的返回值一致(包含qemu的三段式版本号major/minor/micropackage字段);
  • capabilities:声明 Server 在基线规范之外支持的扩展能力,数组内元素顺序无特定意义。

从源码看,问候消息由 monitor/qmp.c 中的qmp_greeting()生成:它通过qmp_marshal_query_version()填充版本信息,再遍历mon->capab_offered[]数组,把 Server 支持的能力名(如"oob")依次追加到cap_list中,最终拼装为{'QMP': {'version': ..., 'capabilities': ...}}结构。也就是说,greeting 中的 capabilities 列表是动态生成的,只包含当前连接实际可用的能力。在 monitor/qmp.c 的monitor_qmp_caps_reset()中可以看到,oob能力的提供与否取决于monitor_requires_iothread()的返回值——只有启用了 monitor I/O 线程(如基于 iothread 的 chardev 后端)时,oob才会被列入 offered 能力。

当前支持的能力

目前规范定义的能力只有一个:

能力名说明
oob支持带外(Out-of-Band)命令执行,详见下文"Out-of-band execution"

在 QAPI 定义层,该能力对应的枚举类型QMPCapability定义于 qapi/control.json,目前仅有oob一个取值。

能力协商(Capabilities Negotiation)

客户端建立连接后,Server 处于Capabilities Negotiation(能力协商)模式。该模式有以下限制:

  • 只允许执行qmp_capabilities命令
  • 其他任何命令都会返回CommandNotFound错误;
  • 不投递任何异步事件消息

客户端应当通过qmp_capabilities命令,显式开启 greeting 中已宣告且自身支持的能力。

qmp_capabilities命令的行为在 qapi/control.json 中有完整定义,其要点包括:

  • 可选参数enable:要启用的QMPCapability值列表;客户端不得启用 greeting 中未提及的能力;若省略该字段,表示不启用任何能力(since 2.12);
  • 该命令仅在刚连接时有效:必须在其他任何命令被接受之前发出,一旦 monitor 开始接受其他命令,再调用就会失败;
  • 客户端需要显式开启能力,否则所有 QMP 能力默认关闭。

从源码实现看,monitor/qmp-cmds-control.c 中的qmp_qmp_capabilities()先检查当前命令表:若mon->commands已经是&qmp_commands(即协商已完成),则返回COMMAND_NOT_FOUND错误"Capabilities negotiation is already complete, command ignored";否则调用qmp_caps_accept()校验所请求的能力是否在mon->capab_offered[]中,若客户端请求了未提供的能力,会返回形如Capability %s not available的错误(见 monitor/qmp-cmds-control.c)。协商成功后,mon->commands被切换为&qmp_commands,Server 进入Command 模式

进入 Command 模式后:

  • 能力变更生效;
  • qmp_capabilities外的所有命令均被允许执行;
  • 异步事件开始正常投递。

qmp_capabilities命令还带有allow-preconfig标记(见 qapi/control.json),意味着在 preconfig 阶段也可调用。另外,monitor/qmp.c 的monitor_qmp_dispatch()中还做了一层兜底:在协商模式下若收到非qmp_capabilities命令且返回了CommandNotFound,会将其错误描述替换为更有指导意义的"Expecting capabilities negotiation with 'qmp_capabilities'"

命令执行(Issuing Commands)

命令执行请求有两种格式:

{ "execute": json-string, "arguments": json-object, "id": json-value }

或(请求带外执行):

{ "exec-oob": json-string, "arguments": json-object, "id": json-value }

各成员含义:

  • execute/exec-oob:标识要执行的命令名。exec-oob请求带外执行(要求能力协商阶段已启用oob);
  • arguments:命令所需的参数对象,当命令不需要参数时可省略;每个命令都文档化了它接受何种参数内容;
  • id:事务标识,可选。若提供,则对应的响应消息中会原样带回该id,便于请求与响应配对;id可以是任意 JSON 值,实践中推荐使用每次递增的 json-number。

从源码看,qapi/qmp-dispatch.c 中的qmp_dispatch_check_obj()负责对请求对象做结构性校验:execute(或启用了oob时的exec-oob)必须是字符串,arguments必须是对象,id会被放行,出现重复的execute/exec-oob键或未知键则报错。这印证了协议"严格校验、宁严勿松"的设计取向。

Out-of-band 带外执行

默认情况下,Server顺序地读取、执行并响应命令,客户端因此会按发出顺序收到响应。启用oob能力(通过能力协商)后,行为发生变化:

  • Server 一边读取一边把命令排入队列,再逐一从队列取出执行;
  • 带外命令插队exec-oob命令会跳过队列中的在带命令被立即执行,因此客户端可能先收到该命令的响应,后收到先前在带命令的响应;
  • 为把响应匹配回命令,客户端必须为带外命令携带id;规范建议接受oob能力的客户端为所有命令都携带id
  • 如果客户端发送在带命令的速度超过 Server 的执行速度,Server 会暂停读取请求,直到请求队列长度降到可接受范围;
  • 为保证带外命令能被读取并执行,客户端在途(in-flight)的在带命令最多不要超过 8 个
  • 只有少数命令支持带外执行,判断标准是query-qmp-schema输出中该命令带有"allow-oob": true

从源码看,带外命令的"插队"路径在 monitor/qmp.c 的handle_qmp_command()中非常直观:一旦检测到请求含exec-oob键(qmp_is_oob(qdict)),就立即调用monitor_qmp_dispatch()执行并返回,根本不会进入在带命令的排队逻辑(g_queue_push_tail(mon->qmp_requests, ...))。在带命令的队列上限由QMP_REQ_QUEUE_LEN_MAX控制(见 monitor/qmp.c),队列满时monitor_suspend()会挂起输入,待 monitor/qmp.c 的monitor_qmp_dispatcher_co()协程消费后monitor_resume()恢复——这就是规范中"暂停读取请求"的底层实现。

哪些命令支持 OOB?查看 QAPI 定义即可:例如迁移控制命令migrate-pausemigrate-continue定义在 qapi/migration.json,yank 系列命令在 qapi/yank.json,它们都显式标注了'allow-oob': true。而allow-oob字段本身在 QAPI 内省结构中定义为布尔类型,见 qapi/introspect.json。

命令响应(Commands Responses)

命令执行后 Server 会产生两类响应:成功(success)或失败(error)。只要命令携带了id,对应的响应消息中就会附带相同的id,客户端应丢弃所有id未知的响应

成功响应

格式:

{ "return": json-value, "id": json-value }
  • return:命令返回的数据,具体内容按命令而定——通常是 json-object 或 json-array,有时是 json-number、json-string;若命令无返回数据则为空 json-object{}
  • id:若客户端发出时携带了事务标识,则原样返回。

错误响应

格式:

{ "error": { "class": json-string, "desc": json-string }, "id": json-value }
  • class:错误类别名称,例如"GenericError"
  • desc:人类可读的错误描述。客户端不应尝试解析该文本(它不构成稳定接口);
  • id:若客户端发出时携带了事务标识,则原样返回。

需要注意:某些错误可能发生在 Server 尚未读取到id成员之前(例如 JSON 解析失败),此时即使客户端提供了id,错误响应中也不包含id

带外执行中的响应乱序

在启用oob后,响应的到达顺序不再保证与请求发出顺序一致(原因见上文),因此客户端绝不能假设响应顺序,必须以id为唯一的配对依据。

异步事件(Asynchronous Events)

由于状态变化,Server 可能在任何时候(只要不处于响应发送中间)主动向客户端推送消息,这类消息称为"异步事件"。格式:

{ "event": json-string, "data": json-object, "timestamp": { "seconds": json-number, "microseconds": json-number } }

各成员含义:

  • event:事件名称;
  • data:事件专属数据,按事件定义;可选
  • timestamp:事件在 Server 侧发生的精确时间,是固定结构的 json-object:secondsmicroseconds表示相对 Unix Epoch(1970-01-01)的时间;若获取宿主机时间失败,两个成员都会被置为-1

事件的具体清单见 docs/interop/qemu-qmp-ref.rst(如POWERDOWNRESETSHUTDOWNDEVICE_DELETED等)。此外规范还说明:

  • 部分事件被限速为每秒至多一条:若一秒钟内到达多条"相似"事件,除最后一条外其余全部丢弃,且最后一条会被延迟投递;"相似"通常指事件类型相同。该机制用于防止事件洪泛压垮客户端。

值得注意的是,在能力协商模式下异步事件不会被投递(见上文"Capabilities Negotiation"),源码对应实现是 monitor/qmp.c 的monitor_qmp_emit_event():若qmp->commands == &qmp_cap_negotiation_commands则直接返回,不发送任何事件。

将 JSON 解析器恢复到已知良好状态

不完整或非法的输入可能让 Server 的 JSON 解析器陷入无法继续解析后续命令的状态。恢复方法是人为制造一个词法错误(lexical error),最干净的做法是发送一个 ASCII 控制字符——但不能是\t(水平制表符)、\r(回车)、\n(换行)。

如果客户端需要兼容老版本 QEMU(它们可能无法把普通控制字符标记为错误),则应改发一个0xFF 字节

QGA 同步(QGA Synchronization)

当客户端通过不具备完善连接语义的传输通道(如 virtio-serial)连接 QGA 时,QGA 可能已经读取了上一个客户端遗留的部分输入。此时客户端需要:

  1. 使用上一节"解析器恢复到已知良好状态"的方法,强制 QGA 的解析器进入已知良好状态;
  2. 客户端还可能收到上一个客户端未读走的输出。为跳过这些残留输出,QGA 提供了guest-sync-delimited命令(详见 QGA 参考文档 qemu-ga-ref)。

QMP 实战示例

规范给出了完整的真实交互示例,其中->表示客户端发送,<-表示 Server 回复。

示例 1:Server greeting

<- { "QMP": {"version": {"qemu": {"micro": 0, "minor": 0, "major": 3}, "package": "v3.0.0"}, "capabilities": ["oob"] } }

示例 2:能力协商

-> { "execute": "qmp_capabilities", "arguments": { "enable": ["oob"] } } <- { "return": {}}

示例 3:简单执行stop命令

-> { "execute": "stop" } <- { "return": {} }

示例 4:查询 KVM 信息(带 id)

-> { "execute": "query-kvm", "id": "example" } <- { "return": { "enabled": true, "present": true }, "id": "example"}

示例 5:解析错误

-> { "execute": } <- { "error": { "class": "GenericError", "desc": "JSON parse error, expecting value" } }

示例 6:Powerdown 事件

<- { "timestamp": { "seconds": 1258551470, "microseconds": 802384 }, "event": "POWERDOWN" }

示例 7:带外执行(错误场景)

-> { "exec-oob": "migrate-pause", "id": 42 } <- { "id": 42, "error": { "class": "GenericError", "desc": "migrate-pause is currently only supported during postcopy-active state" } }

可以看到:解析错误(示例 5)由于发生在读取id之前,错误响应中没有id;而带外命令(示例 7)是合法 JSON,id被原样带回。这正对应前文关于错误响应中id是否存在的说明。

兼容性考虑(Compatibility Considerations)

协议在演进过程中保持严格的向后兼容策略:

  • 不兼容的协议改动默认关闭,并通过 greeting 中的 capabilities 数组宣告;客户端检查该数组后只启用自己支持的能力;
  • QMP Server 对命令参数执行类型检查:若某个值与其键的期望类型不符,或客户端包含 Server 无法识别的键,会生成错误;这种严格性可以及早暴露客户端对 Server schema 的错误假设。客户端可以假定:这类校验错误发生在命令产生任何副作用之前(即校验失败不会留下半执行状态)。

但客户端不得假设以下任何一点:

  • json-array 的长度;
  • json-object 的大小——特别是未来版本可能新增键,客户端应能忽略未知键
  • json-object 成员或 json-array 元素的顺序;
  • 命令可能产生的错误数量——新版本 Server 可能给任何既有命令增加新错误。

此外,任何以x-开头的命令或成员名都被视为实验性的,未来版本可能以不兼容方式被移除或修改。

最后,Server 只保证输出合法 JSON;除此之外,客户端应遵循"发送时保守,接收时宽容"(conservative in what they send, and liberal in what they accept)的原则。

下游扩展 QMP(Downstream Extension of QMP)

官方建议下游消费者(downstream)不要修改 QMP,以便管理工具无需特殊逻辑即可同时支持上游与下游版本。但既然现实中有不可避免的修改需求,规范给出了明确的互操作约定:

保留命名空间:__前缀

QMP 为下游保留了以__(双下划线)开头的 JSON 对象成员名("downstream names")。上游永远不会用这些名字命名命令、参数、错误或异步事件。下游新增的任何名字都必须以__开头;为保证与其他下游的兼容性,强烈建议再追加__RFQDN_前缀(RFQDN 是你拥有且合法的反向完全限定域名)。例如 qemu-kvm 专属的 monitor 命令:

(qemu) __org.linux-kvm_enable_irqchip

下游行为约束

  • 除提供额外能力外,不得改动 server greeting(但规范也指出连新增能力都不被鼓励,见下);
  • 上一节"兼容性考虑"对下游同样适用:对于不含下游成员的输入,下游必须表现得与上游完全一致,唯一的例外是它可以在输出中添加带下游名字的成员
  • 因此,只要客户端不发送含下游成员的输入、并能正确忽略收到的下游成员,就不应该能区分出上游与下游。

关于下游修改的官方建议

  1. 新增命令是允许的;若想扩展现有命令,考虑用带新行为的新命令替代;
  2. 新增异步消息是允许的;若想扩展现有消息,考虑新增一条消息而非修改;
  3. 为新命令引入新错误是允许的;但给现有命令添加新错误属于扩展行为,应按第 1 条处理(即改用新命令);
  4. 新增能力被强烈劝阻:能力用于演进基础协议本身,多个分叉的基础协议方言是最不受欢迎的结局。

从源码理解 QMP 的完整生命周期

综合以上规范与源码,一个典型 QMP 会话的完整流程可以总结为:

  1. 连接:客户端建立 TCP(如-qmp tcp:localhost:4444,server=on,wait=off)或 unix socket 连接;
  2. Greeting:Server 在CHR_EVENT_OPENED时(见 monitor/qmp.c)把命令表切到qmp_cap_negotiation_commands、复位能力并发送 greeting;
  3. 能力协商:客户端发送qmp_capabilities(可带enable: ["oob"]),成功后进入 Command 模式(monitor/qmp-cmds-control.c);
  4. 命令执行:客户端发送{ "execute": ... },请求经 JSON 解析(json_message_parser_feed,见 monitor/qmp.c)、结构性校验(qapi/qmp-dispatch.c)、分发执行后,通过qmp_send_response()返回结果(monitor/qmp.c);
  5. 异步事件:状态变化触发monitor_qmp_emit_event()主动推送(仅在 Command 模式);
  6. 断开CHR_EVENT_CLOSED时清理请求队列、重建解析器(monitor/qmp.c)。

对于想要进一步深入源码的读者,以下文件是核心入口:

  • 协议规范: docs/interop/qmp-spec.rst
  • 命令与内省参考: docs/interop/qemu-qmp-ref.rst
  • QMP 主实现: monitor/qmp.c
  • 控制类命令实现: monitor/qmp-cmds-control.c
  • 分发与校验核心: qapi/qmp-dispatch.c
  • 控制类命令与能力枚举的 QAPI 定义: qapi/control.json
  • OOB 相关命令定义(allow-oob: true): qapi/migration.json、qapi/yank.json
  • 内省结构中allow-oob字段: qapi/introspect.json

掌握以上协议格式与源码路径后,开发者既可以直接手工编写 QMP 客户端脚本(如用 Python 的socket连接并收发 JSON),也可以在此基础上理解 libvirt、OpenStack 等上层管理栈如何通过 QMP 驱动 QEMU,为诊断问题、定制管理工具或做下游集成打下坚实基础。

【免费下载链接】qemuOfficial QEMU mirror. Please see https://www.qemu.org/contribute/ for how to submit changes to QEMU. Pull Requests are disabled. Please only use release tarballs from the QEMU website.项目地址: https://gitcode.com/gh_mirrors/qe/qemu

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询