QEMU monitor:不进客户机观测运行状态、设备与快照的控制通道
虚拟机启动之后,人在宿主机上其实是被隔在外面的:客户机有自己的内核、自己的磁盘视图和自己的进程表,宿主机的 ps、ls 看不到里面发生了什么。如果客户机里还没装系统、串口没有登录入口,或者网络根本没配通,连「进去看一眼」都做不到。QEMU monitor 就是为这个时刻准备的——它是 QEMU 进程开在控制面上的一扇侧门,观测与操控都不经过客户机。
本篇在一台无界面的 RISC-V virt 虚拟平台上完成全部实验:后台启动 QEMU,通过 Unix 套接字接入 monitor,查询运行状态、块设备与 CPU,打一次内部快照,最后让虚拟机退出并在进程外验证快照确实落盘。环境搭建与第一个虚拟平台见 QEMU 安装与第一个 RISC-V 虚拟平台,虚拟机、镜像与快照的基本概念见 认识 QEMU:从零看懂虚拟机、镜像与快照。
本文要回答的问题
- 虚拟机已经在跑了,为什么还需要一条独立于客户机的控制通道?
- HMP 和 QMP 是什么关系,为什么发一条监控命令要套两层协议?
- 连上 QMP 套接字后为什么不能立刻下命令,
qmp_capabilities这一步在握手什么? - 不登录客户机,能查到哪些运行时信息,
info status、info block、info cpus各自返回什么? savevm打快照的瞬间虚拟机发生了什么,退出后怎么从进程外证明快照真的写进了镜像?
一、为什么需要 monitor:控制面与数据面分离
先把一次普通的虚拟机运行拆成两个面。数据面是虚拟机的本职工作:虚拟 CPU 取指执行、virtio 块设备把 Guest 的读写翻译成对 qcow2 文件的 IO、虚拟内存按页表翻译。这条面服务的对象是客户机,宿主机用户只能在外面等结果。控制面则是 QEMU 进程留给运维者的管理接口:查询虚拟机是否在运行、枚举挂上了哪些设备、在某个时刻冻结整机状态存成快照、或者干脆让它关机。
两面分开的好处在「客户机不可达」时最明显。本篇实验刻意用 -display none 启动,没有图形窗口、没有串口登录、客户机里也没有操作系统可交互,整条数据面对操作者是封闭的;但 monitor 照常工作,因为它挂在 QEMU 进程自己身上,不依赖客户机配合。嵌入式仿真里这更是常态:跑一段裸机固件时,唯一的观测手段往往就是 monitor。
本篇的完整操作链路如下,从虚拟机启动到外部验证一共六步,后面每一节对应其中一段。
这张流程卡先给出全篇骨架:蓝色是准备动作,灰色是接入,紫色是协议握手,绿色是只读观测,橙色是会改变虚拟机状态的写操作,红色收尾是关机决策。记住颜色的语义分工,后面看到事件流时就能立刻分辨哪些动作只读、哪些会让虚拟机停顿。
二、HMP 与 QMP:人敲的命令行与程序读的 JSON
QEMU monitor 有两套并存的对话语言。HMP(Human Monitor Protocol,人监控协议)是给人在终端里敲的传统命令行,命令长着 info status、savevm snap1 这样的短词,返回也是给人读的成行文本。QMP(QEMU Machine Protocol,QEMU 机器协议)是给程序读的现代接口,每条命令都是一个 JSON 对象,返回永远是结构化 JSON,还会主动推送异步事件。
生活化的类比:HMP 像银行柜台,你对柜员说一句人话,柜员用人话回答你,适合现场办理;QMP 像银行的开放 API,请求和响应都是固定字段的报文,适合写脚本、做自动化编排和被 libvirt 这类管理平台调用。两者不是新旧替代关系——QMP 是底层唯一的机器协议,而 HMP 命令可以通过一座「桥」在 QMP 通道里执行,这座桥就是 human-monitor-command。
| 维度 | HMP | QMP |
|---|---|---|
| 面向对象 | 人,终端手工敲 | 程序,脚本与管理平台 |
| 报文形态 | 短命令加文本返回 | JSON 对象,结构化返回 |
| 典型命令 | info status、savevm snap1 | query-status、human-monitor-command |
| 异步通知 | 无 | 有,事件主动推送(STOP、SHUTDOWN 等) |
| 稳定性承诺 | 输出文案可能随版本调整 | 接口语义长期稳定 |
| 本篇接入方式 | 包在 QMP 的桥接命令里执行 | Unix 套接字直连 |
两者在进程里的接线关系如下图。终端里的 nc 进程只说 QMP,HMP 命令被装进 human-monitor-command 的 JSON 信封过河,到 QEMU 进程内部再交给 HMP 监控器解释执行,结果重新包成 JSON 字符串返回。
读图抓住一条主线:蓝色终端发出的所有报文都是 JSON,紫色 QMP 解析器是唯一入口;橙色 HMP 监控器只在桥接命令到达时才介入;绿色虚拟机是被观测和被操作的对象,它的状态变化还会沿原路以事件形式回流。灰色块是承载通道,本身不解释命令。
三、连上套接字:握手与 qmp_capabilities
实验虚拟机用下面的方式后台启动,关键是两个参数:-display none 关掉所有图形界面,-qmp unix:./output/qmp.sock,server,nowait 让 QEMU 在当前目录开出一个 Unix domain socket 作为 QMP 服务端,nowait 表示不等客户端接入就直接开始运行。
qemu-system-riscv64 -machine virt -bios default -display none \
-qmp unix:./output/qmp.sock,server,nowait \
-drive file=./output/disk.qcow2,format=qcow2,if=none,id=hd0 \
-device virtio-blk-device,drive=hd0随后用 nc -U ./output/qmp.sock 接上去。连接建立的瞬间客户端什么都还没发,QEMU 先主动推来一条版本问候,这是 QMP 协议规定的开场白。注意此时还不能下业务命令——必须先回一条 qmp_capabilities 完成能力协商,服务器返回空的 {"return": {}} 表示握手完成,通道才进入命令模式。这个设计让新旧版本可以在握手阶段协商可选能力(如乱序执行 oob),避免直接发命令撞上协议差异。
下面这张终端实录覆盖从握手到第一批只读查询的完整往返,命令回显以 + 开头,紧接着的一行就是 QEMU 的真实返回。
问候报文里的 "major": 11, "minor": 1, "micro": 2 与本机 QEMU 11.1.2 完全对应,capabilities 里列出的 oob 就是前面说的可选能力。握手后第一条原生 QMP 命令 query-status 返回 {"return": {"status": "running", "running": true}},两个字段一个给枚举状态、一个给布尔速判,这就是结构化协议相对文本输出的好处:程序不用解析句子,直接取字段。紧接着的桥接 HMP 命令 info status 只回了一句 VM status: running,结论一致,形态不同——同一个事实的两种表达。

