在 libvirt/qemu/kvm 虚拟化环境中,当收到 qemu 发送的虚机事件后,libvirt 事件处理可以分为两个部分:
- libvirt 服务端(libvirtd)自身进行相关事件的处理,比如对于虚机 SHUTDOWN 事件会调用
qemuMonitorJSONHandleShutdown进行处理; - libvirt 服务端将事件发送给注册监听事件的客户端(如 nova),上层客户端再进行相关事件的处理,比如 nova 对于虚机生命周期事件执行对应的回调函数 _event_lifecycle_callback。
本文主要介绍服务端虚机事件线程初始化、添加事件监听及事件处理,后面会再更新一篇文章介绍客户端事件监听初始化及处理。
如有不对之处,烦请指正,感谢。
0、环境信息
- arch:x86_64
- libvirt:9.10.0
- qemu:10.2.0
其中 libvirt 来自 欧拉社区(调试方便),qemu 来自上游社区。
下面开始介绍服务端初始化监听与事件处理,主要包括三个部分:事件线程创建、事件监听添加和事件轮询处理。
1、事件线程创建
libvirt 中事件线程主要包括两类:一是全局事件线程比如 qemu-event 和 udev-event,二是虚机事件线程 vm-domName。
- 全局事件线程:在服务端启动的时候创建,如 qemu-event 为线程池模式,但默认只会创建一个线程,适用于所有虚机,可以处理 GUESTPANIC、DEVICE_DELETED 和 MONITOR_EOF 等复杂事件;
- 虚机事件线程:在虚机启动的时候创建,基于 glib 事件循环机制,每个虚机都会创建一个,处理该虚机所产生的相关事件,比如 SHUTDOWN、RESUME、STOP 等简单事件(复杂事件提交到全局事件线程池处理,避免阻塞)。
通过 top -Hp $(pidof libvirtd) 命令查看 libvirtd 线程情况如下,可以看到,其中包含了 udev-event、qemu-event 和 vm-test 三个事件线程(其中 test 为虚机名)。
1、全局事件线程创建
全局事件线程在 libvirt 启动的时候创建(qemuStateInitialize),以 qemu-event 为例,其创建流程如下。
main / remote_daemon.c
- daemonStateInit
- daemonRunStateInit
- virStateInitialize
- stateInitialize | qemuStateInitialize
- qemu_driver->workerPool = virThreadPoolNewFull(0, 1, 0, qemuProcessEventHandler, "qemu-event",)【线程池最多一个线程】
- virThreadPoolExpand
- virThreadCreateFull(&(*workers)[i], virThreadPoolWorker,)
其中线程池事件处理函数为 qemuProcessEventHandler,用于处理 GUESTPANIC、DEVICE_DELETED 和 MONITOR_EOF 等事件,后文具体介绍。
qemuProcessEventHandler
- processMonitorEOFEvent | QEMU_PROCESS_EVENT_MONITOR_EOF【以虚机 qemu 进程关闭产生 EOF 事件为例】
- event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,)【创建事件】
- qemuProcessStop 【处理事件】
- virObjectEventStateQueue(driver->domainEventState, event)【发送事件,下一篇文章介绍】
- virObjectEventStateQueueRemote
- virObjectEventQueuePush
- virEventUpdateTimeout
2、虚机事件线程创建
虚机事件线程在虚机启动的过程中创建(qemuDomainObjStartWorker),其基于 glib 事件循环机制,通过轮询监听添加的 fd 是否有事件产生,如果有则调用相应回调函数。
qemuProcessStart
- qemuProcessLaunch
- qemuDomainObjStartWorker【虚机事件线程创建】
- threadName = g_strdup_printf("vm-%s", dom->def->name)
- priv->eventThread = virEventThreadNew(threadName)
- virEventThreadStart
- evt->thread = g_thread_try_new()
- virEventThreadWorker
- g_main_loop_run
2、事件监听添加
事件监听添加同样在虚机启动的过程中完成,在虚机事件线程创建完成后将相关事件添加到虚机事件线程上下文中,主要包括两类,一是 qemu monitor(即 qmp),二是 qemu agent(即 qga),这两类添加逻辑类似,本文主要关注 qmp 事件。
qemuProcessStart
- qemuProcessLaunch
- qemuDomainObjStartWorker【虚机事件线程创建】
- qemuProcessWaitForMonitor【添加 qmp 事件监听】
- qemuConnectMonitor
- qemuMonitorOpen(vm->privateData->eventThread->context)【虚机事件线程上下文,即mon->context】
- qemuMonitorOpenInternal
- qemuMonitorRegister
- mon->watch = g_socket_create_source(mon->socket, ...)
- g_source_set_callback(mon->watch, (GSourceFunc)qemuMonitorIO, ...)【设置回调】
- g_source_attach(mon->watch, mon->context)【添加监听】
- qemuConnectAgent【添加agent事件监听】
- qemuAgentOpen(vm->privateData->eventThread->context)【虚机事件线程上下文】
- qemuAgentRegister
3、事件轮询处理
上述 qmp 事件监听函数为 qemuMonitorIO,如果 qmp socket 上面有事件产生,则会调用 qemuMonitorIO。
- 对于 SHUTDOWN 等事件,则在该虚机事件线程中直接处理,即
qemuMonitorJSONHandleShutdown; - 对于 EOF 等事件,则提交到上述全局事件线程池 qemu-event 中处理(
qemuProcessEventHandler),即qemuProcessEventSubmit。
qemuMonitorIO
- qemuMonitorIOProcess【事件解析与处理】
- qemuMonitorJSONIOProcess
- qemuMonitorJSONIOProcessLine
- qemuMonitorJSONIOProcessEvent
- qemuMonitorEmitEvent
- qemuProcessHandleEvent | domainEvent【通用处理,只发送事件】
- event = virDomainQemuMonitorEventNew
- virObjectEventStateQueue(driver->domainEventState, event)
- handler = bsearch【专用处理,通过二分搜索获取对应回调】
- handler->handler | qemuMonitorJSONHandleShutdown【以虚机关机产生 SHUTDOWN 事件为例】
- qemuMonitorEmitShutdown
- qemuProcessHandleShutdown | domainShutdown【专用处理】
- event = virDomainEventLifecycleNewFromObj【生成事件】
- qemuProcessShutdownOrReboot【处理事件】
- virObjectEventStateQueue【发送事件】
- eofNotify(goteof=True) | qemuProcessHandleMonitorEOF【虚机 qemu 进程关闭产生 EOF 事件】
- qemuProcessEventSubmit【提交到全局线程池处理】
- virThreadPoolSendJob
4、示例:虚机关机事件处理全流程
下面我们以虚机内部触发 shutdown 操作为例,介绍下事件从虚机内部到 libvirt 的整个流程(guest-->kvm-->qemu-->libvirt ),其中 libvirt 侧主要处理包含虚机内部关机直接触发的 SHUTDOWN 事件及由 qemu 进程清理时移除 qmp socket 监听引起的 MONITOR_EOF 事件。
- SHUTDOWN 事件:同步处理,虚机事件线程 vm-test 直接处理(
qemuMonitorJSONHandleShutdown→qemuMonitorEmitShutdown→qemuProcessHandleShutdown); - MONITOR_EOF 事件:异步处理,提交给全局事件线程池 qemu-event 处理(
qemuProcessEventSubmit→qemuProcessEventHandler→qemuProcessHandleMonitorEOF)。
具体流程如下:
1、Guest 层:用户发起关机
- GuestUser:执行 shutdown;
- GuestKernel:内核调用 ACPI 子系统,准备发送 S5(Soft-Off)信号;
- GuestKernel -> KVM:ACPI PM1a_CNT 寄存器写操作(port 0xb004 / 0x604 写入 S5 值);
2、KVM 层:捕获 IO 并 VM-EXIT
- KVM:VM-EXIT 触发,检测到 Guest 写 ACPI 关机端口;
- KVM -> QEMU:通过 KVM_RUN ioctl 返回,将 IO 操作转发给 QEMU 用户空间;
3、QEMU 层:处理 ACPI 关机
- QEMU:acpi_pm_cnt_write 识别为 S5 Soft-Off 请求;
-
QEMU:处理 shutdown 事件,
qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); -
QEMU -> QMP:发送事件,
qemu_system_shutdown(casue=SHUTDOWN_CAUSE_GUEST_SHUTDOWN)→qapi_event_emit (event=QAPI_EVENT_SHUTDOWN),此时发送内容为{"event": "SHUTDOWN", "data": {"guest": true, "reason": "guest-shutdown"},说明是虚机内部触发关机;
4、Libvirt 层:处理 SHUTDOWN 事件(guest-shutdown)
- Libvirt vm-test 线程:监听到 qmp socket 可读,调用
qemuMonitorIO()处理事件,解析消息确认为 SHUTDOWN 事件,调用 qemuMonitorJSONHandleShutdown 直接在线程内同步处理; - Libvirt ->QEMU:在关机处理流程
qemuProcessHandleShutdown中,会更新虚机状态为 VIR_DOMAIN_SHUTDOWN,并 kill 虚机 QEMU 进程,即调用virProcessKill (pid=2179, sig=15)向 QEMU 进程发送 SIGTERM 信号;
5、QEMU 层:处理 SIGTERM 信号
- QEMU:收到 SIGTERM 信号后,会调用
termsig_handler进行处理(在qemu_system_killed中将 shutdown_requested 修改为 SHUTDOWN_CAUSE_HOST_SIGNAL); - QEMU -> QMP:发送事件,
qemu_system_shutdown (cause=SHUTDOWN_CAUSE_HOST_SIGNAL)→qapi_event_emit (event=QAPI_EVENT_SHUTDOWN),此时发送内容为{"event": "SHUTDOWN", "data": {"guest": false, "reason": "host-signal"},是 QEMU 进程收到信号触发关机;
6、Libvirt 层:处理 SHUTDOWN 事件(host-signal)
- Libvirt vm-test 线程:监听到 qmp socket 可读,调用
qemuMonitorIO()处理事件,解析消息确认为 SHUTDOWN 事件,同样调用qemuMonitorJSONHandleShutdown直接在线程内同步处理,qemuProcessHandleShutdown中获取虚机状态为 VIR_DOMAIN_SHUTDOWN,重复事件不再处理;
7、QEMU 层:终止进程
- QEMU:继续 处理 SIGTERM 信号,
main_loop_should_exit返回 true,QEMU 会退出主循环qemu_main_loop,调用qemu_cleanup开始清理动作,此时会在monitor_cleanup中 移除 qmp socket 监听(触发 G_IO_HUP ); - QEMU:清理完成后,QEMU 会调用
exit退出。
8、Libvirt 层:处理 MONITOR_EOF 事件
- Libvirt vm-test 线程:监听到 qmp socket 关闭(G_IO_HUP),调用
qemuMonitorIO()处理事件,mon->goteof 设置为 true,调用(eofNotify)(mon, vm)即qemuProcessHandleMonitorEOF进行处理,然后调用qemuProcessEventSubmit提交到全局事件线程池 qemu-event 中进行处理; - Libvirt qemu-event 线程:
qemuProcessEventHandler中判断事件类型为 QEMU_PROCESS_EVENT_MONITOR_EOF,调用processMonitorEOFEvent进行处理,processMonitorEOFEvent处理细节就不再展开了,包括调用qemuProcessStop进行一系列清理操作及更新虚机状态为 VIR_DOMAIN_SHUTOFF。
至此,虚机关机事件处理完成,整个流程还相对比较复杂,关于 Libvirt 服务端向客户端发送注册事件更新等下一篇文章再介绍了:)。
5、总结
下面是使用大模型根据本文内容生成的流程图。
1、libvirt 事件处理机制流程图
基于 Gemini 3.1 Pro 生成结果修改。
2、虚机内部关机事件流程图。
基于 GPT-5.4 Thinking 生成结果修改。
6、参考资料
1、QEMU event handlers,https://www.libvirt.org/kbase/internals/qemu-event-handlers.html