Libvirt的job机制3--基本原理

Job机制简介中介绍了在libvirt中调用接口时如何使用Job机制;

条件变量基本原理中介绍了条件变量的一些基本原理,这个是Job机制的基础。本文主要介绍Job机制中用到的一些函数、结构体等变量,分析其具体的原理和用法。

在Job机制简介中已经介绍了Job中condition的类型:分别是asynchronous, agent and normal.

参数变量定义:

qemuDomainJob

typedef enum {
    QEMU_JOB_NONE = 0,  /* Always set to 0 for easy if (jobActive) conditions */
    QEMU_JOB_QUERY,         /* Doesn't change any state */
    QEMU_JOB_DESTROY,       /* Destroys the domain (cannot be masked out) */
    QEMU_JOB_SUSPEND,       /* Suspends (stops vCPUs) the domain */
    QEMU_JOB_MODIFY,        /* May change state */
    QEMU_JOB_ABORT,         /* Abort current async job */
    QEMU_JOB_MIGRATION_OP,  /* Operation influencing outgoing migration */

    /* The following two items must always be the last items before JOB_LAST */
    QEMU_JOB_ASYNC,         /* Asynchronous job */
    QEMU_JOB_ASYNC_NESTED,  /* Normal job within an async job */

    QEMU_JOB_LAST
} qemuDomainJob

 qemuDomainAsyncJob

typedef enum {
    QEMU_ASYNC_JOB_NONE = 0,
    QEMU_ASYNC_JOB_MIGRATION_OUT,
    QEMU_ASYNC_JOB_MIGRATION_IN,
    QEMU_ASYNC_JOB_SAVE,
    QEMU_ASYNC_JOB_DUMP,
    QEMU_ASYNC_JOB_SNAPSHOT,
    QEMU_ASYNC_JOB_START,
    QEMU_ASYNC_JOB_BACKUP,

    QEMU_ASYNC_JOB_LAST
} qemuDomainAsyncJob

qemuDomainJobStatus

typedef enum {
    QEMU_DOMAIN_JOB_STATUS_NONE = 0,
    QEMU_DOMAIN_JOB_STATUS_ACTIVE,
    QEMU_DOMAIN_JOB_STATUS_MIGRATING,
    QEMU_DOMAIN_JOB_STATUS_QEMU_COMPLETED,
    QEMU_DOMAIN_JOB_STATUS_PAUSED,
    QEMU_DOMAIN_JOB_STATUS_POSTCOPY,
    QEMU_DOMAIN_JOB_STATUS_COMPLETED,
    QEMU_DOMAIN_JOB_STATUS_FAILED,
    QEMU_DOMAIN_JOB_STATUS_CANCELED,
} qemuDomainJobStatus;

Normal/Async/nested/agent之间在调用qemuDomainObjBeginJobInternal函数传参的区别,可以看出他们之间的不同之处:

qemuDomainObjBeginJobInternalqemuDomainJob jobqemuDomainAgentJob agentJobqemuDomainAsyncJob asyncJob
qemuDomainObjBeginJobjobQEMU_AGENT_JOB_NONEQEMU_ASYNC_JOB_NONE
qemuDomainObjBeginAgentJobQEMU_JOB_NONEagentJobQEMU_ASYNC_JOB_NONE
qemuDomainObjBeginAsyncJobQEMU_JOB_ASYNCQEMU_AGENT_JOB_NONEasyncJob
qemuDomainObjBeginNestedJobQEMU_JOB_ASYNC_NESTEDQEMU_AGENT_JOB_NONEQEMU_ASYNC_JOB_NONE

通过以上表格的对比,可以看到NormalJob, AgentJob,AsyncJob,NestedJob之间的区别;

qemuDomainObjBeginJobInternal函数详解

bool nested = job == QEMU_JOB_ASYNC_NESTED;
    bool async = job == QEMU_JOB_ASYNC;
	while (!nested && !qemuDomainNestedJobAllowed(&priv->job, job)) {
// while(!(nested || qemuDomainNestedJobAllowed(&priv->job, job)))
// 就是当前允许嵌套,或者发来的嵌套job,则就不用等待异步条件变量了
        if (nowait)
            goto cleanup;

        VIR_DEBUG("Waiting for async job (vm=%p name=%s)", obj, obj->def->name);
        if (virCondWaitUntil(&priv->job.asyncCond, &obj->parent.lock, then) < 0)
            goto error;
    }

    while (!qemuDomainObjCanSetJob(&priv->job, job, agentJob)) {
        if (nowait)
            goto cleanup;

        VIR_DEBUG("Waiting for job (vm=%p name=%s)", obj, obj->def->name);
        if (virCondWaitUntil(&priv->job.cond, &obj->parent.lock, then) < 0)
            goto error;
    }



    /* No job is active but a new async job could have been started while obj
     * was unlocked, so we need to recheck it. */
    if (!nested && !qemuDomainNestedJobAllowed(&priv->job, job))
        goto retry;

对其中的一个判断逻辑进行分析:

