本文展示的代码中,Linux内核版本为3.14.69

KVM内核模块重要的数据结构

kvm结构体

KVM结构体在 KVM的系统架构中代表一个具体的虚拟机。当通过VM_CREATE_KVM指令创建一个新的KVM结构体对象。

struct kvm结构体如下:

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
47
48
49
50
51
52
struct kvm {
spinlock_t mmu_lock;
struct mutex slots_lock;
struct mm_struct *mm; /* userspace tied to this vm */
struct kvm_memslots *memslots;
struct srcu_struct srcu;
#ifdef CONFIG_KVM_APIC_ARCHITECTURE
u32 bsp_vcpu_id;
#endif
struct kvm_vcpu *vcpus[KVM_MAX_VCPUS];
atomic_t online_vcpus;
int last_boosted_vcpu;
struct list_head vm_list;
struct mutex lock;
struct kvm_io_bus *buses[KVM_NR_BUSES];
#ifdef CONFIG_HAVE_KVM_EVENTFD
struct {
spinlock_t lock;
struct list_head items;
struct list_head resampler_list;
struct mutex resampler_lock;
} irqfds;
struct list_head ioeventfds;
#endif
struct kvm_vm_stat stat;
struct kvm_arch arch;
atomic_t users_count;
#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
struct kvm_coalesced_mmio_ring *coalesced_mmio_ring;
spinlock_t ring_lock;
struct list_head coalesced_zones;
#endif

struct mutex irq_lock;
#ifdef CONFIG_HAVE_KVM_IRQCHIP
/*
* Update side is protected by irq_lock and,
* if configured, irqfds.lock.
*/
struct kvm_irq_routing_table __rcu *irq_routing;
struct hlist_head mask_notifier_list;
struct hlist_head irq_ack_notifier_list;
#endif

#if defined(CONFIG_MMU_NOTIFIER) && defined(KVM_ARCH_WANT_MMU_NOTIFIER)
struct mmu_notifier mmu_notifier;
unsigned long mmu_notifier_seq;
long mmu_notifier_count;
#endif
long tlbs_dirty;
struct list_head devices;
};

KVM结构体对象包含了vCPU、内存、APIC、IRQ、MMU、Event事件管理等信息。该结构体中的信息主要在KVM虚拟机内部使用,用于跟踪虚拟机的状态。

在KVM中,连接了如下几个重要的结构体成员,它们对虚拟机的运行有重要的作用。

  • struct kvm_memslots *memslots;
    KVM虚拟机所分配到的内存slot,以数组形式存储这些slot的地址信息。
    由于客户机物理地址不能直接用于宿主机物理 MMU 进行寻址,所以需要把客户机物理地址转换成宿主机虚拟地址 (Host Virtual Address, HVA),为此,KVM 用一个 kvm_memory_slot 数据结构来记录每一个地址区间的映射关系,此数据结构包含了对应此映射区间的起始客户机页帧号 (Guest Frame Number, GFN),映射的内存页数目以及起始宿主机虚拟地址。于是 KVM 就可以实现对客户机物理地址到宿主机虚拟地址之间的转换,也即首先根据客户机物理地址找到对应的映射区间,然后根据此客户机物理地址在此映射区间的偏移量就可以得到其对应的宿主机虚拟地址。进而再通过宿主机的页表也可实现客户机物理地址到宿主机物理地址之间的转换,也即 GPA 到 HPA 的转换。

  • struct kvm_vcpu *vcpus[KVM_MAX_VCPUS];
    KVM虚拟机中包含的vCPU结构体,一个虚拟机CPU对应一个vCPU结构体。

  • struct kvm_io_bus *buses[KVM_NR_BUSES];
    KVM虚拟机中的I/O总线,一条总线对应一个kvm_io_bus结构体,如ISA总线、PCI总线。

  • struct kvm_vm_stat stat;
    KVM虚拟机中的页表、MMU等运行时的状态信息。

  • struct kvm_arch arch;
    KVM的软件arch方面所需要的一些参数。

kvm_vcpu结构体

在用户通过KVM_CREATE_VCPU系统调用请求创建vCPU之后,KVM子模块讲创建kvm_vcpu结构体并进行初始化操作,然后返回对应的vcpu_fd描述符。在KVM的内部虚拟机调度中,kvm_vcpu和KVM中的相关数据进行操作。

