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),其作用有两点:
- 确认连接成功建立;
- 宣告 Server 已就绪,可以进行能力协商(见下文"Capabilities Negotiation")。
问候消息格式:
{ "QMP": { "version": json-object, "capabilities": json-array } }各成员含义:
version:Server 的版本信息,格式与query-version命令的返回值一致(包含qemu的三段式版本号major/minor/micro与package字段);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-pause、migrate-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:seconds和microseconds表示相对 Unix Epoch(1970-01-01)的时间;若获取宿主机时间失败,两个成员都会被置为-1。
事件的具体清单见 docs/interop/qemu-qmp-ref.rst(如POWERDOWN、RESET、SHUTDOWN、DEVICE_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 可能已经读取了上一个客户端遗留的部分输入。此时客户端需要:
- 使用上一节"解析器恢复到已知良好状态"的方法,强制 QGA 的解析器进入已知良好状态;
- 客户端还可能收到上一个客户端未读走的输出。为跳过这些残留输出,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 条处理(即改用新命令);
- 新增能力被强烈劝阻:能力用于演进基础协议本身,多个分叉的基础协议方言是最不受欢迎的结局。
从源码理解 QMP 的完整生命周期
综合以上规范与源码,一个典型 QMP 会话的完整流程可以总结为:
- 连接:客户端建立 TCP(如
-qmp tcp:localhost:4444,server=on,wait=off)或 unix socket 连接; - Greeting:Server 在
CHR_EVENT_OPENED时(见 monitor/qmp.c)把命令表切到qmp_cap_negotiation_commands、复位能力并发送 greeting; - 能力协商:客户端发送
qmp_capabilities(可带enable: ["oob"]),成功后进入 Command 模式(monitor/qmp-cmds-control.c); - 命令执行:客户端发送
{ "execute": ... },请求经 JSON 解析(json_message_parser_feed,见 monitor/qmp.c)、结构性校验(qapi/qmp-dispatch.c)、分发执行后,通过qmp_send_response()返回结果(monitor/qmp.c); - 异步事件:状态变化触发
monitor_qmp_emit_event()主动推送(仅在 Command 模式); - 断开:
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),仅供参考