static bool
qemuDomainNestedJobAllowed(qemuDomainJobObjPtr jobs, qemuDomainJob newJob)
{

/* 这个函数的名字有点让人容易差生歧义,就是作为是否等待异步信号的条件之一,这里直接命名为允许nested job。
1. 如果当前的jobs->asyncJob为None,说明前面没有设置过任何的asyncJob,就不需要等待异步信号量
 2. 如果当前的asyncJob不为None,但是newJob为None, 说明当前的传入的job为None; 只发送agent的时候,newJob才会设置为None,这种情况也可以不等待异步信号量。
 3. 当前的asyncJob不为None,newJob也不为QEMU_JOB_NONE, 但是job->mask & JOB_MASK(newJob)不为0,说明当前是存在异步的,newJob不为None,说明是嵌套Job,jobs->mask和当前的newJob相与不为0,说明当前的newJob是异步Job所允许的类型;只有asyncjob才会设置mask,其他类型的job都是None。所以这种情况下也是不等待信号量的。
 以上三个条件是依次判断的,层层递进的;
*/
    return !jobs->asyncJob ||
           newJob == QEMU_JOB_NONE ||
           (jobs->mask & JOB_MASK(newJob)) != 0;
}
static bool
qemuDomainObjCanSetJob(qemuDomainJobObjPtr job,
                       qemuDomainJob newJob,
                       qemuDomainAgentJob newAgentJob)
{
	/* 
这个逻辑判断比较复杂,可以简单化,就是看成return 0,即需要等待job的condition。
	1. 如果job->active不为None,并且newJob不为None,说明前面有一个normal job,现在又来一个normal job,则需要等待condition
2. 如果job->agentActive不为None,newAgentJob不为None,说明当前已经有一个agent job,又来一个agent job需要等待condition
3. 同时存在一个normal job和agent job的情况是不存在的
	*/    
return ((newJob == QEMU_JOB_NONE ||
             job->active == QEMU_JOB_NONE) &&
            (newAgentJob == QEMU_AGENT_JOB_NONE ||
             job->agentActive == QEMU_AGENT_JOB_NONE));
}

以上就是当前对Job机制的理解。

还有几个疑问没有解决。

1. qemuDomainObjBeginJobInternal函数中的go retry是什么情况?

2. 等待条件变量的时候,必须有别的地方发送信号,才会停止阻塞,假设第一次调用,如果发生阻塞,那么就永远没有信号发送了。这也是为什么在等待条件变量的时候,会进行一次判断。第一次进入的时候,while的条件判断应该不满足,应该不会出现等待条件变量的情况。

3. 什么情况下会出现获取条件变量的情况?有没有实际的场景?

因为从API获取vm的时候,会首先获得vm中的锁,这个锁会在等待条件变量的时候释放,已经API调用完成时释放。那么问题来了,假设只有一个API调用,那么根本不会进入条件等待的情况;加入有多个API调用同时到来,由于vm锁的原因,肯定会只有一个API在执行,那么则不会获取条件变量;不会获取条件变量,则不会进行阻塞;不会阻塞,则不会释放锁,所以另一个API调用必须等待上一个API调用完成之后,才能获取vm的锁,向下运行。

后面将针对异步和嵌套Job进行具体分析。

virsh destroy vm
qemuDomainObjBeginJobInternal:839 : Starting job: job=destroy agentJob=none asyncJob=none (vm=0x7f41e42a42e0 name=centos, current job=none agentJob=none async=none)
qemuDomainObjBeginJobInternal:886 : Started job: destroy (async=none vm=0x7f41e42a42e0 name=centos)

virsh suspend vm
qemuDomainObjBeginJobInternal:839 : Starting job: job=modify agentJob=none asyncJob=none (vm=0x7f41e42a42e0 name=centos, current job=none agentJob=none async=none)
qemuDomainObjBeginJobInternal:886 : Started job: modify (async=none vm=0x7f41e42a42e0 name=centos)

virsh resume vm
qemuDomainObjBeginJobInternal:839 : Starting job: job=suspend agentJob=none asyncJob=none (vm=0x7f41e42a42e0 name=centos, current job=none agentJob=none async=none)
qemuDomainObjBeginJobInternal:886 : Started job: suspend (async=none vm=0x7f41e42a42e0 name=centos)

virsh save vm mem.save
Starting job: job=none agentJob=none asyncJob=save (vm=0x7f41e42a42e0 name=centos, current job=none agentJob=none async=none)
qemuDomainObjBeginJobInternal:894 : Started async job: save (vm=0x7f41e42a42e0 name=centos)
qemuDomainObjBeginJobInternal:839 : Starting job: job=async nested agentJob=none asyncJob=none (vm=0x7f41e42a42e0 name=centos, current job=none agentJob=none async=save)
qemuDomainObjBeginJobInternal:886 : Started job: async nested (async=save vm=0x7f41e42a42e0 name=centos)
qemuDomainObjBeginJobInternal:839 : Starting job: job=async nested agentJob=none asyncJob=none (vm=0x7f41e42a42e0 name=centos, current job=none agentJob=none async=save)
qemuDomainObjBeginJobInternal:886 : Started job: async nested (async=save vm=0x7f41e42a42e0 name=centos)
qemuDomainObjBeginJobInternal:839 : Starting job: job=async nested agentJob=none asyncJob=none (vm=0x7f41e42a42e0 name=centos, current job=none agentJob=none async=save)

可以看到virsh destroy/suspend/resume vm,其job分别是destroy/suspend/modify,可以看到并未产生条件变量阻塞的情况;

virsh save vm mem.save,可以看到其asyncJob是save,job和agentJob是None;后面再进行其他操作的时候,传入的job是async nested,这个时候的asyncJob一直是save。因此这些操作并未出现条件变量会阻塞的场景

那么问题来了,设计Job架构的初衷是为了什么呢?主要是解决什么问题而引入的呢?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值