为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

Linux KVM虚拟化源代码分析文档.doc

2018-04-08 30页 doc 91KB 26阅读

用户头像

is_196623

暂无简介

举报
Linux KVM虚拟化源代码分析文档.docLinux KVM虚拟化源代码分析文档.doc KVM虚拟机源代码分析 1, KVM结构及工作原理 1.1 KVM结构 KVM基本结构有两部分组成。一个是KVM Driver ,已经成为Linux 内核的一个模块。负责虚拟机的创建,虚拟内存的分配,虚拟CPU寄存器的读写以及虚拟CPU的运行等。另外一个是稍微修改过的Qemu,用于模拟PC硬件的用户空间组件,提供I/O设备模型以及访问外设的途径。 用户模式 客户模式 内核模式 Qemu LibKvm Linux用户ioctl 模式 KVM fd /dev/kvmKvm-v...
Linux KVM虚拟化源代码分析文档.doc
Linux KVM虚拟化源代码文档.doc KVM虚拟机源代码分析 1, KVM结构及工作原理 1.1 KVM结构 KVM基本结构有两部分组成。一个是KVM Driver ,已经成为Linux 内核的一个模块。负责虚拟机的创建,虚拟内存的分配,虚拟CPU寄存器的读写以及虚拟CPU的运行等。另外一个是稍微修改过的Qemu,用于模拟PC硬件的用户空间组件,提供I/O设备模型以及访问外设的途径。 用户模式 客户模式 内核模式 Qemu LibKvm Linux用户ioctl 模式 KVM fd /dev/kvmKvm-vm fd Kvm-vcpu fd Linux内核 VMM(Linux 内核+ KVM Driver)模式 图1 KVM基本结构 KVM基本结构如图1所示。其中KVM加入到的Linux内核中,被组织成Linux中标准的字符设备(/dev/kvm)。Qemu通KVM提供的LibKvm应用程序接口,通过ioctl系统调用创建和运行虚拟机。KVM Driver使得整个Linux成为一个虚拟机监控器。并且在原有的Linux两种执行模式(内核模式和用户模式)的基础上,新增加了客户模式,客户模式拥有自己的内核模式和用户模式。在虚拟机运行下,三种模式的分工如下: 客户模式:执行非I/O的客户代码。虚拟机运行在客户模式下。 内核模式:实现到客户模式的切换。处理因为I/O或者其它指令引起的从客户模式的退出。KVM Driver工作在这种模式下。 用户模式:代客户执行I/O指令Qemu运行在这种模式下。 在KVM模型中,每一个Guest OS 都作为一个标准的Linux进程,可以使用Linux的进程管理指令管理。 在图1中./dev/kvm在内核中创建的标准字符设备,通过ioctl系统调用来访问内核虚拟机,进行虚拟机的创建和初始化;kvm_vm fd是创建的指向特定虚拟机实例的文件描述符,通过这个文件描述符对特定虚拟机进行访问控制;kvm_vcpu fd指向为虚拟机创建的虚拟处理器的文件描述符,通过该描述符使用ioctl系统调用设置和调度虚拟处理器的运行。 1.2 KVM工作原理 KVM的基本工作原理:用户模式的Qemu利用接口libkvm通过ioctl系统调用进入内核模式。KVM Driver为虚拟机创建虚拟内存和虚拟CPU后执行VMLAUCH指令进入客户模式。装载Guest OS执行。如果Guest OS发生外部中断或者影子页表缺页之类的事件,暂停Guest OS的执行,退出客户模式进行一些必要的处理。然后重新进入客户模式,执行客户代码。如果发生I/O事件或者信号队列中有信号到达,就会进入用户模式处理。KVM采用全虚拟化技术。客户机不用修改就可以运行。 用户模式内核模式KVM 客户模式Guest QemuDriverOS 开始 进入客户模式执行客户代码 执行IOCTL系统调 用 处理退出 处理I/OYes I/O, NO 信号到 达 VMX Root VMX Non Root OperationOperation 图2 KVM 工作基本原理 2 ,相关技术-处理器管理和硬件辅助虚拟化技术 Intel 在2006年发布了硬件虚拟化技术。其中支持X86体系结构的称为Intel VT-x技术。ADM称为SVM技术。 VT-x引入了一种新的处理器操作,叫做VMX(Virtual Machine Extension),提供了两种处理器的工作环境。VMCS结构实现两种环境之间的切换。VM Entry使虚拟机进去客户模式,VM Exit使虚拟机退出客户模式。 2.1 KVM中Guest OS的调度执行 VMM调度Guest OS执行时,Qemu通过ioctl系统调用进入内核模式,在KVM Driver中通过get_cpu获得当前物理CPU的引用。之后将Guest状态从VMCS中读出。并装入物理CPU中。执行VMLAUCH指令使得物理处理器进入非根操作环境,运行客户代码。 当Guest OS执行一些特权指令或者外部事件时,比如I/O访问,对控制寄存器的操作,MSR的读写数据包到达等。都会导致物理CPU发生VMExit,停止运行Guest OS。将Guest OS保存到VMCS中,Host状态装入物理处理器中,处理器进入根操作环境,KVM取得控制权,通过读取VMCS中VM_EXIT_REASON字段得到引起VM Exit的原因。从而调用kvm_exit_handler处理函数。如果由于I/O获得信号到达,则退出到用户模式的Qemu处理。处理完毕后,重新进入客户模式运行虚拟CPU。如果是因为外部中断,则在Lib KVM中做一些必要的处理,重新进入客户模式执行客户代码。 2.2 KVM中内存管理 KVM使用影子页表实现客户物理地址到主机物理地址的转换。初始为空,随着虚拟机页访问实效的增加,影子页表被逐渐建立起来,并随着客户机页表的更新而更新。在KVM中提供了一个哈希列表和哈希函数,以客户机页表项中的虚拟页号和该页表项所在页表的级别作为键值,通过该键值查询,如不为空,则表示该对应的影子页表项中的物理页号已经存在并且所指向的影子页表已经生成。如为空,则需新生成一张影子页表,KVM将获取指向该影子页表的主机物理页号填充到相应的影子页表项的内容中,同时以客户机页表虚拟页号和表所在的级别生成键值,在代表该键值的哈希桶中填入主机物理页号,以备查询。但是一旦Guest OS中出现进程切换,会把整个影子页表全部删除重建,而刚被删掉的页表可能很快又被客户机使用,如果只更新相应的影子页表的表项,旧的影子页表就可以重用。因此在KVM中采用将影子页表中对应主机物理页的客户虚拟页写保护并且维护一张影子页表的逆向映射表,即从主机物理地址到客户虚拟地址之间的转换表,这样VM对页表或页目录的修改就可以触发一个缺页异常,从而被KVM捕获,对客户页表或页目录项的修改就可以同样作用于影子页表,通过这种方式实现影子页表与客户机页表保持同步。 2.3 KVM中设备管理 一个机器只有一套I/o地址和设备。设备的管理和访问是操作系统中的突出问题、同样也是虚拟机实现的难题,另外还要提供虚拟设备供各个VM使用。在KVM中通过移植Qemu中的设备模型(Device Model)进行设备的管理和访问。操作系统中,软件使用可编程I/O(PIO)和内存映射I/O(MMIO)与硬件交互。而且硬件可以发出中断请求,由操作系统处理。在有虚拟机的情况下,虚拟机必须要捕获并且模拟PIO和MMIO的请求,模拟虚拟硬件中断。 捕获PIO:由硬件直接提供。当VM发出PIO指令时,导致VM Exit然后硬件会将VM Exit原因及对应的指令写入VMCS控制结构中,这样KVM就会模拟PIO指令。MMIO捕获:对MMIO页的访问导致缺页异常,被KVM捕获,通过X86模拟器模拟执行MMIO指令。KVM中的I/O虚拟化都是用户空间的Qemu实现的。所有PIO和MMIO的访问都是被转发到Qemu的。Qemu模拟硬件设备提供给虚拟机使用。KVM通过异步机制以及I/O指令的模拟来完成设备访问,这些通知包括:虚拟中断请求,信号驱动机制以及VM间的通信。 以虚拟机接收数据包来说明虚拟机和设备的交互。 图3 I/O分析 (1)当数据包到达主机的物理网卡后,调用物理网卡的驱动程序,在其中利用Linux内核中的软件网桥,实现数据的转发。 (2)在软件网挢这一层,会判断数据包是发往那个设备的,同时调用网桥的发送函数,向对应的端口发送数据包。 (3)若数据包是发往虚拟机的,则要通过tap设备进行转发,tap设备由两部分组成,网络设备和字符设备。网络设备负责接收和发送数据包,字符设备负责将数据包往内核空间和用户空间进行转发。Tap网络部分收到数据包后,将其设备文件符置位,同时向正在运行VM的进程发出I/O可用信号,引起VM Exit,停止VM运行,进入根操作状态。KVM根据KVM_EXIT_REASON判断原因,模拟I/O指令的执行,将中断注入到VM的中断向量表中。 (4)返回用户模式的Qemu中,执行设备模型。返回到kvm_main loop中,执行Kvm—main—loop—wait,之后进入main_loop wait中,在这个函数里收集对应设备的设备文件描述符的状态,此时tap设备文件描述符的状态同样被集到fd set。 (5)Kvm main—loop不停地循环,通过select系统调用判断哪螋文件描述符的状态发生变化,相应的调用对应的处理函数。对予tap来说,就会通过Qemu—send_packet将数据发往rtl8139 一do—receiver,在这个函数中完成相当于硬件RTL8139网卡的逻辑操作。KVM通过模拟I,O指令操作虚拟RTL8139将数据拷贝到用户地址空间,放在相应的I,O地址。用户模式处理完毕后返回内核模式,而后进入客户模式,VM被再次执行,继续收发数据包。 3,KVM 源代码分析-虚拟机创建和运行流程代码分析 3.1 KVM创建和运行虚拟机流程 KVM虚拟机创建和运行虚拟机分为用户态和核心态两个部分,用户态主要提供应用程序接口,为虚拟机创建虚拟机上下文环境,在libkvm中提供访问内核字符设备/dev/kvm的接口;内核态为添加到内核中的字符设备/dev/kvm,模块加载进内核后即可进行接口用户空间调用创建虚拟机。在创建虚拟机过程中,kvm字符设备主要为客户机创建kvm数据机构,创建该虚拟机的虚拟机文件描述符及其相应的数据结构以及创建虚拟处理器及其相应的数据结构。Kvm创建虚拟机的流程如图4所示。 首先申明一个kvm_context_t 变量用以描述用户态虚拟机上下文信息,然后调用kvm_init()函数初始化虚拟机上下文信息;函数kvm_create()创建虚拟机实例,该函数通过ioctl系统调用创建虚拟机相关的内核数据结构并且返回虚拟机文件描述符给用户态kvm_context_t数据结构;创建完内核虚拟机数据结构后,再创建内核pit以及mmio等基本外设模拟设备,然后调用kvm_create_vcpu()函数来创建虚拟处理器,kvm_create_vcpu()函数通过ioctl()系统调用向由vm_fd文件描述符指向的虚拟文件调用创建虚拟处理器,并将虚拟处理器的文件描述符返回给用户态程序,用以以后的调度使用;创建完虚拟处理器后,由用户态的QEMU程序申请客户机用户空间,用以加载和运行客户机代码;为了使得客户虚拟机正确执行,必须要在内核中为客户机建立正确的内存映射关系,即影子页表信息。因此,申请客户机内存地址空间后,调用函数kvm_create_phys_mem()创建客户机内存映射关系,该函数主要通过ioctl系统调用向vm_fd指向的虚拟文件调用设置内核数据结构中客户机内存域相关信息,主要建立影子页表信息;当创建好虚拟处理器和影子页表后,即可读取客户机到指定分配的空间中,然后调度虚拟处理器运行。调度虚拟机的函数为kvm_run(),该函数通过ioctl系统调用调用由虚拟处理器文件描述符指向的虚拟文件调度处理函数kvm_run()调度虚拟处理器的执行,该系统调用将虚拟处理器vcpu信息加载到物理处理器中,通过vm_entry执行进入客户机执行。在客户机正常运行期间kvm_run()函数不返回,只有发生以下两种情况时,函数返回:1,发生了I/O事件,如客户机发出读写I/O的指令;2,产生了客户机和内核KVM都无法处理的异常。I/O事件处理完毕后,通过重新调用KVM_RUN()函数继续调度客户机的执行。 用户态内核态 声明虚拟机上下文 Kvm_context_t 调用kvm_init()初始化 虚 拟机上下文kvm_context_t 调用调用kvm_create()函数创建ioctl(fd,KVM_CREATE_VM返回虚拟机文件描述符 vm_fd虚拟机,0)来创建内核虚拟机相 关数据结构 调用 ioctl(kvm_vmfd,KVM_CRE创建pit,mmio,以及irqchipATE_IRQCHIP)来创建内核 irqchip数据结构 Ioctl 系统调用 调用调用kvm_create_vcpu创建ioctl(kvm_vmfd,KVM_CRE返回虚拟处理器的文件描 述符信息虚拟处理器ATE_VCPU,slot)来创建内 核虚拟处理器信息申请虚拟机用户空间内存块 调用调用kvm_create_phys_memioctl(kvm_vmfd,KVM_SET 创建虚拟机内核内存,建立_USER_MEMORY_REGION,** 基本的影子页表mem)来创建内核虚拟机影 子页表信息 调用调用kvm_run()函数运行虚ioctl(fd,KVM_RUN,0)来拟处理器,调度客户机的执调度虚拟处理器被加载到行物理处理器上执行 图 4 KVM虚拟机创建流程 3.2 虚拟机创建和运行主要函数分析 1,函数kvm_init():该函数在用户态创建一个虚拟机上下文,用以在用户态保存基本的虚拟机信息,这个函数是创建虚拟机第一个需要调用的函数,函数返回一个kvm_context_t结构体。该函数原型为: Kvm_context_t kvm_init(struct kvm_callbacks *callbacks,void *opaque); 参数:callbacks为结构体kvm_callbacks变量,该结构体包含指向函数的一组指针,用于在客户机执行过程中因为I/O事件退出到用户态的时候处理的回调函数(后面会分析)。参数opaque一般未使用。 函数执行基本过程:打开字符设备dev/kvm,申请虚拟机上下文变量kvm_context_t空间,初始化上下文的基本信息:设置fd文件描述符指向/dev/kvm、禁用虚拟机文件描述符vm_fd(-1) 设置I/O事件回调函数结构体,设置IRQ和PIT的标志位以及内存页面记录的标志位。 主要相关数据结构: 虚拟机上下文structkvm_context_t,用户态数据结构,用以描述虚拟机实例的用户态上下文信息。 Struct kvm_context: 该结构体用于表示一个虚拟机上下文。主要包含的数据域为: Int fd :指向内核标准字符设备/dev/kvm的文件描述符。 Int vm_fd:指向所创建的内核虚拟机数据结构相关文件的文件描述符。 Int vcpu_fd[MAX_VCPUS]:指向虚拟机所有的虚拟处理器的文件描述符数组。 Struct kvm_run *run[MAX_VCPUS]:指向虚拟机运行环境上下文的指针数组。 Struct kvm_callbacks *call_backs: 回调函数结构体指针,该结构体用于处理用户态I/O事件。 Void *opaque:指针(还未弄清楚) Int dirty_page_log_all:设置是否记录脏页面的标志。 Int no_ira_creation: 用于设置是否再kernel里设置irq芯片。 Int_irqchip_in_kernel: 内核中irqchip的状态 Int irqchip_inject_ioctl:用于拦截中断的iotcl系统调用 Int no_pit_creation: 设置是否再内核中设置陷阱 int pit_in_kernel:PIT状态 int coalesced_mmio:kernel中mmio struct kvm_irq_routing *irq_routes:KVM中中断处理的路由结构指针。 int nr_allocated_irq_routes:分配给该虚拟机的中断路由数目。 int max_used_gsi:使用的最大的gsi(?)。 用户态I/O处理函数结构体struct kvm_callbacks,该结构体指向一组函数,主要用于处理客户机因为I/O事件退出而执行的过程。 struct kvm_callbacks :该结构体用于在用户态中处理I/O事件,在KVM中调用KVM_QEMU实现。主要包含的数据域为: int (*inb)(void *opaque, uint16_t addr, uint8_t *data):用于模拟客户机执行8位的inb指令。 int (*inw)(void *opaque, uint16_t addr, uint16_t *data):用于模拟客户机执行16位的inw指令。 int (*inl)(void *opaque, uint16_t addr, uint32_t *data):用于模拟客户机执行32位的inl指令。 int (*outb)(void *opaque, uint16_t addr, uint8_t data):用于模拟客户机执行8位的outb指令。 int (*outw)(void *opaque, uint16_t addr, uint16_t data):用于模拟客户机执行16位的outw指令。 int (*outl)(void *opaque, uint16_t addr, uint32_t data):用于模拟客户机执行32位的outl指令。 int (*mmio_read)(void *opaque, uint64_t addr, uint8_t *data,int len):用于模拟客户机执行mmio读指令。 int (*mmio_write)(void *opaque, uint64_t addr, uint8_t *data,int len):用于模拟客户机执行mmio写指令。 int (*debug)(void *opaque, void *env, struct kvm_debug_exit_arch *arch_info):用户客户机调试的回调函数。 int (*halt)(void *opaque, int vcpu):用于客户机执行halt指令的响应。 int (*shutdown)(void *opaque, void *env):用于客户机执行shutdown指令的响应。 int (*io_window)(void *opaque):用于获得客户机io_windows。 int (*try_push_interrupts)(void *opaque):用于注入中断的回调函数。 void (*push_nmi)(void *opaque):用于注入nmi中断的函数。 void (*post_kvm_run)(void *opaque, void *env);用户得到kvm运行状态函数。 int (*pre_kvm_run)(void *opaque, void *env);用于获得kvm之前运行状态的函数 int (*tpr_access)(void *opaque, int vcpu, uint64_t rip, int is_write);获得tpr访问处理函数 int (*powerpc_dcr_read)(int vcpu, uint32_t dcrn, uint32_t *data);用于powerpc的dcr读操作 nt (*powerpc_dcr_write)(int vcpu, uint32_t dcrn, uint32_t data);用于powerpc的dcr写操作 int (*s390_handle_intercept)(kvm_context_t context, int vcpu,struct kvm_run *run);用于s390的中断处理。 int (*s390_handle_reset)(kvm_context_t context, int vcpu,struct kvm_run *run);用于s390的重设处理。 } 当客户机执行I/O事件或者停机操作等事件时,KVM会交给用户态的QEMU模拟外部I/O事件,调用这个结构体指向的相关的函数进行处理。 Struct kvm_run: 用于KVM运行时一些的一些状态信息。主要包含的数据域为: __u8 request_interrupt_window; __u8 padding1[7]; __u32 exit_reason; __u8 ready_for_interrupt_injection; __u8 if_flag; __u8 padding2[2]; /* in (pre_kvm_run), out (post_kvm_run) */ __u64 cr8; __u64 apic_base; union { /* KVM_EXIT_UNKNOWN */ struct { __u64 hardware_exit_reason; 记录退出原因 } hw; /* KVM_EXIT_FAIL_ENTRY */ 客户机执行过程中执行VM_ENTRY失败。 struct { __u64 hardware_entry_failure_reason; } fail_entry; /* KVM_EXIT_EXCEPTION */ 客户机因为异常退出 struct { __u32 exception; __u32 error_code; } ex; /* KVM_EXIT_IO */ 客户机因为IO事件退出。 struct kvm_io { #define KVM_EXIT_IO_IN 0 #define KVM_EXIT_IO_OUT 1 __u8 direction; __u8 size; /* bytes */ __u16 port; __u32 count; __u64 data_offset; /* relative to kvm_run start */ } io; struct { struct kvm_debug_exit_arch arch; } debug; /* KVM_EXIT_MMIO */ 客户机因为MMIO退出 struct { __u64 phys_addr; __u8 data[8]; __u32 len; __u8 is_write; } mmio; /* KVM_EXIT_HYPERCALL */ 客户机退出的超调用参数。 struct { __u64 nr; __u64 args[6]; __u64 ret; __u32 longmode; __u32 pad; } hypercall; /* KVM_EXIT_TPR_ACCESS */ 客户机退出访问TPR参数 struct { __u64 rip; __u32 is_write; __u32 pad; } tpr_access; /* KVM_EXIT_S390_SIEIC */ 和S390相关数据 struct { __u8 icptcode; __u64 mask; /* psw upper half */ __u64 addr; /* psw lower half */ __u16 ipa; __u32 ipb; } s390_sieic; /* KVM_EXIT_S390_RESET */ #define KVM_S390_RESET_POR 1 #define KVM_S390_RESET_CLEAR 2 #define KVM_S390_RESET_SUBSYSTEM 4 #define KVM_S390_RESET_CPU_INIT 8 #define KVM_S390_RESET_IPL 16 __u64 s390_reset_flags; /* KVM_EXIT_DCR */ struct { __u32 dcrn; __u32 data; __u8 is_write; } dcr; /* Fix the size of the union. */ char padding[256]; 2, 函数kvm_create():该函数主要用于创建一个虚拟机内核环境。该函数原型为: int kvm_create(kvm_context_t kvm,unsigned long phys_mem_bytes, void **phys_mem); 参数:kvm_context_t 表示传递的用户态虚拟机上下文环境,phys_mem_bytes表示需要创建的物理内存的大小,phys_mem表示创建虚拟机的首地址。这个函数首先调用kvm_create_vm() 分配IRQ并且初始化为0,设置vcpu[0]的值为-1,即不允许调度虚拟机执行。然后调用ioctl系统调用ioctl(fd,KVM_CREATE_VM,0)来创建虚拟机内核数据结构struct kvm。 3,系统调用函数ioctl(fd,KVM_CREATE_VM,0),用于在内核中创建和虚拟机相关的数据结构。该函数原型为: Static long kvm_dev_ioctl(struct file *filp,unsigned int ioctl, unsigned long arg);其中ioctl表示命令。这个函数调用kvm_dev_ioctl_create_vm()创建虚拟机实例内核相关数据结构。该函数首先通过内核中kvm_create_vm()函数创建内核中kvm上下文struct kvm,然后通过函数 Anno_inode_getfd(“kvm_vm”,&kvm_vm_fops,kvm,0)返回该虚拟机的文件描述符,返回给用户调用函数,由2中描述的函数赋值给用户态虚拟机上下文变量中的虚拟机描述符kvm_vm_fd。 4,内核创建虚拟机kvm对象后,接着调用kvm_arch_create函数用于创建一些体系结构相关的信息,主要包括kvm_init_tss、kvm_create_pit以及kvm_init_coalsced_mmio等信息。然后调用kvm_create_phys_mem创建物理内存,函数kvm_create_irqchip用于创建内核irq信息,通过系统调用ioctl(kvm->vm_fd,KVM_CREATE_IRQCHIP)。 5,函数kvm_create_vcpu():用于创建虚拟处理器。该函数原型为: int kvm_create_vcpu(kvm_context_t kvm, int slot); 参数:kvm表示对应用户态虚拟机上下文,slot表示需要创建的虚拟处理器的个数。 该函数通过ioctl系统调用ioctl(kvm->vm_fd,KVM_CREATE_VCPU,slot)创建属于该虚拟机的虚拟处理器。该系统调用函数: Static init kvm_vm_ioctl_create_vcpu(struct *kvm, n) 参数kvm为内核虚拟机实例数据结构,n为创建的虚拟CPU的数目。 6,函数kvm_create_phys_mem()用于创建虚拟机内存空间,该函数原型: Void * kvm_create_phys_mem(kvm_context_t kvm,unsigned long phys_start,unsigned len,int log,int writable); 参数:kvm 表示用户态虚拟机上下文信息,phys_start为分配给该虚拟机的物理起始地址,len表示内存大小,log表示是否记录脏页面,writable表示该段内存对应的页表是否可写。 该函数首先申请一个结构体kvm_userspace_memory_region 然后通过系统调用KVM_SET_USER_MEMORY_REGION来设置内核中对应的内存的属性。该系统调用函数原型: Ioctl(int kvm->vm_fd,KVM_SET_USER_MEMORY_REGION,&memory); 参数:第一个参数vm_fd为指向内核虚拟机实例对象的文件描述符,第二个参数KVM_SET_USER_MEMORY_REGION为系统调用命令参数,表示该系统调用为创建内核客户机映射,即影子页表。第三个参数memory表示指向该虚拟机的内存空间地址。系统调用首先通过参数memory通过函数copy_from_user从用户空间复制struct_user_momory_region 变量,然后通过kvm_vm_ioctl_set_memory_region函数设置内核中对应的内存域。该函数原型: Int kvm_vm_ioctl_set_memory_region(struct *kvm,struct kvm_usersapce_memory_region *mem,int user_alloc);该函数再调用函数kvm_set_memory_resgion()设置影子页表。当这一切都准备完毕后,调用kvm_run()函数即可调度执行虚拟处理器。 7,函数kvm_run():用于调度运行虚拟处理器。该函数原型为: Int kvm_run(kvm_context_t kvm,int vcpu, void *env) 该函数首先得到vcpu的描述符,然后调用系统调用ioctl(fd,kvm_run,0)调度运行虚拟处理器。Kvm_run函数在正常运行情况下并不返回,除非发生以下事件之一:一是发生了I/O事件,I/O事件由用户态的QEMU处理;一个是发生了客户机和KVM都无法处理的异常事件。KVM_RUN()中返回截获的事件,主要是I/O以及停机等事件。 4, KVM客户机异常处理机制和代码流程 KVM保证客户机正确执行的基本手段就是当客户机执行I/O指令或者其它特权指令时,引发处理器异常,从而陷入到根操作模式,由KVM Driver模拟执行,可以说,虚拟化保证客户机正确执行的基本手段就是异常处理机制。由于KVM采取了硬件辅助虚拟化技术,因此,和异常处理机制相关的一个重要的数据结构就是虚拟机控制结构VMCS。 VMCS控制结构分为三个部分,一个是版本信息,一个是中止标识符,最后一个是VMCS数据域。VMCS数据域包含了六类信息:客户机状态域,宿主机状态域,VM-Entry控制域,VM-Execution控制域VM-Exit控制域以及VM-Exit信息域。其中VM-Execution控制域可以设置一些可选的标志位使得客户机可以引发一定的异常的指令。宿主机状态域,则保持了基本的寄存器信息,其中CS:RIP指向KVM中异常处理程序的地址。是客户机异常处理的总入口,而异常处理程序则根据VM-Exit信息域来判断客户机异常的根本原因,选择正确的处理逻辑来进行处理。 vmx.c文件是和Intel VT-x体系结构相关的代码文件,用于处理内核态相关的硬件逻辑代码。在VCPU初始化中(vmx_vcpu_create),将kvm中的对应的异常退出处理函数赋值给CS:EIP中,在客户机运行过程中,产生客户机异常时,CPU根据VMCS中的客户机状态域装载CS:EIP的值,从而退出到内核执行异常处理。在KVM内核中,异常处理函数的总入口为: static int vmx_handle_exit(struct kvm_run *kvm_run, struct kvm_vcpu *vcpu); 参数:kvm_run,当前虚拟机实例的运行状态信息,vcpu,对应的虚拟cpu。 这个函数首先从客户机VM-Exit信息域中读取exit_reason字段信息,然后进行一些必要的处理后,调用对应于函数指针数组中对应退出原因字段的处理函数进行处理。函数指针数组定义信息为: static int (*kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) = { [EXIT_REASON_EXCEPTION_NMI] = handle_exception, [EXIT_REASON_EXTERNAL_INTERRUPT] = handle_external_interrupt, [EXIT_REASON_TRIPLE_FAULT] = handle_triple_fault, [EXIT_REASON_NMI_WINDOW] = handle_nmi_window, [EXIT_REASON_IO_INSTRUCTION] = handle_io, [EXIT_REASON_CR_ACCESS] = handle_cr, [EXIT_REASON_DR_ACCESS] = handle_dr, [EXIT_REASON_CPUID] = handle_cpuid, [EXIT_REASON_MSR_READ] = handle_rdmsr, [EXIT_REASON_MSR_WRITE] = handle_wrmsr, [EXIT_REASON_PENDING_INTERRUPT] = handle_interrupt_window, [EXIT_REASON_HLT] = handle_halt, [EXIT_REASON_INVLPG] = handle_invlpg, [EXIT_REASON_VMCALL] = handle_vmcall, [EXIT_REASON_TPR_BELOW_THRESHOLD] = handle_tpr_below_threshold, [EXIT_REASON_APIC_ACCESS] = handle_apic_access, [EXIT_REASON_WBINVD] = handle_wbinvd, [EXIT_REASON_TASK_SWITCH] = handle_task_switch, [EXIT_REASON_EPT_VIOLATION] = handle_ept_violation, }; 这是一组指针数组,用于处理客户机引发异常时候,根据对应的退出字段选择处理函数进行 处理。例如EXIT_REASON_EXCEPTION_NMI对应的handle_exception处理函数用于处理NMI 引脚异常,而EXIT_REASON_EPT_VIOLATION对应的handle_ept_violation处理函数用于处理 缺页异常。 5, KVM 客户机影子页表机制和缺页处理机制及其代码 流程 5.1 KVM影子页表基本原理和缺页处理流程 KVM给客户机呈现了一个从0开始的连续的物理地址空间,成为客户机物理地址空间, 在宿主机中,只是由KVM分配给客户机的宿主机物理内存的一部分。客户机中为应用程序 建立的客户机页表,只是实现由客户机虚拟地址到客户机物理地址的转换(GVA-GPA),为 了正确的访问内存,需要进行二次转换,即客户机物理地址到宿主机物理地址的转换 (GPA-HPA),两个地址转换,前者由客户机负责完成,后者由VMM负责完成。为了实现客 户机物理地址到主机物理地址的地址翻译,VMM为每个虚拟机动态维护了一张从客户机物 理地址到宿主机物理地址的映射关系表。客户机OS所维护的页表只是负责传统的客户机虚 拟地址到客户机物理地址的转换。如果MMU直接装在客户机操作系统所维护的页表进行内 存访问,硬件则无法争取的实现地址翻译。 影子页表是一个有效的解决办法。一份影子页表与一份客户机操作系统的页表相对应,作用是实现从客户虚拟机地址到宿主机物理地址的直接翻译。这样,客户机所能看到和操作的都是虚拟MMU,而真正载入到屋里MMU的是影子页表。如图5所示。 虚拟内存空间 虚拟地址 客户机操作系统内页表 客户机物理内存空间 影子页表客户机物理地址 虚实物理地址翻译表 宿主机物理内存空间 宿主机物理地址 图5-影子页表 由于客户机维护的页表真正体现在影子页表上,因此,客户机对客户机页表的操作实质上反映到影子页表中,并且由VMM控制,因此,影子页表中,对于页表页的访问权限是只读的。一旦客户机对客户机页表进行修改,则产生页面异常,由VMM处理。 影子页表的结构:以x86的两级页表为例。客户机物理帧号GFN和宿主机物理帧号MFN是一一对应的,而宿主机必须要另外负责创建一个页用于CR3寄存器指向的影子页表SMFN。通常通过hash表实现MFN和SMFN之间的计算。影子页表的建立和修改过程交错在客户机对客户机页表的创建过程中。 影子页表的缺页异常处理:当发生缺页异常时,产生异常条件并被VMM捕获,让VMM发现客户机操作系统尚未给客户机虚拟机地址分配客户机物理页,那么VMM首先将缺页异常传递给客户机操作系统,由客户机操作系统为这个客户机虚拟地址分配客户机物理页,由于客户机操作系统分配客户机物理页需要修改其页表,因为又被VMM捕获,VMM更新影子页表中相应的目录和页表项,增加这个客户机虚拟地址到分配的宿主页的映射。如果产生异常时,VMM发现客户机操作系统已经分配给了相应的客户机物理页,只是写权限造成写异常,则VMM更新影子页表中的页目录和页表项,重定向客户机虚拟地址页到宿主机页的映射。客户机缺页异常处理逻辑流程如图6所示。 客户机产生缺页异常 客户机本身引起异 常 是否 VMM将异常注入到客户机, 由Guest OS 自身处理 影子页表缺页异常 根据客户机页表建立相应 影子页目录和页表结构 根据客户页表得到与之对 应的客户机物理地址 根据虚实物理地址对照表 得到宿主机物理地址 将宿主机物理地址填充到 影子页表中 同步客户机页表和影子页 表访问修改位,语义同步 图6 – 缺页异常处理流程 5.2 KVM中缺页异常的处理代码流程 由4中说明的异常处理逻辑中,vmx_handle_exit函数用于处理客户机异常的总入口程 序,KVM根据退出域中的退出原因字段,调用函数指针数据中对应的处理函数进行处理, 在缺页异常中,处理函数为handle_ept_violation。该函数调用kvm_mmu_page_fault函数进 行缺页异常处理。 6, KVM中主要数据结构以及相互关系 6.1 用户态关键数据机构 <>kvm_context-int fd-int vm_fd;-int vcpu_fd[MAX_VCPUS]-struct kvm_run *run[MAX_VCPUS]-struct kvm_callbacks *callbacks-void *opaque-int dirty_pages_log_all-int no_irqchip_creation-int irqchip_in_kernel-int irqchip_inject_ioctl-int no_pit_creation-int pit_in_kernel-int coalesced_mmio-struct kvm_irq_routing *irq_routes-int nr_allocated_irq_routes-int max_used_gsi <><>kvm_callbackskvm_run+int (*inb)(void *opaque, uint16_t addr, uint8_t *data)+__u8 request_interrupt_window+int (*inw)(void *opaque, uint16_t addr, uint16_t *data)+__u8 padding1[7]+int (*inl)(void *opaque, uint16_t addr, uint32_t *data)+__u32 exit_reason+int (*outb)(void *opaque, uint16_t addr, uint8_t data)+__u8 ready_for_interrupt_injection+int (*outw)(void *opaque, uint16_t addr, uint16_t data)+__u8 if_flag+int (*outl)(void *opaque, uint16_t addr, uint32_t data)+__u8 padding2[2]+int (*mmio_read)(void *opaque, uint64_t addr, uint8_t *data,int len)+__u64 cr8+int (*mmio_write)(void *opaque, uint64_t addr, uint8_t *data,int len)+_u64 apic_base+int (*debug)(void *opaque, void *env, struct kvm_debug_exit_arch *arch_info)+union anno+int (*halt)(void *opaque, int vcpu)+int (*shutdown)(void *opaque, void *env)+int (*io_window)(void *opaque)+int (*try_push_interrupts)(void *opaque)+void (*push_nmi)(void *opaque)+void (*post_kvm_run)(void *opaque, void *env)<>+int (*pre_kvm_run)(void *opaque, void *env)kvm_irq_routing+int (*tpr_access)(void *opaque, int vcpu, uint64_t rip, int is_write)+__u32 nr+int (*powerpc_dcr_read)(int vcpu, uint32_t dcrn, uint32_t *data)+__u32 flags+int (*powerpc_dcr_write)(int vcpu, uint32_t dcrn, uint32_t data)+struct kvm_irq_routing_entry entries[0]+int (*s390_handle_intercept)(kvm_context_t context, int vcpu,struct kvm_run *run)+int (*s390_handle_reset)(kvm_context_t context, int vcpu,struct kvm_run *run) <>kvm_irq_routing_entry+__u32 gsi+__u32 type+__u32 flags+__u32 pad<>+union ukvm_debug_exit_arch+__u32 exception+__u32 pad+__u64 pc+__u64 dr6<>u+__u64 dr7-struct kvm_irq_routing_irqchip irqchip-struct kvm_irq_routing_msi msi-__u32 pad[8] <><>kvm_irq_routing_msikvm_irq_routing_irqchip+__u32 address_lo+__u32 irqchip+__u32 address_hi+__u32 pin+__u32 data+__u32 pad KVM用户态重要数据结构 KVM-CONTEXT-T为虚拟机上下文 KVM-run为虚拟机运行状态 Kvm-callbacks为虚拟机产生I/O事件处理的回调函数 Kvm-irq-routing 为中断处理相关数据结构 6.2 内核态关键数据机构 <>kvm+struct mutex lock+spinlock_t mmu_lock+struct rw_semaphore slots_lock+struct mm_struct *mm+int nmemslots+struct kvm_memory_slot memslots[KVM_MEMORY_SLOTS +KVM_PRIVATE_MEM_SLOTS]+struct kvm_vcpu *vcpus[KVM_MAX_VCPUS]+struct list_head vm_list+struct kvm_io_bus mmio_bus+struct kvm_io_bus pio_bus+struct kvm_vm_stat stat+struct kvm_arch arch+atomic_t users_count+struct kvm_coalesced_mmio_dev *coalesced_mmio_dev+struct kvm_coalesced_mmio_ring *coalesced_mmio_ring+struct list_head irq_routing+struct hlist_head mask_notifier_list+struct mmu_notifier mmu_notifier+unsigned long mmu_notifier_seq+long mmu_notifier_count <>kvm_vcpu<><>kvm_memory_slotkvm_io_bus+struct kvm *kvm+struct preempt_notifier preempt_notifier+gfn_t base_gfn+int dev_count+int vcpu_id+unsigned long npages+#define NR_IOBUS_DEVS 6+struct mutex mutex+unsigned long flags+struct kvm_io_device *devs[NR_IOBUS_DEVS]+int cpu+unsigned long *rmap+struct kvm_run *run+unsigned long *dirty_bitmap+int guest_mode+struct *lpage_info+unsigned long requests+unsigned long userspace_addr+unsigned long guest_debug+int user_alloc+int fpu_active+int guest_fpu_loaded+wait_queue_head_t wq+int sigset_active+sigset_t sigset+struct kvm_vcpu_stat stat<>lpage_info+unsigned long rmap_pde+int write_count <><><>kvm_ioapickvm_archkvm_vm_stat(x86)+u64 base_address+int naliases+u32 mmu_shadow_zapped;+u32 ioregsel+struct kvm_mem_alias aliases[KVM_ALIAS_SLOTS]+u32 mmu_pte_write+u32 id+unsigned int n_free_mmu_pages+u32 mmu_pte_updated+u32 irr+unsigned int n_requested_mmu_pages;+u32 mmu_pde_zapped+u32 pad+unsigned int n_alloc_mmu_pages+u32 mmu_flooded+union kvm_ioapic_redirect_entry redirtbl[IOAPIC_NUM_PINS]+struct hlist_head mmu_page_hash[KVM_NUM_MMU_PAGES]+u32 mmu_recycled+struct kvm_io_device dev+struct list_head active_mmu_pages+u32 mmu_cache_miss+struct kvm *kvm+struct list_head assigned_dev_head+u32 mmu_unsync+void (*ack_notifier)(void *opaque, int irq)+struct list_head oos_global_pages+u32 mmu_unsync_global+struct iommu_domain *iommu_domain+u32 remote_tlb_flush+struct kvm_pic *vpic+u32 lpages+struct kvm_ioapic *vioapic+struct kvm_pit *vpit<>+struct hlist_head irq_ack_notifier_listkvm_mem_alias+int vapics_in_nmi_mode+gfn_t base_gfn+unsigned int tss_addr+unsigned long npages+struct page *apic_access_page+gfn_t target_gfn+gpa_t wall_clock+struct page *ept_identity_pagetable+bool ept_identity_pagetable_done+unsigned long irq_sources_bitmap+unsigned long irq_states[KVM_IOAPIC_NUM_PINS]+u64 vm_init_tsc <><>kvm_coalesced_mmio_devkvm_coalesced_mmio_ring+struct kvm_io_device dev+__u32 first, last+struct kvm *kvm+struct kvm_coalesced_mmio coalesced_mmio[0]+int nb_zones+struct kvm_coalesced_mmio_zone zone[KVM_COALESCED_MMIO_ZONE_MAX] KVM 结构体定义 <>kvm_vcpu+struct kvm *kvm+struct preempt_notifier preempt_notifier+int vcpu_id+struct mutex mutex+int cpu+struct kvm_run *run+int guest_mode+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+int mmio_needed;+int mmio_read_completed+int mmio_is_write+int mmio_size+unsigned char mmio_data[8]+gpa_t mmio_phys_addr+struct kvm_vcpu_arch arch <><>kvm_vcpu_stat<>kvm_vcpu_archpreempt_notifierkvm_run-u32 pf_fixed+struct list_head link+__u8 request_interrupt_window-u32 pf_guest+struct task_struct *tsk+__u8 padding1[7]-u32 tlb_flush+struct preempt_ops *ops+__u32 exit_reason-u32 invlpg+__u8 ready_for_interrupt_injection-u32 exits+__u8 if_flag-u32 io_exits+__u8 padding2[2]-u32 mmio_exits+__u64 cr8-u32 signal_exits+_u64 apic_base-u32 irq_window_exits+union anno-u32 nmi_window_exits-u32 halt_exits-u32 halt_wakeup-u32 request_irq_exits-u32 request_nmi_exits-u32 irq_exits;-u32 host_state_reloadpreempt_ops-u32 efer_reload-u32 fpu_reload-void (*sched_in)(struct preempt_notifier *notifier, int cpu)-u32 insn_emulation-void (*sched_out)(struct preempt_notifier *notifier,struct task_struct *next)-u32 insn_emulation_fail-u32 hypercalls-u32 irq_injections-u32 nmi_injections VCPU 结构体定义 <>kvm_vcpu_arch +u64 host_tsc+int interrupt_window_open+unsigned long irq_summary+DECLARE_BITMAP(irq_pending, KVM_NR_INTERRUPTS)+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_gfnkvm_vcpu_stat+int last_pt_write_count+u64 *last_pte_updated-u32 pf_fixed+gfn_t last_pte_gfn-u32 pf_guest+struct update_pte-u32 tlb_flush+struct i387_fxsave_struct host_fx_image-u32 invlpg+struct i387_fxsave_struct guest_fx_image-u32 exits+gva_t mmio_fault_cr2-u32 io_exits+struct kvm_pio_request pio-u32 mmio_exits+void *pio_data-u32 signal_exits+struct kvm_queued_exception exception-u32 irq_window_exits+struct kvm_queued_interrupt interrupt-u32 nmi_window_exits+struct rmode-u32 halt_exits+int halt_request-u32 halt_wakeup+int cpuid_nent-u32 request_irq_exits+struct kvm_cpuid_entry2 cpuid_entries[KVM_MAX_CPUID_ENTRIES]-u32 request_nmi_exits+struct x86_emulate_ctxt emulate_ctxt-u32 irq_exits;+gpa_t time;-u32 host_state_reload+struct pvclock_vcpu_time_info hv_clock-u32 efer_reload+unsigned int hv_clock_tsc_khz-u32 fpu_reload+unsigned int time_offset-u32 insn_emulation+struct page *time_page-u32 insn_emulation_fail+bool nmi_pending-u32 hypercalls+bool nmi_injected-u32 irq_injections+bool nmi_window_open;-u32 nmi_injections+struct mtrr_state_type mtrr_state+u32 pat+int switch_db_regs+unsigned long host_db[KVM_NR_DB_REGS]+unsigned long host_dr6+unsigned long host_dr7+unsigned long db[KVM_NR_DB_REGS]+unsigned long dr6+unsigned long dr7+unsigned long eff_db[KVM_NR_DB_REGS] kvm_vcpu_arch结构 <>kvm_vcpu_arch <><><>kvm_mmukvm_lapickvm_pv_mmu_op_buffer+void (*new_cr3)(struct kvm_vcpu *vcpu)+unsigned long base_address+void *ptr+int (*page_fault)(struct kvm_vcpu *vcpu, gva_t gva, u32 err)+struct kvm_io_device dev+unsigned len+void (*free)(struct kvm_vcpu *vcpu)+struct kvm_timer lapic_timer+unsigned processed+gpa_t (*gva_to_gpa)(struct kvm_vcpu *vcpu, gva_t gva)+u32 divide_count+char buf[512] __aligned(sizeof(long))+void (*prefetch_page)(struct kvm_vcpu *vcpu,struct kvm_mmu_page *page)+struct kvm_vcpu *vcpu;+int (*sync_page)(struct kvm_vcpu *vcpu struct kvm_mmu_page *sp)+struct page *regs_page+void (*invlpg)(struct kvm_vcpu *vcpu, gva_t gva)+void *regs+hpa_t root_hpa;+gpa_t vapic_addr+int root_level+struct page *vapic_page+int shadow_root_level+union kvm_mmu_page_role base_role+u64 *pae_root+u64 rsvd_bits_mask[2][4] <><><><>kvm_mmu_memory_cachekvm_pio_requestkvm_queued_exceptionupdate_ptekvm_queued_interrupt-int nobjs+unsigned long count+bool pending+gfn_t gfn+bool pending-void *objects[KVM_NR_MEM_OBJS]+int cur_count+bool has_error_code+pfn_t pfn+u8 nr+gva_t guest_gva+u8 nr+int largepage+int in+u32 error_code+unsigned long mmu_seq+int port+int size+int string+int down+int rep <><>decode_cachermodekvm_cpuid_entry2x86_emulate_ctxt-u8 twobyte-int active+__u32 function+struct kvm_vcpu *vcpu;-u8 b-u8 save_iopl+__u32 index+unsigned long eflags-u8 lock_prefix-struct kvm_save_segment+__u32 flags+int mode-u8 rep_prefix+__u32 eax+u32 cs_base-u8 op_bytes+__u32 ebx+struct decode_cache decode-u8 ad_bytes+__u32 ecx<>-u8 rex_prefix+__u32 edxkvm_save_segment-struct operand src+__u32 padding[3]+u16 selector-struct operand src2+unsigned long base-struct operand dst+u32 limit-bool has_seg_override+u32 ar-u8 seg_override-unsigned int d-unsigned long regs[NR_VCPU_REGS]<>-unsigned long eip<>pvclock_vcpu_time_info-u8 modrmmtrr_state_type+u32 version-u8 modrm_mod+struct mtrr_var_range var_ranges[MTRR_MAX_VAR_RANGES]+u32 pad0-u8 modrm_reg+mtrr_type fixed_ranges[MTRR_NUM_FIXED_RANGES]+u64 tsc_timestamp-u8 modrm_rm+unsigned char enabled+u64 system_time-u8 use_modrm_ea+unsigned char have_fixed+u32 tsc_to_system_mul-bool rip_relative+mtrr_type def_type+s8 tsc_shift-unsigned long modrm_ea+u8 pad[3]-void *modrm_ptr-unsigned long modrm_val-struct fetch_cache fetch <><>kvm_io_devicekvm_timer+void (*read)(struct kvm_io_device *this,gpa_t addr, int len, void *val)+struct hrtimer timer+void (*write)(struct kvm_io_device *this,gpa_t addr,int len,const void *val)+s64 period+int (*in_range)(struct kvm_io_device *this, gpa_t addr, int len,int is_write)+atomic_t pending+void (*destructor)(struct kvm_io_device *this)+bool reinject+void *private+struct kvm_timer_ops *t_ops+struct kvm *kvm<>+int vcpu_idkvm_timer_ops+bool (*is_periodic)(struct kvm_timer *) Kvm-vcpu-arch 包含数据结构
/
本文档为【Linux KVM虚拟化源代码分析文档&#46;doc】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索