读这张终端记录按六个编号小节往下走:开头 qemu-img create 建好 64 MiB 的 qcow2 数据盘,后台启动的 QEMU 进程号与后面 info cpus 里的 thread_id 都是 1555,印证单 vCPU 跑在主线程上;中段每条 + 开头的命令回显下面紧跟一个 JSON 信封,原生 QMP 与桥接 HMP 的返回形态差异可以直接对照;最末尾 quit 触发关机事件后,进程外的 qemu-img 仍能列出 monitor 打下的内部快照 snap1,说明快照确实落进了磁盘镜像而不是只活在 monitor 会话里。
四、info 命令族:块设备与 CPU 的运行时清单
确认活着之后,下一步是盘点它身上挂了什么。HMP 的 info 命令族就是运行时清单工具,本篇实测三条最常用的:info block 看块设备,info cpus 看虚拟 CPU,外加前面用过的 info status。它们都经 human-monitor-command 桥接执行,返回值是包在 JSON 里的多行文本,\r\n 是换行转义。
info block 的返回信息量最大。drive0 (#block194) 表明这个盘挂在 QEMU 块设备层的 194 号节点上, backing 文件是 ./output/disk.qcow2,格式 qcow2;Cache mode: writeback 说明宿主机页缓存默认回写,Guest 的写请求先进缓存再落盘,性能优先、掉电安全性弱于 writethrough;Attached to 那串路径指出它最终接在机器的匿名外设节点上,也就是启动命令里那条 virtio-blk 设备。另外还能看到一个 floppy0: [not inserted]——virt 机器自带软驱控制器但没插介质,属于正常空置。
{
"return": "drive0 (#block194): ./output/disk.qcow2 (qcow2)\r\n Attached to: /machine/peripheral-anon/device[0]\r\n Cache mode: writeback\r\n\r\nfloppy0: [not inserted]\r\n Removable device: not locked, tray closed\r\n"
}info cpus 在这台单 vCPU 虚拟机上返回 * CPU #0: thread_id=1555 model=rv64。行首的星号表示当前调度所在的 CPU,0 号是唯一一颗;model=rv64 对应 64 位 RISC-V 架构;thread_id=1555 是承载这颗 vCPU 的宿主机线程号,而实验启动记录里 QEMU 进程号正是 1555——单 vCPU 配置下,虚拟 CPU 就跑在 QEMU 的主线程上,两个编号相同是这台机器的实测结果。多 vCPU 时这里会列出多行,线程号也会各不相同。
一个版本差异值得记下:QEMU 11 已经没有老教程里常出现的 info devices 命令,照抄会得到未知命令报错。枚举设备改用两条路径——存储类看 info block,整机设备树看 info qtree,后者把机器的设备层级完整打印出来,是排查「设备到底挂没挂上」的首选。
五、savevm 快照与事件流:停顿、存盘、恢复
观测之外,monitor 还能改变虚拟机的状态,savevm snap1 就是最重的一个动作:把整机运行现场(CPU 寄存器、设备状态)连同磁盘当前内容一起存成 qcow2 内部快照,标签叫 snap1。内部快照与镜像文件的关系、它和外部快照的区别,qcow2 扩容、压缩与快照管理 有完整展开,这里专注 monitor 侧能观察到的过程。
快照要保证整机状态一致,QEMU 必须先让虚拟 CPU 停下来。下面的实录里,savevm 发出后通道先推送一个 STOP 事件,约 0.2 秒后推送 RESUME,命令本身才返回空字符串——这两个事件就是「停顿存盘、存完继续」的直接证据。整个过程不需要客户机知情,也没有任何客户机内的操作。
事件带宿主机时间戳,STOP 与 RESUME 相差约 0.2 秒,对一个 64MiB 的小盘这就是快照停顿的量级;盘越大、脏状态越多,这个窗口越长。存完用 info snapshots 复查,列表里出现 TAG 为 snap1 的记录:VM_SIZE 732 KiB,VM_CLOCK 是 0000:00:02.307,即快照瞬间客户机视角的虚拟时钟只走了 2.3 秒,日期戳为 2026-11-07 23:27:07。VM_CLOCK 记录的是虚拟机运行时长而不是宿主机墙钟,这个区别在回溯快照时很关键。
最后发原生 QMP 命令 quit 让虚拟机关机。返回之前先收到 SHUTDOWN 事件,data 里 "guest": false 说明关机不是客户机自己发起的,"reason": "host-qmp-quit" 精确指认来源:宿主机通过 QMP 命令要求退出。事件先行、确认在后,是 QMP 的固定次序,自动化脚本应该监听事件而不是 sleep 猜时长。
六、进程外交叉验证:快照真的写进镜像了吗
monitor 说存了快照,不等于镜像文件里真有。控制面的自述需要一个站在 QEMU 进程外的独立证人,这就是 qemu-img snapshot -l——虚拟机已经退出,QEMU 进程不存在了,完全由镜像工具直接读 qcow2 元数据。
qemu-img snapshot -l ./output/disk.qcow2外部列表与 monitor 内部列表逐字段对上:ID 为 1,TAG 是 snap1,VM_SIZE 同样是 732 KiB,VM_CLOCK 同样停在 0000:00:02.307,日期戳一致。ID 在 info snapshots 里显示为 -- 是 HMP 的展示习惯,外部工具给出了 qcow2 元数据里的真实编号 1。两条观测路径、两个独立工具,指向同一条快照记录,证据链到此闭合。
这也厘清了 savevm 与 qemu-img snapshot 的分工:前者是运行时动作,能在虚拟机活着时连设备状态一起存,停顿以事件流可见;后者是离线工具,只能操作关机后的镜像,适合列出、删除和应用快照。日常做模板克隆时更常用的 backing file 增量链,则是另一套「元数据加增量」思路,实战写法见 qcow2 backing file:增量链原理与秒级克隆虚拟机。
这一篇在知识体系里的位置
本篇是 SoC 与 QEMU 入门路线上控制面的第一篇:向上承接 认识 QEMU:从零看懂虚拟机、镜像与快照 的基本概念与 QEMU 安装与第一个 RISC-V 虚拟平台 的环境准备,向下为调试类内容铺路——后续用 QEMU 仿真裸机程序和外设时,monitor 的寄存器查看、设备树枚举与快照回滚会成为主要观测手段。横向对照阅读:qcow2 扩容、压缩与快照管理 讲透内部快照的镜像格式原理,qcow2 backing file:增量链原理与秒级克隆虚拟机 展示快照思想在克隆场景的另一种产品形态。
代码获取:本篇全部实验(镜像准备、无界面启动、QMP 握手、info 命令族、savevm 快照与外部验证)的可复现脚本见公开仓库 derekzhuo/geek-doc.cn 的
experiments/qemu-monitor-basics/目录。克隆后执行./run.sh即可在output/重新生成套接字、镜像与完整实验日志。
实验环境
机器/系统:MacBook Pro(Intel x86_64)/ macOS 26
工具版本:QEMU 11.1.2(
qemu-system-riscv64 --version实测)、系统自带nc(Unix 套接字客户端)日期:2026-11-07
完整命令序列:
bash./run.sh脚本内部依次完成:
qemu-img create -f qcow2 disk.qcow2 64M建盘;带-qmp unix:./output/qmp.sock,server,nowait的qemu-system-riscv64后台启动;nc -U接入后发送qmp_capabilities、query-status、三条human-monitor-command桥接的info命令、savevm snap1与quit;虚拟机退出后执行qemu-img snapshot -l做外部交叉验证。
总结
QEMU monitor 是独立于客户机的运行时控制通道,本篇在无界面的 RISC-V 虚拟机上走通了整条链路:经 Unix 套接字接入 QMP,先发 qmp_capabilities 握手才能下命令;原生 QMP 的 query-status 返回结构化 JSON,HMP 的 info status、info block、info cpus 则通过 human-monitor-command 桥接执行,分别报出运行状态、qcow2 盘与 writeback 缓存模式、rv64 虚拟 CPU;savevm snap1 打快照时 STOP 与 RESUME 事件先后来推,生成 732 KiB 的内部快照,quit 触发 reason 为 host-qmp-quit 的 SHUTDOWN 事件;虚拟机退出后 qemu-img snapshot -l 在进程外读到同一条快照记录,完成内外交叉验证。