首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

KVM halt-polling机制分析(1)

KVM halt-polling机制分析(1)

简介

在实际业务中,guest执行HLT指令是导致虚拟化overhead的一个重要原因。如[1].

KVM halt polling特性就是为了解决这一个问题被引入的,它在Linux 4.3-rc1被合入主干内核,其基本原理是当guest idle发生vm-exit时,host 继续polling一段时间,用于减少guest的业务时延。进一步讲,在vcpu进入idle之后,guest内核默认处理是执行HLT指令,就会发生vm-exit,host kernel并不马上让出物理核给调度器,而是poll一段时间,若guest在这段时间内被唤醒,便可以马上调度回该vcpu线程继续运行。

polling机制带来时延上的降低,至少是一个线程调度周期,通常是几微妙,但最终的性能提升是跟guest内业务模型相关的。如果在host kernel polling期间,没有唤醒事件发生或是运行队列里面其他任务变成runnable状态,那么调度器就会被唤醒去干其他任务的事。因此,halt polling机制对于那些在很短时间间隔就会被唤醒一次的业务特别有效。
代码流程

guest执行HLT指令发生vm-exit后,kvm处理该异常,在kvm_emulate_halt处理最后调用kvm_vcpu_halt(vcpu)。

int kvm_vcpu_halt(struct kvm_vcpu *vcpu){
    ++vcpu->stat.halt_exits;    if (lapic_in_kernel(vcpu)) {
        vcpu->arch.mp_state = KVM_MP_STATE_HALTED;        return 1;
    } else {
        vcpu->run->exit_reason = KVM_EXIT_HLT;        return 0;
    }
}

将mp_state的值置为KVM_MP_STATE_HALTED,并返回1。

static int vcpu_run(struct kvm_vcpu *vcpu){    int r;    struct kvm *kvm = vcpu->kvm;

    vcpu->srcu_idx = srcu_read_lock(&kvm->srcu);    for (;;) {        if (kvm_vcpu_running(vcpu)) {
            r = vcpu_enter_guest(vcpu);
        } else {
            r = vcpu_block(kvm, vcpu);
        }        if (r <= 0)            break;        //省略
    }
}

由于kvm处理完halt异常后返回1,故主循环不退出,但在下一个循环时kvm_vcpu_running(vcpu)返回false,所以进入vcpu_block()分支,随机调用kvm_vcpu_block()。

通用的halt polling代码在virt/kvm/kvm_main.c文件中的额kvm_vcpu_block()函数中实现。

ktime_t stop = ktime_add_ns(ktime_get(), vcpu->halt_poll_ns);do {    /*
     * This sets KVM_REQ_UNHALT if an interrupt
     * arrives.
     */
    if (kvm_vcpu_check_block(vcpu) < 0) {
        ++vcpu->stat.halt_successful_poll;        if (!vcpu_valid_wakeup(vcpu))
            ++vcpu->stat.halt_poll_invalid;        goto out;
    }
    cur = ktime_get();
} while (single_task_running() && ktime_before(cur, stop));

情况一:如果当前物理核上没有其他task处于running状态,而且在polling时间间隔内,那么就一直等着,直到kvm_vcpu_check_block(vcpu) < 0,即vcpu等待的中断到达,便跳出循环。

out:
    block_ns = ktime_to_ns(cur) - ktime_to_ns(start);    if (!vcpu_valid_wakeup(vcpu))
        shrink_halt_poll_ns(vcpu);    else if (halt_poll_ns) {        if (block_ns <= vcpu->halt_poll_ns)
            ;        /* we had a long block, shrink polling */
        else if (vcpu->halt_poll_ns && block_ns > halt_poll_ns)
            shrink_halt_poll_ns(vcpu);        /* we had a short halt and our poll time is too small */
        else if (vcpu->halt_poll_ns < halt_poll_ns &&
            block_ns < halt_poll_ns)
            grow_halt_poll_ns(vcpu);
    } else
        vcpu->halt_poll_ns = 0;

    trace_kvm_vcpu_wakeup(block_ns, waited, vcpu_valid_wakeup(vcpu));
    kvm_arch_vcpu_block_finish(vcpu);

这段代码主要用于调整下一次polling的等待时间。若block_ns大于halt_poll_ns,即vcpu halt时间很短就被唤醒了,则把下一次的halt_poll_ns调长;否则,减短。

情况二:如果当前物理核上其他task变成running态,或polling时间到期,则唤醒调度器,调度其他任务,如下代码。

for (;;) {
        prepare_to_swait(&vcpu->wq, &wait, TASK_INTERRUPTIBLE);        if (kvm_vcpu_check_block(vcpu) < 0)            break;

        waited = true;
        schedule();
    }
返回列表