本文由作者朱益军授权网易云社区发布。

简介

在实际业务中,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();
    }

模块参数说明

Module Parameter Description default Value
halt_poll_ns The global max polling interval which defines the ceiling value which defines the ceiling value which defines the ceiling value of the polling interval for each vcpu. KVM_HALT_POLL_NS_DEFAULT (per arch value)
halt_poll_ns_grow The value by which the halt polling interval is multiplied polling interval is multiplied polling interval is multiplied in the grow_halt_poll_ns() function. 2
halt_poll_ns_shrink The value by which the halt polling interval is divided in the shrink_halt_poll_ns() function. 0

kvm用这3个参数来动态调整最大halt polling时长。debugfs下/sys/module/kvm/parameters/存放着这3个模块参数的默认值。X86架构下,KVM_HALT_POLL_NS_DEFAULT默认值为400000。 grow一次,值乘以halt_poll_ns_grow:

static void grow_halt_poll_ns(struct kvm_vcpu *vcpu){
    unsigned int old, val, grow;     old = val = vcpu->halt_poll_ns;
    grow = READ_ONCE(halt_poll_ns_grow);    /* 10us base */
    if (val == 0 && grow)
        val = 10000;    else
        val *= grow;    if (val > halt_poll_ns)
        val = halt_poll_ns;     vcpu->halt_poll_ns = val;
    trace_kvm_halt_poll_ns_grow(vcpu->vcpu_id, val, old);
}

shrink一次,值除以halt_poll_ns_shrink,当前系统该参数为0,说明在设定的polling时长下虚拟机未被唤醒,那么下一次polling时长降到基准值10us:

static void shrink_halt_poll_ns(struct kvm_vcpu *vcpu){
    unsigned int old, val, shrink;     old = val = vcpu->halt_poll_ns;
    shrink = READ_ONCE(halt_poll_ns_shrink);    if (shrink == 0)
        val = 0;    else
        val /= shrink;     vcpu->halt_poll_ns = val;
    trace_kvm_halt_poll_ns_shrink(vcpu->vcpu_id, val, old);
}

值得注意几点

  1. 该机制有可能导致物理CPU实际空闲的情况下占用率表现为100%。因为如果guest上业务模型是隔一段时间被唤醒一次来处理很少量的流量,并且这个时间间隔比kvm halt_poll_ns短,那么host将poll整个虚拟机的block时间,cpu占用率也会冲上100%。

  2. halt polling是电源能耗和业务时延的一个权衡。为了减少进入guest的时延,idle cpu时间转换为host kernel时间。

  3. 该机制只有在CPU上没有其他running任务的情况得以应用,不然polling动作被立马终止,唤醒调度器,调度其他进程。

延伸阅读

业界针对虚拟机idle这个课题有比较多的研究,因为它带来了比较大的overhead。主要可以归结为以下几种:

  1. idle=poll,即把虚拟机idle时一直polling,空转,不退出。这样不利于物理CPU超线程的发挥。

  2. 阿里提出guest里面提供halt polling机制,即在VM退出前先等会儿,这样可以减少VM退出次数。 --- 优点:性能较社区halt polling机制好;缺点:需要修改guest内核;状态:社区尚未接收 https://lwn.net/Articles/732236/

  3. AWS及腾讯考虑guest HLT指令不退出。 --- 优点:性能较阿里好;缺点:只适用于vcpu独占物理核场景;状态:社区讨论中,比较大可能被接受。https://patchwork.kernel.org/patch/10199917/

  4. idle等于mwait及mwait不退出。 --- 需要高版本kvm及高版本guest内核支持。

总结

如何高效地处理虚拟机idle是提升虚拟化性能的研究点。针对不同的业务模型,采用不同的机制最大化业务性能。后续将在考拉及其他业务上逐个验证这些方案。

参考文档

  1. https://www.linux-kvm.org/images/2/27/Kvm-forum-2013-idle-latency.pdf

  2. http://events17.linuxfoundation.org/sites/events/files/slides/Message%20Passing%20Workloads%20in%20KVM%20%28SLIDES%29.pdf

  3. http://events17.linuxfoundation.org/sites/events/files/slides/KVM%20performance%20tuning%20on%20Alibaba%20Cloud.pdf

  4. https://www.kernel.org/doc/Documentation/virtual/kvm/halt-polling.txt

免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐

更多网易技术、产品、运营经验分享请访问网易云社区

相关文章:
【推荐】 收集、分析线上日志数据实战——ELK
【推荐】 CEF与代理

