QEMU如何实现虚拟机关机和重启
时间:2023-06-30 11:00:01 | 来源:网站运营
时间:2023-06-30 11:00:01 来源:网站运营
QEMU如何实现虚拟机关机和重启:
1. 概述
突然发现,QEMU的hmp命令中,只有关机(system_powerdown)和整个系统复位(system_reset,直接虚拟机硬件复位)的命令,并没有虚拟机重启的命令。那QEMU是如何区分和实现虚拟机系统的关机和重启操作的呢?如果再将操作的来源(虚拟机内、虚拟机外)考虑在内,则可以将这个问题拆分成四个问题问题:
- 虚拟机内关机操作的实现
- 虚拟机外关机操作的实现
- 虚拟机内重启操作的实现
- 虚拟机外重启操作的实现
对于计算机电源方面的操作,其实可以有多种实现方式,目前主流的操作系统,如Windows、Linux,基本上都是支持基于ACPI机制实现的,以下的介绍也都是基于ACPI机制。并且,可以通过QEMU的qmp事件来获取QEMU发生的一些事件,查看到虚拟机的行为。
2. ACPI电源管理机制
ACPI标准中,会对系统的各种电源状态定义成Sx状态,如正常工作为S0,Win10的休眠对应S4,关机对应S5等。并且在提交给OS的ACPI表中,会定义System State Package,告知OS需要往PM1a/b_CNT.SLP_TYPE控制寄存器写入什么样的数值让系统进入对应的Sx状态。
如,在虚拟机的ACPI表的DSDT子表中,找到如下S5 Package State的定义,即表示需要为PM1a/b_CNT.SLP_TYPE中写入0,让系统进入S5(Soft Off)状态,也就是咱们正常说的关机状态。
3. 虚拟机内关机操作的实现
当在Win10虚拟机内点击Windows菜单中带的关机按钮后,当Windows关闭完各种程序后,在虚拟机关闭的瞬间,QEMU会收到SHUTDOWN事件,并且geust为True表示该关机操作是由Guest OS发起的。
查看QEMU源码,可以发现,
当虚拟机向PM1 Control Register的Sleep Type和Enable寄存器写的时候,如果Type为soft power off即S5,就会触发关机操作,即发出shutdown请求。
QEMU主线程在收到shutdown reqeust的请求后,会发送SHUTDOWN事件,然后qemu进程退出,完成虚拟机的关机操作。
4. 虚拟机外关机操作的实现
QEMU虚拟机外的关机操作是通过hmp的system_powerdown命令实现的,libvirt的virsh shutdown命令也是通过发送system_powerdown命令实现的。
和虚拟机内触发关机操作类似,只是关机操作的触发源不一样,system_powerdown模拟的是电源键按下的操作。
hmp_system_powerdown -> qmp_system_powerdown -> qemu_system_powerdown_request
main_loop_should_exit在检测到powerdown请求的时候,调用qemu_system_powerdown()
qapi_event_send_poerdown发出POWERDOWN事件。
ICH9初始化(ich9_pm_init)的时候,会注册一个powerdown notifier,即pm_powerdown_req
该notifier会模拟电源键按下产生ACPI SCI中断,OS在收到这个中断的时候,就会做出相应的处理。默认情况下,Windows的操作就是关机。
但是如果将WIndows系统中电源按钮的的动作设置为不采取任何动作,即检测到电源键按下后,不做任何操作,则hmp的system_powerdown命令或者libvirt的virsh shutdown命令将被Windows忽略,不做任何操作。
5. 虚拟机内重启操作的实现
对于支持ACPI电源管理的虚拟机(Guest OS)而言,虚拟机内触发的系统重启也是优先通过ACPI机制实现的。查看Linux内核源码reboot命令的实现,可以看到最后执行对硬件执行reset的时候,会优先选择FADT提供的reset register。
以Q35主板为例,Q35主板的南桥芯片ICH9带有reset控制逻辑单元,即lpc-reset-control,用于reset整个系统或者是CPU,当虚拟机往这边写的时候,会触发相应的reset操作。
对应的Reset寄存器定义如下所示:
当往RST_CPU比特位写1的时候,就会触发qemu_system_reset_request()。
在ACPI标准中,也有对Reset Register进行定义:
前面说的ICH9中的lpc-reset-control就是Reset Register的一种具体实现。
ACPI大表中FADT(签名为FACP)表的Offset 116起的12个字节的RESET_REG,RESET_REG包含reset regsiter的地址。并且Offset 112的Flags有比特位用于表示RESET_REG是否有实现。
其中RESET_REG字段包含的GAS(Generic Address Structure)的定义如下所示:
- Address Space ID为地址空间ID,对于RESET_REG而言,该寄存器只能映射到I/O地址空间,PCI配置空间或者系统内存空间。
- Register Bit Width,对于RESET_REG而言,固定为8比特。
- Register Bit Offset,对于RESET_REG而言,固定为0
- Access Size表示访问的对其要求。
- Address为给定地址空间的具体地址。
以Q35主板为例,在虚拟机内,通过RW Everything可以看到ACPI表中,FACP表中的Flags标记中,RESET_REG_SUP为1,标志系统支持RESET_REG寄存器。
并且reset register的地址为I/O port 0xCF9,并且reset value为0x0F,即往里面些0x0F后,系统会reset。
Guest OS根据这些信息,在执行系统重启的最后阶段(即OS已经保存好状态,为最后主要硬件的reset做好准备),就会将reset value的值写到reset register中,触发主板硬件执行reset操作。
根据前面的代码,QEMU在实现ICH9中的Reset寄存器的时候,当往Reset寄存器写的时候,就会触发reset请求(qemu_system_reset_request()),执行qemu_system_reset,并发出RESET事件。
在虚拟机的hmp通道中就可以看到RESET事件,并且guest为Ture,说明是Guest OS主动触发的reset操作。
6. 虚拟机外重启操作的实现
对于QEMU的qmp/hmp通道而言,是没有虚拟机重启的命令的,只有硬件重启(system_reset)命令,但是在libvirt端却有虚拟机重启的virsh reboot命令,那virsh reboot是如何实现的呢?
virsh reboot重启虚拟机是模拟按电源键触发Windows关机 ,即POWERDOWN事件,然后Windows关机,触发SHUTDOWN事件(guest为true)。Windows关机后,qemu进程不退出,然后由libvirt触发RESET事件(guest为false),触发虚拟机硬件reset,让虚拟机重新启动。
所以virsh reboot实际上是通过虚拟机关机,qemu进程不退出、虚拟机系统硬件reset的操作来实现的,对Guest来说其实就是关机再开机,并不是真正的重启。
如果Guest OS针对用户发起的关机和重启操作执行的关机过程有所区别的话,virsh reboot操作其实就无法实现Guest OS的重启操作。以Win10 Guest OS为例,若Win10开启了快速启动,在Windows的关机过程中,Windows的内核其实是进行了休眠而不是关闭,所以系统重启后,会发现,Windows的内核运行时间并没有清零,而是累加了上次关机前的运行时间。
7. 总结
综上所述,QEMU的虚拟机内关机是通过模拟ACPI电源控制寄存器(PM1a/b_CNT)实现的,虚拟机外的关机则是通过模拟电源按键实现的。虚拟机内的重启操作,是通过模拟Reset控制寄存器实现的。但是,虚拟机外重启操作并没有直接的模拟,主要是因为Guest OS对外并没有重启操作的接口,并且重启操作过程中,Guest OS是需要保存自己的软件状态的,什么时候执行底层的硬件reset操作,QEMU本身并不知道(Guest OS)并不会提前告知硬件,或者对外发出事件。所以虚拟机外并没有真正的重启操作命令,而是通过虚拟机关机、再启动实现的,这时候就需要具体的管理程序,如libvirt,对虚拟机的关机、启动的过程进行跟踪和监控,并且这种实现方式在少数场景(如Win10开启了快速启动)将不适用。