只有偏执狂才能生存
分类: LINUX
2015-07-06 15:26:03
在Linux中,大家应该对syscall非常的了解和熟悉,其是用户态进入内核态的一种途径或者说是一种方式,完成了两个模式之 间的切换;而在虚拟环境中,有没有一种类似于syscall这种方式,能够从no root模式切换到root模式呢?答案是肯定的,KVM提供了Hypercall机制,x86体系架构也有相关的指令支持。
KVM代码中提供了五种形式的Hypercall接口:
file: arch/x86/include/asm/kvm_para.h, line: 34 static inline long kvm_hypercall0(unsigned int nr);
static inline long kvm_hypercall1(unsigned int nr, unsigned long p1);
static inline long kvm_hypercall2(unsigned int nr, unsigned long p1, unsigned long p2);
static inline long kvm_hypercall3(unsigned int nr, unsigned long p1, unsigned long p2, unsigned long p3)
static inline long kvm_hypercall4(unsigned int nr, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4)
|
|
这几个接口的区别在于参数个数的不用,本质是一样的。挑个参数最多的看下:
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; }
|
|
Hypercall内部实现是标准的内嵌汇编,稍作分析:
1 | #define KVM_HYPERCALL ".byte 0x0f,0x01,0xc1" |
对于KVM hypercall来说,KVM_HYPERCALL是一个三字节的指令序列,x86体系架构下即是vmcall指令,官方手册解释:
vmcall: op code:0F01C1 -- VMCALLCalltoVM monitorbycausing VMexit言简意赅,vmcall会导致VM exit到VMM。
: “=a”(ret),表示返回值放在eax寄存器中输出。
: “a”(nr), “b”(p1), “c”(p2), “d”(p3), “S”(p4),表示输入参数放在对应的eax,ebx,ecx,edx,esi中,而nr其实就是可以认为是系统调用号。
当Guest发起一次hypercall后,VMM会接管到该call导致的VM Exit。
1 2 3 4 5 | static int (*const kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = { ...... [EXIT_REASON_VMCALL] = handle_vmcall, ...... } |
进入handle_vmcall()->kvm_emulate_hypercall()处理,过程非常简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
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; } |
|
|
整个过程非常简洁和简单,hypercall机制给了Guest能够主动进入VMM的一种方式。个人理解是:一旦Guest使用了这种机制,意味着Guest知道自己位于Guest中,也就意味着所谓的半虚拟化。