Hypercall

在Linux中,大家应该对syscall非常的了解和熟悉,其是用户态进入内核态的一种途径或者说是一种方式,完成了两个模式之间的切换;而在虚拟环境中,有没有一种类似于syscall这种方式,能够从no root模式切换到root模式呢?答案是肯定的,KVM提供了Hypercall机制,x86体系架构也有相关的指令支持。

Hypercall的发起

KVM代码中提供了五种形式的Hypercall接口:

1
2
3
4
5
6
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)

这几个接口的区别在于参数个数的不用,本质是一样的。挑个参数最多的看下:

1
2
3
4
5
6
7
8
9
10
11
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内部实现是标准的内嵌汇编,稍作分析:

KVM_HYPERCALL

1
#define KVM_HYPERCALL ".byte 0x0f,0x01,0xc1"

对于KVM hypercall来说,KVM_HYPERCALL是一个三字节的指令序列,x86体系架构下即是vmcall指令,官方手册解释:

vmcall:
    op code:0F 01 C1 -- VMCALL Call to VM monitor by causing VM exit

言简意赅,vmcall会导致VM exit到VMM。

返回值

: “=a”(ret),表示返回值放在eax寄存器中输出。

输入

: “a”(nr), “b”(p1), “c”(p2), “d”(p3), “S”(p4),表示输入参数放在对应的eax,ebx,ecx,edx,esi中,而nr其实就是可以认为是系统调用号。

hypercall的处理

当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;
}

Conclusion

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

Share