qemu虚拟机的关闭方式主要包括如下几种方式:
system_powerdown是qemu monitor中支持的一个命令。
qemu进程的主循环位于vl.c:main_loop(),在主循环会循环调用main_loop_should_exit()判断是否结束主循环。
main_loog_should_exit()函数中会通过qemu_powerdown_requested()函数判断qemu虚拟机是否接收到了powerdown的请求,如果有该请求,则调用qemu_system_powerdown(),该函数先发出POWERDOWN的事件,然后通知注册到powerdown的notifier函数
不同的主板,会注册不同的powerdown notifier
以piix4为例,在piix4_pm_realize函数中,会将piix4_pm_powerdwon_req()这个notify handler注册到qemu虚拟机的powerdown notifier中。
该notifier handler主要是调用acpi_pm1_evt_power_down()函数,在该函数中,会检测在该虚拟机主板上,电源键(Power Button)是否被使能,如果被使能了,则模拟该电源键被按下,发送SCI(System Control Interrupt)中断,触发Guest OS进行关机。
所以qemu monitor的system_powerdown命令本质上是模拟电源键被按下触发SCI中断的动作。至于Guest OS是否会关机,则由Guest OS的行为配置决定的,如在Windows中,如果在电源设置中,将Windows的电源按键设置为不响应,则发送system_powrdown命令后,Windows将不做响应,无法实现虚拟机关机,除非Windows中将电源键功能设置为关机。
virsh shutdown命令会调用到libvirt的virDomainShutdown()函数,该函数会继续调用不同类型hypervisor的shutdown函数,如对于qemu而言,它会调用到qemu dirver对应的domainShutdown,即qemuDomainShutdown(),在该函数中,会执行如下操作:
所以,在不用Qemu Guest Agent的情况下,virsh shutdown命令其实就对应到qemu monitor中的system_powerdown命令,只是该命令由libvirt发送。
在虚拟机内使用Guest OS自带的关机按钮/功能进行关机,以Ubuntu为例,通过systemctl poweroff命令对Ubuntu进行关机,该命令会调用到kernel/power/poweroff.c文件中定义个poweroff_work对应的工作,该工作主要是执行do_poweroff()对系统进行关机。
kernel/power/poweroff.c:do_poweroff() -> kernel_power_off() ,该函数关闭所有的用户程序和设备,最后调用machine_power_off()函数。
machine_power_off() -> power_off() -> native_machine_poweroff() -> pm_power_off() 若该Ubuntu运行的虚拟机支持ACPI标准,则pm_power_off将指向acpi_power_off()函数。
acpi_power_off() -> acpi_enter_sleep_state()
该函数将会往ACPI标准中规定的寄存器PM1 Control Registers Fixed Hardware Feature Control Bits中的SLP_TYPE和SLP_EN域写相应的数据,让虚拟机主板进入到指定的睡眠状态,即ACPI标准中定义的S0 ~ S5状态。
而在qemu模拟器方面,在hw/acpi/core.c中会对PM1 Control Registers Fixed Hardware Feature Control Bits的写进行监控,当向SLY_TYP和SLY_EN写特定值的时候,会触发qemu调用qemu_system_shutdown_request()发出shutdown的请求。
qemu进程的主要循环函数main_loop()中,会循环调用main_loop_should_exit()函数,该函数会检测系统中是否有shutdown的请求发出。若有shutdown的请求,则qemu进程退出主循环,结束并退出qemu虚拟机。
qemu monitor的quit命令让qemu进程直接退出。
直接在qemu monitor中输入quit命令,将触发qemu虚拟机直接退出,Guest OS完全不知道虚拟机即将关闭,所以对Guest OS来说,将会直接闪退,类似物理机的直接掉电。
quit命令将调度到qemu的hmp_quit()函数,该函数将会告知qemu进程在虚拟机关闭的时候退出,同时发出shutdown的请求,qemu进程的主循环main_loop()在执行main_loop_should_exit()检查是否需要退出主循环的时候,将检测到该事件,然后直接退出。
libvirt提供的virsh destroy命令,将调用到qemu driver的qemuDomainDestroy() -> qemuDomainDestroyFlags() -> qemuProcessStop() -> qemuProcessKill()接口,将qemu进程关掉。
qemuProcessKill()中,将会向qemu进程发送SIGKILL或SIGTERM信号,让qemu进程退出。
在qemu这边,qemu的主进程中,会通过os_setup_signal_handling()设置对信号的响应。
termsig_handler() -> qemu_system_killed(),在qemu_system_killed()中,将会生成shutdown的请求,并且shutdown请求的原因是host signal。该请求将会让qemu进程的主循环退出,不等Guest OS准备好关机。
在主机端通过Ctrl+C组合键,让qemu进程退出,其实是向qemu进程发送SIGINT信号,该信号的处理方式和SIGTERM是一样的,也就是最后调用到qemu_system_killed()函数,发出shutdown请求,让qemu主进程退出,同时记录下shutdown的原因为host signal。
在主机端使用kill -9 pid或者pkill -9 pname的方式,也是向主机进程发送SIGKILL信号,让进程退出的。