• Hypercall机制


    在Linux中,大家应该对syscall非常的了解和熟悉,其是用户态进入内核态的一种途径或者说是一种方式,完成了两个模式之间的切换;

    而在虚拟环境中,有没有一种类似于syscall这种方式,能够从no root模式切换到root模式呢?答案是肯定的,KVM提供了Hypercall机制,

    x86体系架构也有相关的指令支持。

    hypercall的处理

    当Guest发起一次hypercall后,VMM会接管到该call导致的VM Exit。

    static int (*const kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
        ......
        [EXIT_REASON_VMCALL]                  = handle_vmcall,
        ......
    }

    static inline long kvm_hypercall0(unsigned int nr)
    {
        long ret;
        asm volatile(KVM_HYPERCALL
                 : "=a"(ret)
                 : "a"(nr)
                 : "memory");
        return ret;
    }
    
    
    熟悉inline汇编的同学一看就明白,nr可以认为是系统调用号,放在AX寄存器中,返回值也是放在AX中。当Guest OS中调用该函数时,导致VM-EXIT,于是KVM内核模块开始接收到控制权:

    进入handle_vmcall()->kvm_emulate_hypercall()处理,过程非常简单:

    int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
    {
        unsigned long nr, a0, a1, a2, a3, ret;
        int r = 1;
    
        if (kvm_hv_hypercall_enabled(vcpu->kvm))
            return kvm_hv_hypercall(vcpu);
    
        nr = kvm_register_read(vcpu, VCPU_REGS_RAX);  //取出参数
        a0 = kvm_register_read(vcpu, VCPU_REGS_RBX);
        a1 = kvm_register_read(vcpu, VCPU_REGS_RCX);
        a2 = kvm_register_read(vcpu, VCPU_REGS_RDX);
        a3 = kvm_register_read(vcpu, VCPU_REGS_RSI);
    
        trace_kvm_hypercall(nr, a0, a1, a2, a3);
    
        if (!is_long_mode(vcpu)) {
            nr &= 0xFFFFFFFF;
            a0 &= 0xFFFFFFFF;
            a1 &= 0xFFFFFFFF;
            a2 &= 0xFFFFFFFF;
            a3 &= 0xFFFFFFFF;
        }
    
        if (kvm_x86_ops->get_cpl(vcpu) != 0) {
            ret = -KVM_EPERM;
            goto out;
        }
    
        switch (nr) {                 //根据调用号进行处理
        case KVM_HC_VAPIC_POLL_IRQ:
            ret = 0;
            break;
        case KVM_HC_KICK_CPU:
            kvm_pv_kick_cpu_op(vcpu->kvm, a0, a1);
            ret = 0;
            break;
        default:
            ret = -KVM_ENOSYS;
            break;
        }
    out:
        kvm_register_write(vcpu, VCPU_REGS_RAX, ret);   //将处理结果写入eax寄存器返回
        ++vcpu->stat.hypercalls;
        return r;
    }
    

    Conclusion

    整个过程非常简洁和简单,hypercall机制给了Guest能够主动进入VMM的一种方式。个人理解是:一旦Guest使用了这种机制,意味着Guest知道自己位于Guest中,也就意味着所谓的半虚拟化。

    static inline long kvm_hypercall4(unsigned int nr, unsigned long p1,unsigned long p2, unsigned long p3, unsigned long p4)
    {
        long ret;
        asm volatile(KVM_HYPERCALL
                 : "=a"(ret)
                 : "a"(nr), "b"(p1), "c"(p2), "d"(p3), "S"(p4)
                 : "memory"); return ret;
    }将由客户机调用该函数发生vmexit
     
    执行asm汇编代码时,首先将 "a"(nr), "b"(p1), "c"(p2), "d"(p3), "S"(p4)输入,当执行KVM_HYPERCALL时,#define KVM_HYPERCALL ".byte 0x0f,0x01,0xc1",表示该指令序列为0F01C1,此时VMM根据vmx.c中的kvm_vmx_exit_handlers中将会调用handle_vmcall,调用kvm_emulate_hypercall进行相应操作,并将结果返回给RAX寄存器,并返回"=a"(ret)。hypercall是同步执行的,如果host中kvm_emulate_hypercall不返回(比如睡眠10秒),那么guest中kvm_hypercall也不会返回,直到host里睡眠结束。
     
    如果想要在非根模式下运行的客户机的每一个IO指令都产生VM-Exit,那么可以将Use I/O bitmaps 比特位所在字段设置为0,同时将Unconditional I/O exiting字段设为1即可。这样的话在客户机中运行的每一个IO指令(IN, INS, INSB, etc, etc...)都将导致VM-Exit从而陷入到VMM中。但是如果我们将Use I/O bitmaps字段置1,则Unconditional I/O exiting就无效了。此时在客户机中运行的IO指令是否陷入到VMM中由IO位图(bitmap)来指定:如果IO指令访问的某一端口地址在IO bitmap中对应位为1,则该条指令导致VM-Exit,否则意味着该端口可以被客户机直接所访问而无需VMM的介入。
    在vmx_init中,将vmx_io_bitmap_a设置为0xff全1(memset(vmx_io_bitmap_a, 0xff, PAGE_SIZE)),在vmx_vcpu_setup中将vmx_io_bitmap_a读入IO_BITMAP_A(vmcs_write64(IO_BITMAP_A, __pa(vmx_io_bitmap_a))),则将端口地址对应IO bitmap位设为1,执行该指令将导师vm-exit。
     
    host对于即将到来的根模式非根模式切换所做的准备,vmx_create_vcpu->vmx_vcpu_setup->vmx_set_constant_host_state
     
     

    半虚拟化:Xen 内核定制修改

    既然有全虚拟化,那与之相对的也就有半虚拟化,前面说了,由于敏感指令的关系,全虚拟化的VMM需要捕获到这些指令并完整模拟执行这个过程,实现既满足虚拟机操作系统的需要,又不至于影响到物理计算机。

    但说来简单,这个模拟过程实际上相当的复杂,涉及到大量底层技术,并且如此模拟费时费力。

    而试想一下,如果把操作系统中所有执行敏感指令的地方都改掉,改成一个接口调用(HyperCall),接口的提供方VMM实现对应处理,省去了捕获和模拟硬件流程等一大段工作,性能将获得大幅度提升。

    这就是半虚拟化,这项技术的代表就是Xen,一个诞生于2003年的开源项目。

    这项技术一个最大的问题是:需要修改操作系统源码,做相应的适配工作。这对于像Linux这样的开源软件还能接受,充其量多了些工作量罢了。但对于Windows这样闭源的商业操作系统,修改它的代码,无异于痴人说梦。

    硬件辅助虚拟化 VT / AMD-v

    折腾来折腾去,全都是因为x86架构的CPU天然不支持经典虚拟化模式,软件厂商不得不想出其他各种办法来在x86上实现虚拟化。

    如果进一步讲,CPU本身增加对虚拟化的支持,那又会是一番怎样的情况呢?

    在软件厂商使出浑身解数来实现x86平台的虚拟化后的不久,各家处理器厂商也看到了虚拟化技术的广阔市场,纷纷推出了硬件层面上的虚拟化支持,正式助推了虚拟化技术的迅猛发展。

    这其中为代表的就是Intel的VT系列技术和AMD的AMD-v系列技术。

    硬件辅助虚拟化细节较为复杂,简单来说,新一代CPU在原先的Ring0-Ring3四种工作状态之下,再引入了一个叫工作模式的概念,有VMX root operation 和 VMX non-root operation 两种模式,每种模式都具有完整的Ring0-Ring3四种工作状态,前者是VMM运行的模式,后者是虚拟机中的OS运行的模式。

    VMM运行的层次,有些地方将其称为Ring -1,VMM可以通过CPU提供的编程接口,配置对哪些指令的劫持和捕获,从而实现对虚拟机操作系统的掌控。

    换句话说,原先的VMM为了能够掌控虚拟机中代码的执行,不得已采用“中间人”来进行翻译执行,现在新的CPU告诉VMM:不用那么麻烦了,你提前告诉我你对哪些指令哪些事件感兴趣,我在执行这些指令和发生这些事件的时候就通知你,你就可以实现掌控了。完全由硬件层面提供支持,性能自然高了不少。

    上面只是硬件辅助虚拟化技术的一个简单理解,实际上还包含更多的要素,提供了更多的便利给VMM,包括内存的虚拟、I/O的虚拟等等,让VMM的设计开发工作大大的简化,VMM不再需要付出昂贵的模拟执行成本,整体虚拟化的性能也有了大幅度的提升。

    硬件辅助的虚拟化指的是在处理器中直接加入虚拟化指令支持(比如Intel的VT-x或者AMD的SVM),处理器引入了新的虚拟化模式,例如在Intel处理器上是VMX root模式与VMX non-root模式,两种模式都支持ring 0 ~ ring 3,虚拟机管理程序运行在VMX root模式下,而虚拟机操作系统则运行在VMX non-root模式下。

    运行在VMX root模式下的虚拟机管理程序通过显式调用VMLAUNCH或VMRESUME指令切换到VMX non-root模式,硬件自动加载虚拟机操作系统的上下文,于是虚拟机操作系统获得运行,这种转换称为VM entry。

    虚拟机操作系统运行过程中遇到需要虚拟机管理程序处理的事件,例如外部中断或缺页异常,或者主动调用VMCALL指令调用虚拟机管理程序的服务的时候(与系统调用类似),硬件自动挂起虚拟机操作系统,切换到VMX root模式,恢复虚拟机管理程序的运行,这种转换称为VM exit。

    VMX root模式下软件的行为与在没有VT-x技术的处理器上的行为基本一致;而VMX non-root模式则有很大不同,最主要的区别是此时运行某些指令或遇到某些事件时,会发生VM exit。

    由于硬件辅助的虚拟化性能已经很好,而且也不需要修改虚拟机操作系统的代码,因此现在主流的虚拟机软件都是基于硬件辅助的虚拟化技术来开发的。在Linux平台上,就是基于内核的KVM与用户态的虚拟机管理程序qemu的结合。

  • 相关阅读:
    poj 1634
    poj 2153
    POJ 1693
    poj 1789
    POJ 2676
    vue 路由
    用 node.js 创建第一个Hello World
    js原生Ajax 的封装和原理
    BFC原理
    怎么理解js的面向对象编程
  • 原文地址:https://www.cnblogs.com/dream397/p/14273395.html
Copyright © 2020-2023  润新知