1. 一句话总结
内存虚拟化处理虚拟机里边的进程怎么拜访物理机上的内存这一问题。
GuestOS自身有虚拟地址空间,用GVA表明。虚拟机以为自己独占整个内存空间,用GPA表明。
HostOS自身有虚拟机地址空间,用HVA表明。宿主机自身有物理内存空间,用HPA表明。
好,内存虚拟化的问题变成了GVA-》HPA的映射问题。
GVA-》GPA经过GuestOS页表映射。HVA-》HPA经过HostOS页表映射。因而,只需树立GPA-》HVA的映射联系,即可处理内存虚拟化的问题。但,这样三段逐次映射,功率低下。
引进软件模仿的影子页表和硬件辅佐的EPT页表。
影子页表:GuestOS创立GVA-》GPA页表的时分,kvm知道GVA对应的HPA,并悄悄记载下映射联系GVA-》HPA。后续需求GVA到GPA映射的时分,依据影子页表就能查到HPA。
EPT页表:硬件层面引进EPTP寄存器。直接将Guest的CR3加载到宿主机的MMU中。一起EPT页表被载入专门的EPT页表指针寄存器 EPTP。也便是说GVA-》GPA-》HPA两次地址转化都由硬件完结。
2. 概述
咱们知道80386引进了保护形式后,内存空间分为虚拟地址空间和物理地址空间。后续引进页表机制,把虚拟机地址送往mmu,mmu查TLB不中的状况下,顺次查页表就能够找到对应的物理地址。
在虚拟化场景下状况稍微杂乱,分为以下几种:
①GuestOS 虚拟地址(guestOS virtual Adress,GVA)
说白了guestos中进程运用的虚拟地址便是GVA,也便是程序拜访逻辑存储器的地址。
②guestOS 物理地址(GuestOS Physical Address,GPA)
Guestos以为的物理地址,也是虚拟机mmu查页表得出的地址可是他实质是一个逻辑上的地址,是引进虚化后发生的一个逻辑概念。它有必要借助于内存虚拟化映射到宿主机的物理地址上才干拜访内存
③主机虚拟机地址(Host virtul Address,HVA)
宿主机中的虚拟地址,宿主机进程运用的虚拟地址空间。
④主机物理地址(Host Physical Address,HPA)
宿主机实在内存地址,实在能够拜访的物理内存空间。
至此,在虚拟机场景下,怎么由GVA-》HPA便是内存虚拟化的作业。其间,Qemu担任办理虚拟机内存大小,记载内存对应的HVA地址(因为Qemu是用户态的进程,无法办理HPA)想要转化为HPA需求借助于KVM内核也便是影子页表SPT(Shadow Page Table)和EPT(Extent Page Table)
2.1 影子页表
在Guestos树立页表的时分,KVM悄悄的树立了一套指向宿主机物理地址的页表。客户机中的每一个页表项都有一个影子页表项与之相对应,就像其影子相同。
在客户机拜访内存时,真实被装入宿主机 MMU 的是客户机当时页表所对应的影子页表这样经过影子页表就能够完结真实的内存拜访虚拟机页表和影子页表经过一个哈希表树立相关这样经过页目录/页表的客户机物理地址就能够在哈希链表中快速地找到对应的影子页目录/页表当客户机切换进程时,客户机操作体系会把待切换进程的页表基址载入 CR3而 KVM 将会截获这一特权指令,进行新的处理,也即在哈希表中找到与此页表基址对应的影子页表基址,载入客户机 CR3使客户机在康复运转时 CR3 实践指向的是新切换进程对应的影子页表。
2.2 EPT
EPT 技能在原有客户机页表对客户机虚拟地址到客户机物理地址映射的基础上引进了 EPT页表来完结客户机物理地址到宿主机物理地址的另一次映射,这两次地址映射都是由硬件主动完结。客户机运转时,客户机页表被载入 CR3,而 EPT 页表被载入专门的EPT 页表指针寄存器 EPTP。
在客户机物理地址到宿主机物理地址转化的过程中,因为缺页、写权限缺乏等原因也会导致客户机退出,发生 EPT反常。关于 EPT 缺页反常,KVM首要依据引起反常的客户机物理地址,映射到对应的宿主机虚拟地址,然后为此虚拟地址分配新的物理页最终 KVM 再更新 EPT 页表,树立起引起反常的客户机物理地址到宿主机物理地址之间的映射。对 EPT 写权限引起的反常,KVM 则经过更新相应的 EPT 页表来处理。
由此能够看出,EPT 页表相关于前述的影子页表,其完结办法大大简化。并且,因为客户机内部的缺页反常也不会致使客户机退出,因而提高了客户机运转的功能。此外,KVM 只需为每个客户机保护一套 EPT 页表,也大大减少了内存的额定开支。
3. Qemu到KVM内存办理
3.1 设置钩子
main(vl.c)==》configure_accelerator==》kvm_init(kvm_all.c)==》memory_listener_register(&kvm_memory_listener,NULL);将kvm_memory_listener增加到memory_listeners链表中,将address_spaces和listener树立相关
3.2 内存目标初始化
main(vl.c)==》cpu_exec_init_all(exec.c)==》memory_map_init(exec.c)Qemu中体系内存system_memory来办理,io内存用system_io来办理。staTIc MemoryRegion *system_memory.MemoryRegion能够有子区域。而memory_lister担任处理增加和移除内存区域的办理。
3.3 内存实例化
pc_init1(hw\pc_piix.c)==》pc_memory_init这儿首要分配整个内存区域要点重视memory_region_init_ram办法memory_region_init_ram==》qemu_ram_alloc(取得内存的HVA记载到)==》qemu_ram_alloc_internal==》ram_block_add(生成一个RAMBlock增加到ram_list,hva放到host字段)==》phys_mem_alloc==》qemu_anon_ram_alloc==》mmap
3.4 VM-Exit处理
因为mmio导致的退出,相关处理如下kvm_cpu_exec==》 case KVM_EXIT_MMIO==》 cpu_physical_memory_rw==》 address_space_rw==》 io_mem_write
3.5 qemu到kvm的内存调用接口
前面咱们讲到注册过listener,当设置内存时会调用到
staTIc MemoryListener kvm_memory_listener = {
.region_add = kvm_region_add,
region_add==》kvm_region_add==》kvm_set_phys_mem
①物理开端地址和长度,在kvm_state中查找已树立的KVMSlot *mem区域
②假如没找到树立一个slot
==》kvm_set_user_memory_region(告诉内核态树立内存区域)==》kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem)
3.6 KVM内存处理
kvm_vm_ioctl==》kvm_vm_ioctl_set_memory_region==》kvm_set_memory_region==》__kvm_set_memory_region内核态也保护了一个slots,内核态slot的办理战略是依据用户空间的slot_id一一对应的slot =id_to_memslot(kvm-》memslots, mem-》slot);
①经过用户态的slot获取到内核态对应结构
②依据slot中的值和要设置的值,决定要操作的类别
③依据2中的动作进行操作
a.KVM_MR_CREATE: kvm_arch_create_memslot(做了一个3级的页表)
b.KVM_MR_DELETE OR KVM_MR_MOVE:
请求一个slots,把kvm-》memslots暂存到这儿。首要经过id_to_memslot获取预备刺进的内存条对应到kvm的插槽是slot。不管删去仍是移动,将其先标记为KVM_MEMSLOT_INVALID。然后是install_new_memslots,其实便是更新了一下slots-》generaTIon的值。
4. EPT相关
4.1 EPT初始化
kvm_arch_init==》 kvm_mmu_module_init
①树立pte_list_desc_cache缓存结构
②树立mmu_page_header_cache缓存结构,该结构用于kvm_mmu_page
③register_shrinker(&mmu_shrinker);当体系内存收回被调用时的钩子
vcpu_create==》vmx_create_vcpu==》init_rmode_idenTIty_map==》alloc_identity_pagetable==》__x86_set_memory_region
4.2 EPT载入
vcpu_enter_guest(struct kvm_vcpu *vcpu)==》 kvm_mmu_reload(Guest的MMU初始化,为内存虚拟化做预备)==》 kvm_mmu_load==》mmu_topup_memory_caches==》mmu_alloc_roots–》mmu_alloc_direct_roots(依据当时vcpu的分页形式树立 ept顶层页表的办理结构)==》kvm_mmu_sync_roots
4.3 gfn_to_page
该函数处理GPA的页号到HPA的page结构:
gfn_to_page==》gfn_to_pfn==》gfn_to_pfn_memslot==》__gfn_to_pfn_memslot==》__gfn_to_hva_many|hva_to_pfn==》hva_to_pfn_fast|hva_to_pfn_slow
4.4 分配页表
mmu_alloc_roots–》mmu_alloc_direct_roots–》kvm_mmu_get_page–》kvm_mmu_alloc_page
4.5 EPT vm-entry
①KVM_REQ_MMU_RELOAD–》kvm_mmu_unload–》mmu_free_roots
②KVM_REQ_MMU_SYNC–》kvm_mmu_sync_roots–》mmu_sync_roots–》mmu_sync_children–》kvm_sync_page–》__kvm_sync_page
③KVM_REQ_TLB_FLUSH–》kvm_vcpu_flush_tlb–》tlb_flush–》vmx_flush_tlb–》__vmx_flush_tlb–》ept_sync_context–》__invept
进入非根形式下,依据不同事情针对内存做相关处理。
4.6 EPT VM-exit
①设置cr3
mmu_alloc_direct_roots中会分配arch.mmu.root_hpavcpu_enter_guest的时分会调用kvm_mmu_load==》 vcpu-》arch.mmu.set_cr3(vcpu,vcpu-》arch.mmu.root_hpa)这个函数要请求内存,作为根页表运用。一起root_hpa指向根页表的物理地址。然后能够看到,vcpu中cr3寄存器的地址要指向这个根页表的物理地址。
②handle_ept_violation
–》kvm_mmu_page_fault–》arch.mmu.page_fault–》tdp_page_fault
__direct_map 这个函数是依据传进来的gpa进行核算,从第4级(level-4)页表页开端,一级一级地填写相应页表项这些都是在for_each_shadow_entry(vcpu, (u64)gfn 《《 PAGE_SHIFT, iterator) 这个宏界说里边完结的。这两种状况是这姿态的:
a.假如当时页表页的层数(iterator.level )是最终一层( level )的页表页,那么直接经过调用 mmu_set_spte (之后会细讲)设置页表项。
b.假如当时页表页 A 不是最终一层,而是中心某一层(leve-4, level-3, level-2)
并且该页表项之前并没有初始化(!is_shadow_present_pte(*iterator.sptep) )那么需求调用kvm_mmu_get_page 得到或许新建一个页表页 B然后经过 link_shadow_page 将其link到页表页 A 相对应的页表项中
4.7 EPT遍历操作
for_each_shadow_entry这个是界说在mmu.c中的一个宏,用来不断的遍历页表的层级。
4.8 影子页表
init_kvm_mmu==》init_kvm_softmmu
在上述的ept的过程中,依据参数不同会有不同分支大体逻辑保持一致,无须赘言。