sruct kvm_vcpu如下:

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
47
48
49
50
51
52
53
54
55
56
struct kvm_vcpu {
struct kvm *kvm;
#ifdef CONFIG_PREEMPT_NOTIFIERS
struct preempt_notifier preempt_notifier;
#endif
int cpu;
int vcpu_id;
int srcu_idx;
int mode;
unsigned long requests;
unsigned long guest_debug;

struct mutex mutex;
struct kvm_run *run;

int fpu_active;
int guest_fpu_loaded, guest_xcr0_loaded;
wait_queue_head_t wq;
struct pid *pid;
int sigset_active;
sigset_t sigset;
struct kvm_vcpu_stat stat;

#ifdef CONFIG_HAS_IOMEM
int mmio_needed;
int mmio_read_completed;
int mmio_is_write;
int mmio_cur_fragment;
int mmio_nr_fragments;
struct kvm_mmio_fragment mmio_fragments[KVM_MAX_MMIO_FRAGMENTS];
#endif

#ifdef CONFIG_KVM_ASYNC_PF
struct {
u32 queued;
struct list_head queue;
struct list_head done;
spinlock_t lock;
} async_pf;
#endif

#ifdef CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT
/*
* Cpu relax intercept or pause loop exit optimization
* in_spin_loop: set when a vcpu does a pause loop exit
* or cpu relax intercepted.
* dy_eligible: indicates whether vcpu is eligible for directed yield.
*/
struct {
bool in_spin_loop;
bool dy_eligible;
} spin_loop;
#endif
bool preempted;
struct kvm_vcpu_arch arch;
};

kvm_vcpu结构体中的字段较多,其中重要的成员如下:

  • int vcpu_id;
    对应的CPU的ID。

  • struct kvm_run *run;
    vCPU的运行时参数,其中保存了寄存器信息、内存信息、虚拟机状态等各种动态信息。

  • struct kvm_vcpu_arch arch;
    存储有KVM虚拟机的运行时参数,如定时器、中断、内存槽等方面的信息。

kvm_x86_ops结构体

kvm_x86_ops结构体中包含了针对具体的CPU架构进行虚拟化时的函数指针调用,其定义在Linux内核文件的arch/x86/include/asm/kvm_host.h中。该结构体主要包含以下几种类型的操作。

  • CPU VMM状态硬件初始化
  • vCPU创建与管理
  • 中断管理
  • 寄存器管理
  • 时钟管理

kvm_x86_ops结构体中的所有成员都是函数指针,在kvm-intel.ko和kvm-amd.ko这两个不同的模块中,针对各自的体系提供了不同的函数。在KVM的初始化过程和后续的运行过程中,KVM子系统的代码将通过该结构体的函数进行实际的硬件操作。

kvm_x86_ops结构体通过静态初始化。针对amd架构的初始化代码在svm.c中,针对Intel架构的初始化代码在vmx.c中。Intel架构的kvm_x86_ops结构体部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static struct kvm_x86_ops vmx_x86_ops = {
.cpu_has_kvm_support = cpu_has_kvm_support,
.disabled_by_bios = vmx_disabled_by_bios,
.hardware_setup = hardware_setup,
.hardware_unsetup = hardware_unsetup,
.check_processor_compatibility = vmx_check_processor_compat,
.hardware_enable = hardware_enable,
.hardware_disable = hardware_disable,
.cpu_has_accelerated_tpr = report_flexpriority,

.vcpu_create = vmx_create_vcpu,
.vcpu_free = vmx_free_vcpu,
.vcpu_reset = vmx_vcpu_reset,

.prepare_guest_switch = vmx_save_host_state,
.vcpu_load = vmx_vcpu_load,
.vcpu_put = vmx_vcpu_put,
......

需要注意的是,因为KVM架构要同时考虑不同的架构体系。因此,kvm_x86_ops结构体是在KVM架构的初始化过程中注册并导出成为全局变量,让KVM的各个子模块能够方便地调用。

arch/x86/kvm/x86.c中,定义了名为kvm_x86_ops的静态变量,通过export_symbol宏在全局范围内导出。在kvm_init的初始化过程中,通过调用kvm_arch_init函数给kvm_x86_ops赋值,代码如下,其中ops就是通过vmx.c调用kvm_init函数时传入的kvm_x86_ops结构体。

1
2
3
kvm_init_msr_list();

kvm_x86_ops = ops;