http://blog.chinaunix.net/uid-26000137-id-3761116.html
从软件架构上来说,kvm提供了两个内核模块,使用kvm的io_ctl接口可以管理vcpu和内存,为vcpu注入中断和提供时钟信号,而kvm本身没有提供设备的模拟。设备模拟需要应用层软件Qemu来实现。这种架构保证了kvm避免了繁琐的设备模拟和设备驱动部分(内核中80%以上的代码就是驱动部分)。
总结一下kvm软件的架构特点:
q Kvm本身只提供两个内核模块。Kvm实现了vcpu和内存的管理。
q Qemu控制逻辑,负责创建虚拟机,创建vcpu。
2.2 Kvm管理接口
Qemu和kvm关系很深,甚至可以认为双方本来是一个软件,Qemu是应用层的控制部分,而kvm是内核执行部分。软件复用能达到如此天衣无缝的地步,是一件很神奇的事情,也说明kvm设计时候的思路之巧。
所以分析kvm,必须首先从Qemu的代码分析入手。为了避免繁琐,引入太多知识点,而混杂不清。所以把Qemu的代码做简化处理。
代码清单2-1 Qemu启动代码
s->fd = qemu_open("/dev/kvm", O_RDWR);
ret = kvm_ioctl(s, KVM_GET_API_VERSION, 0);
s->vmfd = kvm_ioctl(s, KVM_CREATE_VM, 0);
...............................
ret = kvm_vm_ioctl(s, KVM_CREATE_VCPU, env->cpu_index);
.............................
env->kvm_fd = ret;
run_ret = kvm_vcpu_ioctl(env, KVM_RUN, 0);
可以看到,kvm提供了一个设备/dev/kvm,对kvm的控制要通过这个设备提供的io_ctl接口实现。这是linux内核提供服务的最通用方式,不再赘述。
而kvm提供了三种概念,分别通过不同的io_ctl接口来控制。
q kvm:代表kvm模块本身,用来管理kvm版本信息,创建一个vm。
q vm:代表一个虚拟机。通过vm的io_ctl接口,可以为虚拟机创建vcpu,设置内存区间,创建中断控制芯片,分配中断等等。
q vcpu:代表一个vcpu。通过vcpu的io_ctl接口,可以启动或者暂停vcpu,设置vcpu的寄存器,为vcpu注入中断等等。
Qemu的使用方式,首先是打开/dev/kvm设备,通过KVM_CREATE_VM创建一个虚拟机对象,然后通过KVM_CREATE_VCPU为虚拟机创建vcpu对象,最后通过KVM_RUN设置vcpu运行起来。因为是简化的代码,中断芯片的模拟,内存的模拟,寄存器的设置等等都已经省略了。
2.3 VT技术和vmcs结构
前文讲到kvm是基于硬件辅助虚拟化来实现的。这个硬件辅助的虚拟化技术,在不同的cpu架构中有不同的实现。在x86的平台下,intel实现了VT技术,而另一家x86芯片厂家AMD也推出了自己的虚拟化技术AMD-V。反映到代码上,intel技术的代码都在/arch/x86/kvm目录里面的vmx.c文件,而AMD的实现代码在相同目录的svm.c文件中。
回顾一下虚拟化技术的实现,经典的虚拟化使用了陷入-模拟的模式,而硬件辅助虚拟化引入了根模式(root operation)和非根模式(none-root operation),每种模式都有ring0-3的四级特权级别。所以,在硬件辅助虚拟化中,陷入的概念实际上被VM-EXIT操作取代了,它代表从非根模式退出到根模式,而从根模式切换到非根模式是VM-Entry操作。
2.3.1 需要具备的硬件知识
做系统软件的必须和硬件打交道,这就必须深入cpu架构和设备的架构。但是intel的架构浩大繁杂,说明文档多达上千页,深入了解着实有难度,另外一种趋势是软硬件的分离已经进行了多年,而系统软件的作者多半是软件人员,而非硬件人员。作为软件人员,了解必备的硬件知识是需要的,也是理解代码和架构的基础。同时,在操作系统软件的理解中,分清软件部分的工作和硬件部分的工作是必备条件,这也是操作系统软件中最让人困惑的部分。
对于虚拟化的vt技术而言,它的软件部分基本体现在vmcs结构中(virtual machine control block)。主要通过vmcs结构来控制vcpu的运转。
q Vmcs是个不超过4K的内存块。
q Vmcs通过下列的指令控制,vmclear:清空vmcs结构,vmread:读取vmcs数据,vmwrite:数据写入vmcs
q 通过VMPTR指针指向vmcs结构,该指针包含vmcs的物理地址。
Vmcs包含的信息可以分为六个部分。
q Guest state area:虚拟机状态域,保存非根模式的vcpu运行状态。当VM-Exit发生,vcpu的运行状态要写入这个区域,当VM-Entry发生时,cpu会把这个区域保存的信息加载到自身,从而进入非根模式。这个过程是硬件自动完成的。保存是自动的,加载也是自动的,软件只需要修改这个区域的信息就可以控制cpu的运转。
q Host state area:宿主机状态域,保存根模式下cpu的运行状态。只在vm-exit时需要将状态
q VM-Execution control filelds:包括page fault控制,I/O位图地址,CR3目标控制,异常位图,pin-based运行控制(异步事件),processor-based运行控制(同步事件)。这个域可以设置那些指令触发VM-Exit。触发VM-Exit的指令分为无条件指令和有条件指令,这里设置的是有条件指令。
q VM-entry contorl filelds:包括vm-entry控制,vm-entry MSR控制,VM-Entry插入的事件。MSR是cpu的模式寄存器,设置cpu的工作环境和标识cpu的工作状态。
q VM-exit control filelds:包括VM-Exit控制,VM-Exit MSR控制。
q VM退出信息:这个域保存VM-Exit退出时的信息,并且描述原因。
有了vmcs结构后,对虚拟机的控制就是读写vmcs结构。后面对vcpu设置中断,检查状态实际上都是在读写vmcs结构。在vmx.h文件给出了intel定义的vmcs结构的内容。
2.4 cpu虚拟化
2.4.1 Vcpu数据结构
struct kvm_vcpu {
struct kvm *kvm;
#ifdef CONFIG_PREEMPT_NOTIFIERS
struct preempt_notifier preempt_notifier;
#endif
int vcpu_id;
struct mutex mutex;
int cpu;
struct kvm_run *run;
unsigned long requests;
unsigned long guest_debug;
int fpu_active;
int guest_fpu_loaded;
wait_queue_head_t wq;
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_size;
unsigned char mmio_data[8];
gpa_t mmio_phys_addr;
#endif
struct kvm_vcpu_arch arch;
};
这个结构定义了vcpu的通用结构,其中重点是kvm_vcpu_arch,这个是和具体cpu型号有关的信息。
struct kvm_vcpu_arch {
u64 host_tsc;
unsigned long regs[NR_VCPU_REGS];
u32 regs_avail;
u32 regs_dirty;
unsigned long cr0;
unsigned long cr2;
unsigned long cr3;
unsigned long cr4;
unsigned long cr8;
u32 hflags;
u64 pdptrs[4];
u64 shadow_efer;
u64 apic_base;
struct kvm_lapic *apic;
int32_t apic_arb_prio;
int mp_state;
int sipi_vector;
u64 ia32_misc_enable_msr;
bool tpr_access_reporting;
struct kvm_mmu mmu;
struct kvm_pv_mmu_op_buffer mmu_op_buffer;
struct kvm_mmu_memory_cache mmu_pte_chain_cache;
struct kvm_mmu_memory_cache mmu_rmap_desc_cache;
struct kvm_mmu_memory_cache mmu_page_cache;
struct kvm_mmu_memory_cache mmu_page_header_cache;
gfn_t last_pt_write_gfn;
int last_pt_write_count;
u64 *last_pte_updated;
gfn_t last_pte_gfn;
struct {
gfn_t gfn;
pfn_t pfn;
unsigned long mmu_seq;
} update_pte;
struct i387_fxsave_struct host_fx_image;
struct i387_fxsave_struct guest_fx_image;
gva_t mmio_fault_cr2;
struct kvm_pio_request pio;
void *pio_data;
u8 event_exit_inst_len;
struct kvm_queued_exception {
bool pending;
bool has_error_code;
u8 nr;
u32 error_code;
} exception;
struct kvm_queued_interrupt {
bool pending;
bool soft;
u8 nr;
} interrupt;
int halt_request;
int cpuid_nent;
struct kvm_cpuid_entry2 cpuid_entries[KVM_MAX_CPUID_ENTRIES];
struct x86_emulate_ctxt emulate_ctxt;
gpa_t time;
struct pvclock_vcpu_time_info hv_clock;
unsigned int hv_clock_tsc_khz;
unsigned int time_offset;
struct page *time_page;
bool singlestep;
bool nmi_pending;
bool nmi_injected;
struct mtrr_state_type mtrr_state;
u32 pat;
int switch_db_regs;
unsigned long db[KVM_NR_DB_REGS];
unsigned long dr6;
unsigned long dr7;
unsigned long eff_db[KVM_NR_DB_REGS];
u64 mcg_cap;
u64 mcg_status;
u64 mcg_ctl;
u64 *mce_banks;
};
q 有寄存器信息,cr0,cr2,cr3等。
q 有内存mmu的信息,
q 有中断控制芯片的信息kvm_lapic
q 有io请求信息kvm_pio_request
q 有vcpu的中断信息interrupt
2.4.2 vcpu创建
首先是Qemu创建VM,从代码分析一下:
代码清单2-2 V
static int kvm_dev_ioctl_create_vm(void)
{
int fd;
struct kvm *kvm;
kvm = kvm_create_vm();
if (IS_ERR(kvm))
return PTR_ERR(kvm);
fd = anon_inode_getfd("kvm-vm", &kvm_vm_fops, kvm, 0);
if (fd < 0)
kvm_put_kvm(kvm);
return fd;
}
调用了函数kvm_create_vm,然后是创建一个文件,这个文件作用是提供对vm的io_ctl控制。
代码清单2-3 V