KVM halt-polling机制分析的更多相关文章

  1. Linux内核抢占实现机制分析【转】

    Linux内核抢占实现机制分析 转自:http://blog.chinaunix.net/uid-24227137-id-3050754.html [摘要]本文详解了Linux内核抢占实现机制.首先介 ...

  2. Linux kernel workqueue机制分析

    Linux kernel workqueue机制分析 在内核编程中,workqueue机制是最常用的异步处理方式.本文主要基于linux kernel 3.10.108的workqueue文档分析其基 ...

  3. Linux mips64r2 PCI中断路由机制分析

    Linux mips64r2 PCI中断路由机制分析 本文主要分析mips64r2 PCI设备中断路由原理和irq号分配实现方法,并尝试回答如下问题: PCI设备驱动中断注册(request_irq) ...

  4. IOS Table中Cell的重用reuse机制分析

    IOS Table中Cell的重用reuse机制分析 技术交流新QQ群:414971585 创建UITableViewController子类的实例后,IDE生成的代码中有如下段落: - (UITab ...

  5. 您还有心跳吗?超时机制分析(java)

    注:本人是原作者,首发于并发编程网(您还有心跳吗?超时机制分析),此文结合那里的留言作了一些修改. 问题描述 在C/S模式中,有时我们会长时间保持一个连接,以避免频繁地建立连接,但同时,一般会有一个超 ...

  6. Java 类反射机制分析

    Java 类反射机制分析 一.反射的概念及在Java中的类反射 反射主要是指程序可以访问.检测和修改它本身状态或行为的一种能力.在计算机科学领域,反射是一类应用,它们能够自描述和自控制.这类应用通过某 ...

  7. Linux信号(signal) 机制分析

    Linux信号(signal) 机制分析 [摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核 ...

  8. Java 动态代理机制分析及扩展

    Java 动态代理机制分析及扩展,第 1 部分 王 忠平, 软件工程师, IBM 何 平, 软件工程师, IBM 简介: 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟 ...

  9. Android内存机制分析1——了解Android堆和栈

    //----------------------------------------------------------------------------------- Android内存机制分析1 ...

随机推荐

  1. datagridview 如何禁止行被选中

    如题,如何规定特定的行,光标不能定位,也不能被选中,就好想Button中的Enable属性那样,变灰,而且点击也没有反应那种,这样的效果,如何实现. datagridview [解决办法]dataGr ...

  2. Android 获取ROOT权限原理解析

    一. 概述 本文介绍了android中获取root权限的方法以及原理,让大家对android玩家中常说的“越狱”有一个更深层次的认识. 二. Root的介绍 1.       Root 的目的 可以让 ...

  3. Linux 添加硬盘

    一.简介 本文介绍为Linux 添加硬盘的基本方法,同时适用于为虚拟机添加硬盘的情况.   二.添加小于2T的硬盘 1)分区 fdisk /dev/hda 2)建立文件系统 3)设置开机自动挂载磁盘 ...

  4. Laravel 根据任务的性质和要求决定处理的方式(Cron or Job)

    1 前言 一般地,我们在应用的开发中,会碰到各种各样的任务解决需求.我的原则是,选择合适的方法做正确的事. 2 任务分类 在开发中, 一般会有以下几种性质的任务. 2.1 实时任务 一般是指,任务间的 ...

  5. 3. Install Spring-Tool-Suite & TestNG

    1.Install Spring-Tool-Suite 2.Install TestNG

  6. 设计资源:三个精美APP原型例子下载

    原型设计是整个产品生产过程中不可或缺的一环,无论你是移动端UI设计师或是网页设计师,原型设计都会让整个设计过程更加轻松.原型是产品概念的具象化,它让每个项目参与者都能查看并提出意见以便在产品发布前日臻 ...

  7. 【文件下载】Java下载文件的几种方式

    [文件下载]Java下载文件的几种方式  摘自:https://www.cnblogs.com/sunny3096/p/8204291.html 1.以流的方式下载. public HttpServl ...

  8. Perl的调试模式熟悉和应用

    perl -d file.pl perl -c file.pl DB<1> hList/search source lines:               Control script ...

  9. jQuery 插件使用记录

    Validate 表单验证 ver 1.6 浏览更多 默认情况下,当表单 submit 时,那些验证不通过的 field 旁边会出现错误消息提示,有时很方便,但有时看起来很不美观.可以关闭此消息提示. ...

  10. sys.argv和getopt.getopt()的用法

    1.sys.argv Python中sys.argv是命令行参数从程序外部传值的的一种途径,它是一个列表,列表元素是我们想传进去的的新参数,所以可以用索引sys.argv[]来获得想要的值.因为一个写 ...