热迁移save分析

 

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日上传

猜你喜欢

转载自blog.csdn.net/tdaajames/article/details/107516472