本人目前就职于烽火集成,从事云计算产品架构设计相关工作,长期专注于内核、虚拟化、分布式、云计算等方向。
技术交流请联系:xiaoding@fiberhome.com
电源管理是在学习虚拟化时候比较容易被忽略的一个问题。
最近在碰到一个问题,如何区分,虚拟机是从外部destroy掉还是用户内部shutdown的行为。
所以顺便好好研究了一下VM电源管理这一块。
首先简单描述一下,linux执行poweroff会做的事情。
当执行poweroff后,会执行系统调用kernel_power_off,然后根据不同架构来调用相关的
poweroff实现。
kernel/sys.c
void kernel_power_off ( void ) { kernel_shutdown_prepare ( SYSTEM_POWER_OFF ); if ( pm_power_off_prepare ) pm_power_off_prepare (); migrate_to_reboot_cpu (); syscore_shutdown (); printk ( KERN_EMERG "Power down. \n " ); kmsg_dump ( KMSG_DUMP_POWEROFF ); machine_power_off (); }
arch/x86/kernel/reboot.c
struct machine_ops machine_ops = { . power_off = native_machine_power_off , . shutdown = native_machine_shutdown , . emergency_restart = native_machine_emergency_restart , . restart = native_machine_restart , . halt = native_machine_halt , #ifdef CONFIG_KEXEC . crash_shutdown = native_machine_crash_shutdown , #endif };
在现在计算机中,电源管理交给了APM和ACPI,因为APM有着天然缺陷,所以现在一般都会使用ACPI。
使用ACPI需要在kernel中开启选项。值得一提的是APM和ACPI只有一个会生效。
在我测试的发行版中,redhat7,redhat6 已结sles11sp3中,kernel都只开启了ACPI,而没有编译APM,
所以如果禁止了ACPI选项,就会出现没有电源管理驱动加载。
好了,先来看看kvm下的电源管理。
kvm使用qemu来作为设备模拟,所以kvm直接使用了qemu中piix4中初始化的ACPI模块。
hw/i386/pc_piix.c
pc_init1
if ( pci_enabled && acpi_enabled ) { DeviceState * piix4_pm ; I2CBus * smbus ; smi_irq = qemu_allocate_irqs ( pc_acpi_smi_interrupt , first_cpu , 1 ); /* TODO: Populate SPD eeprom data. */ smbus = piix4_pm_init ( pci_bus , piix3_devfn + 3 , 0xb100 , gsi [ 9 ], * smi_irq , kvm_enabled (), fw_cfg , & piix4_pm ); smbus_eeprom_init ( smbus , 8 , NULL , 0 ); object_property_add_link ( OBJECT ( machine ), PC_MACHINE_ACPI_DEVICE_PROP , TYPE_HOTPLUG_HANDLER , ( Object ** ) & pc_machine -> acpi_dev , object_property_allow_set_link , OBJ_PROP_LINK_UNREF_ON_RELEASE , & error_abort ); object_property_set_link ( OBJECT ( machine ), OBJECT ( piix4_pm ), PC_MACHINE_ACPI_DEVICE_PROP , & error_abort ); }
可以看到,如果开启了ACPI,则会触发piix4_pm 这个芯片的初始化。
这个芯片在
hw/acpi/piix4.c 中。
这里就不细看了,只需要查看,相关寄存器读写以后会有什么状态触发。
在hw/acpi/core.c 中可以看到在这个acpi-cnt的io空间,如果进行write操作,就会
触发qemu_system_shutdown_request函数。这个函数,就会发起qemu退出信号。然后然后整个
qemu进程就退出了。
memory_region_init_io ( & ar -> pm1 . cnt . io , memory_region_owner ( parent ), & acpi_pm_cnt_ops , ar , "acpi-cnt" , 2 ); static const MemoryRegionOps acpi_pm_cnt_ops = { . read = acpi_pm_cnt_read , . write = acpi_pm_cnt_write , . valid . min_access_size = 2 , . valid . max_access_size = 2 , . endianness = DEVICE_LITTLE_ENDIAN , }; static void acpi_pm1_cnt_write ( ACPIREGS * ar , uint16_t val ) { ar -> pm1 . cnt . cnt = val & ~ ( ACPI_BITMASK_SLEEP_ENABLE ); if ( val & ACPI_BITMASK_SLEEP_ENABLE ) { /* change suspend type */ uint16_t sus_typ = ( val >> 10 ) & 7 ; switch ( sus_typ ) { case 0 : /* soft power off */ qemu_system_shutdown_request (); break ; case 1 : qemu_system_suspend_request (); break ; default: if ( sus_typ == ar -> pm1 . cnt . s4_val ) { /* S4 request */ qapi_event_send_suspend_disk ( & error_abort ); qemu_system_shutdown_request (); } break ; } } }
以上就是如果用户从VM内部执行poweroff后,qemu中ACPi相关的操作。
刚才提到了其实kernel中ACPI是可选的。所以如果在grub时候,在启动项中加入acpi=off,系统
就不会初始化ACPI,那么会出现什么问题呢?
由于发行版中并没有编译APM的驱动,所以当关闭acpi后,执行poweroff,kvm的VM会打印一个
System Halt.然后卡住了。这也许是一个目前虚拟机中的bug。
下来看看xen的电源管理。
在xen中也使用qemu进行设备模拟,不可避免的也会初始化ACPI设备。
所以上面流程中提到的关于ACPI设备的操作在xen下都会出现。
但是在xen下,由于并不依赖与这个信号退出qemu。所以这个ACPI设备其实没有实际作用。
其实xen的电源管理是自己实现的。
这里我们主要看xl中电源管理的实现。
在xl执行create 命令后,并不会退出,
而是fork到后台后开始执行一个poll。
用来监听消息事件。
其中libxl_evenable_domain_death 注册了回调函数,来监听虚拟机退出事件。
ret = libxl_evenable_domain_death ( ctx , domid , 0 , & deathw ); if ( ret ) goto out ; while ( 1 ) { libxl_event * event ; ret = domain_wait_event ( & event ); if ( ret ) goto out ; switch ( event -> type ) { case LIBXL_EVENT_TYPE_DOMAIN_SHUTDOWN : LOG ( "Domain %d has shut down, reason code %d 0x%x" , domid , event -> u . domain_shutdown . shutdown_reason , event -> u . domain_shutdown . shutdown_reason ); switch ( handle_domain_death ( & domid , event , ( uint8_t ** ) & config_data , & config_len , & d_config )) { case 2 : if ( ! preserve_domain ( & domid , event , & d_config )) { /* If we fail then exit leaving the old domain in place. */ ret = - 1 ; goto out ; }
可以看到 监听的key是@releaseDomain ,这个key是一个特殊key,只要有vm退出都会触发信息。
所以需要自己来判断是否是关心的VM触发了退出事件。
int libxl_evenable_domain_death ( libxl_ctx * ctx , uint32_t domid , libxl_ev_user user , libxl_evgen_domain_death ** evgen_out ) { GC_INIT ( ctx ); libxl_evgen_domain_death * evg , * evg_search ; int rc ; CTX_LOCK ; evg = malloc ( sizeof ( * evg )); if ( ! evg ) { rc = ERROR_NOMEM ; goto out ; } memset ( evg , 0 , sizeof ( * evg )); evg -> domid = domid ; evg -> user = user ; LIBXL_TAILQ_INSERT_SORTED ( & ctx -> death_list , entry , evg , evg_search , , evg -> domid > evg_search -> domid ); if ( ! libxl__ev_xswatch_isregistered ( & ctx -> death_watch )) { rc = libxl__ev_xswatch_register ( gc , & ctx -> death_watch , domain_death_xswatch_callback , "@releaseDomain" ); if ( rc ) { libxl__evdisable_domain_death ( gc , evg ); goto out ; } } * evgen_out = evg ; rc = 0 ; out: CTX_UNLOCK ; GC_FREE ; return rc ; };
因为xen下的VM并没有依赖ACPI来实现电源管理。所以无论ACPI是否生效,虚拟机都会成功
关闭,当然,如果没有开启ACPI,在VM关机时,还是会看到System Halt.