1. xend部分:
首先,在XendCheckpoint.py中执行save函数,保存虚拟机的内存信息,工作状态等,为迁移到目的端恢复工作做准备。
cmd = [xen.util.auxbin.pathTo(XC_SAVE), str(fd), str(dominfo.getDomid()), "0", "0", str(int(live) | (int(hvm) << 2)) ] … forkHelper(cmd, fd, saveInputHandler, False, domid = dominfo.getDomid()) |
这个cmd对应的是xc_save.c中的main函数(xc_save.c文件会生成一个xc_save的可执行文件)。由forkHelper作为入口调用,在forkHelper中有如下循环:
while 1: line = child.fromchild.readline() if line == "": break else: line = line.rstrip() log.debug('%s', line) inputHandler(line, child.tochild) |
forkHelper会把xc_save这个可执行文件作为自己的一个线程来启动,并循环检测xc_save的标准输出,line就是用来存储xc_save的标准输出的。如果line不为空,则调用saveInputHandler来处理。实际上就相当于下图的关系:
forkHelper启动并检测xc_save的输出
xc_save -> forkHelper
下面我们看看xc_save里面都做了些什么。
首先它会对suspend的事件通道进行初始化。
si.suspend_evtchn = xc_suspend_evtchn_init(si.xc_fd, si.xce, si.domid, port); |
如果初始化失败,会返回-1.
接下来,会调用最主要的部分xc_domain_save进行suspend操作和内存迭代拷贝。
ret = xc_domain_save(si.xc_fd, io_fd, si.domid, maxit, max_f, si.flags, &callbacks, !!(si.flags & XCFLAGS_HVM), &switch_qemu_logdirty); |
在xc_domain_save中调用suspend_and_state来完成suspend的操作。
if ( suspend_and_state(callbacks->suspend, callbacks->data, xc_handle, io_fd, dom, &info) ) { ERROR("Domain appears not to have suspended"); goto out; } |
suspend函数是作为函数指针传进去的,它的本体位于xc_save.c中。
if ((sx_state == 0) && (si.suspend_evtchn >= 0)) return evtchn_suspend();
return compat_suspend(); |
可以看到,有两条通道都可以完成suspend操作。第一条是evtchn_suspend(),第二条是compat_suspend()。在压力大的情况下,通常获得事件通道失败,就会走compat_suspend通道;正常情况下,压力不大,都能获得事件通道,会走evtchn_suspend通道。在evtchn_suspend通道中会打印标准输出suspended,在compat_suspend通道中会打印标准输出suspend。
forkHelper检测到有输出就会调用saveInputHandler进行处理:
if line == "suspend": log.debug("Suspending %d ...", dominfo.getDomid()) dominfo.shutdown('suspend') dominfo.waitForSuspend() if line in ('suspend', 'suspended'): dominfo.migrateDevices(network, dst, DEV_MIGRATE_STEP2, domain_name) log.info("Domain %d suspended.", dominfo.getDomid()) dominfo.migrateDevices(network, dst, DEV_MIGRATE_STEP3, domain_name) if hvm: dominfo.image.saveDeviceModel() |
接下来对两种suspend通道进行处理,evtchn_suspend通道会直接从第二个if往下走,compat_suspend通道会从第一个if往下走。
首先,分析第二种情况,走入shutdown函数:
self.storeDom("control/shutdown", reason) |
这是一个将suspend写入xenstore的control/shutdown键值的通信过程,下面pvdriver会从xenstore里检测到这个键值,进行对虚拟机的suspend操作。
在执行完shutdown函数,紧接着就是waitForSuspend,来检测虚拟机状态,判定它是否已经被成功suspend掉。
while self._stateGet() in (DOM_STATE_RUNNING,DOM_STATE_PAUSED): self.state_updated.wait(1.0) if state == "suspend": if nr_tries == 0: msg = ('Timeout waiting for domain %s to suspend' |
通过self._stateGet()来获得虚拟机的状态信息,它最终是通过超调用,从hypervisor里直接读取虚拟机状态的:_stateGet→dom_get→xc.domain_getinfo
首先,分析第二种情况,走入shutdown函数:
self.storeDom("control/shutdown", reason) |
这是一个将suspend写入xenstore的control/shutdown键值的通信过程,下面pvdriver会从xenstore里检测到这个键值,进行对虚拟机的suspend操作。 在执行完shutdown函数,紧接着就是waitForSuspend,来检测虚拟机状态,判定它是否已经被成功suspend掉。
while self._stateGet() in (DOM_STATE_RUNNING,DOM_STATE_PAUSED): self.state_updated.wait(1.0) if state == "suspend": if nr_tries == 0: msg = ('Timeout waiting for domain %s to suspend' |
通过self._stateGet()来获得虚拟机的状态信息,它最终是通过超调用,从hypervisor里直接读取虚拟机状态的:_stateGet→dom_get→xc.domain_getinfo
2. pvdriver部分
pvdriver中在xenpci_do.c文件中,有一个XenPci_EvtDeviceD0EntryPostInterruptsEnabled函数,该函数对control/shutdown这个键值添加了watch一个监控。
/* 热迁移时在control/shutdown中写入suspend也触发这个回调函数 */ response = XenBus_AddWatch(xpdd, XBT_NIL, SHUTDOWN_PATH, XenPci_ShutdownHandler, device); |
当watch检测到control/shutdown中有键值写入,就会调用XenPci_ShutdownHandler函数。
res = XenBus_Read(xpdd, XBT_NIL, SHUTDOWN_PATH, &value); if (res) { KdPrint(("Error reading shutdown path - %s\n", res)); XenPci_FreeMem(res); FUNCTION_EXIT(); return; } |
XenPci_ShutdownHandler函数中会读取control/shutdown中的值,如果没有读到,就跳出函数。在evtchn_suspend通道时,是读不到这个值的,会由pvdriver中的XenPci_SuspendEvtDpc函数来处理suspend操作;走compat_suspend通道时,会读到该键值为suspend,继续向下执行:
if (strlen(value) && strcmp(value, "suspend") == 0) { WDF_WORKITEM_CONFIG_INIT(&workitem_config, XenPci_SuspendResume); } |
在XenPci_SuspendResume中会把所有的子设备都suspend掉:
while ((status = WdfChildListRetrieveNextDevice(child_list, …… { KdPrint((__DRIVER_NAME " Suspending child\n")); XenPci_Pdo_Suspend(child_device); } |
DomU内部suspend流程
XenPci_EvtDeviceD0EntryPostInterruptsEnabled(WDFDEVICE device, WDF_POWER_DEVICE_STATE previous_state)
调用
XenPci_ConnectSuspendEvt(xpdd); //连接Suspend事件通道
调用
EvtChn_BindDpc(xpdd, xpdd->suspend_evtchn, XenPci_SuspendEvtDpc, xpdd->wdf_device);//绑定事件处理函数
XenPci_SuspendEvtDpc
调用
XenPci_SuspendResume //将设备挂起
3. 超时
第一个部分waitForSuspend中,nr_tries设定超时时限,循环判断虚拟机状态,如果为running或paused就继续判断,直至超时;
第二个部分是在signalDeviceModel中,读取xenstore里的state时,state不为pause,读取判断操作超过10s,则报错。
while state != ret: state = xstransact.Read("/local/domain/0/device-model/%i/state" % self.vm.getDomid()) time.sleep(0.1) count += 1 if count > 100: raise VmError('Timed out waiting for device model action') |
第三个部分是等待qemu信号超过5s,报错。
tv.tv_sec = 5; tv.tv_usec = 0; FD_ZERO(&fdset); FD_SET(xs_fileno(xs), &fdset);
if ((select(xs_fileno(xs) + 1, &fdset, NULL, NULL, &tv)) != 1) errx(1, "timed out waiting for qemu logdirty response.\n"); |
4. 热迁移流程
server.xend.domain.migrate(dom, dst, opts.vals.live,
opts.vals.port,
opts.vals.node,
opts.vals.ssl,
opts.vals.change_home_server)
main→vshParseArgv→vshCommandParse→vshCmaddefSearch/vshCmddefGetOption/vshCmddefGetData
main→vshInit
main→vshCommandRun
commands:
{"migrate", cmdMigrate, opts_migrate, info_migrate}
cmdMigrate→virDomainMigrateToURI→virDomainMigratePeer2Peer→domainMigratePerform→xenUnifiedDomainMigratePerform→xenDaemonDomainMigratePerform→xend_op
2013年5月20日上传