當前位置:
首頁 >
Mips KVM TrapEmulate implemented in Linux
發布時間:2025/3/15
49
豆豆
生活随笔
收集整理的這篇文章主要介紹了
Mips KVM TrapEmulate implemented in Linux
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
基本原理
Trap&Emulate,即陷入&模擬的方式,是純軟件實現的全虛擬化方案,基本不借助硬件虛擬化功能。本文主要關注內存虛擬化實現中的核心,TLB miss相關實現。基本原理是:
所有的TLB miss都將導致Guest退出到VMM處理,然后在VMM中進行相應模擬。
具體實現原理描述如下:
-
當前內核版本(4.9)的實現方式定義了2中TLB:
- Guest TLB。由Guest OS維護,用于映射GVA->GPA,本質為一段內存,用于模擬TLB,虛擬TLB,并不真實存在。該TLB不會被Guest OS直接使用,只用于生成Shadow TLB。Guest OS 在維護Guest TLB時,對 TLB 的讀寫指令會被虛擬機管理器(virtual machine monitor, VMM)捕捉并模擬,使 Guest OS 認為成功讀寫了硬件 TLB。
- Shadow Host TLB,也是Host使用的物理TLB。由VMM(即Host維護)。對于Guest來說,用于映射GVA->HPA,對于Host來說,用于映射HVA->HPA。
-
Guest最終通過Shadow Host TLB中的條目實現GVA到HPA的轉換,GPA->HPA的映射通過線性映射表實現。
-
Guest中,任何TLB相關的操作都將觸發異常。
Guest OS做訪存操作時有如下幾中情況發生:
- VMM先遍歷Guest TLB,如果找到相應條目,則將Guest TLB中翻譯后的GPA轉換為HPA,然后填入Shadow Host TLB(硬件TLB),并返回Guest OS繼續運行。
- 如果Guest TLB中沒有找到,則向Guest OS中注入TLB Load/Store異常(由Guest OS負責重填Guest TLB),然后填入Shadow Host TLB(硬件TLB),并返回Guest OS繼續運行。
- 當Guest得到調度運行時,TLB Load/Store異常(General異常)入口,最終會進入page fault相關流程,完成Guest TLB重填。
- 重填TLB時,會調用類似TLBWR之類的之令,此時會再次觸發VM-Exit,VMM通過捕獲該異常,其中填入虛擬的Guest TLB、flust相應的Shadow Host TLB條目,然后返回Guest OS繼續執行。
代碼實現
kvm_mips_handle_tlbmiss
tlbmiss異常通用入口,在如下兩種情況下發生:
主要處理邏輯:
- 查找Guest TLB中是否存在相應entry;
- 如果存在,則調用kvm_mips_handle_mapped_seg_tlb_fault填充shadow Host TLB(即物理TLB);
- 如果不存在,則根據exccode向Guest中注入相應的異常,比如TLB miss load異常,接口如kvm_mips_emulate_tlbmiss_ld。
具體代碼實現如下:
enum emulation_result kvm_mips_handle_tlbmiss(u32 cause,u32 *opc,struct kvm_run *run,struct kvm_vcpu *vcpu) {enum emulation_result er = EMULATE_DONE;/* 讀取異常類型碼 */u32 exccode = (cause >> CAUSEB_EXCCODE) & 0x1f;/* 獲取發送tlbmiss的地址,為GVA地址 */unsigned long va = vcpu->arch.host_cp0_badvaddr;int index;kvm_debug("kvm_mips_handle_tlbmiss: badvaddr: %#lx\n",vcpu->arch.host_cp0_badvaddr);/** KVM would not have got the exception if this entry was valid in the* shadow host TLB. Check the Guest TLB, if the entry is not there then* send the guest an exception. The guest exc handler should then inject* an entry into the guest TLB.*//* 查找Guest TLB(本質為一段內存,虛擬TLB,并不真實存在),確認是否存在 */index = kvm_mips_guest_tlb_lookup(vcpu,(va & VPN2_MASK) |(kvm_read_c0_guest_entryhi(vcpu->arch.cop0) &KVM_ENTRYHI_ASID));/* 不存在 */if (index < 0) {/* 根據異常類型碼向Guest中注入不同的異常 */if (exccode == EXCCODE_TLBL) {/* TLB load異常 */er = kvm_mips_emulate_tlbmiss_ld(cause, opc, run, vcpu);} else if (exccode == EXCCODE_TLBS) {/* TLB set異常 */er = kvm_mips_emulate_tlbmiss_st(cause, opc, run, vcpu);} else {kvm_err("%s: invalid exc code: %d\n", __func__,exccode);er = EMULATE_FAIL;}} else {/* 如果在guest TLB中找到相應條目 *//* 獲取相應條目 */struct kvm_mips_tlb *tlb = &vcpu->arch.guest_tlb[index];/** Check if the entry is valid, if not then setup a TLB invalid* exception to the guest*//* 檢查條目是否可用,不可用,則再注入TLB相應異常 */if (!TLB_IS_VALID(*tlb, va)) {if (exccode == EXCCODE_TLBL) {er = kvm_mips_emulate_tlbinv_ld(cause, opc, run,vcpu);} else if (exccode == EXCCODE_TLBS) {er = kvm_mips_emulate_tlbinv_st(cause, opc, run,vcpu);} else {kvm_err("%s: invalid exc code: %d\n", __func__,exccode);er = EMULATE_FAIL;}} else { /* 條目可用,則填充Host TLB */kvm_debug("Injecting hi: %#lx, lo0: %#lx, lo1: %#lx into shadow host TLB\n",tlb->tlb_hi, tlb->tlb_lo[0], tlb->tlb_lo[1]);/** OK we have a Guest TLB entry, now inject it into the* shadow host TLB*//* Host TLB的實際填充操作在kvm_mips_handle_mapped_seg_tlb_fault函數中完成*/if (kvm_mips_handle_mapped_seg_tlb_fault(vcpu, tlb)) {kvm_err("%s: handling mapped seg tlb fault for %lx, index: %u, vcpu: %p, ASID: %#lx\n",__func__, va, index, vcpu,read_c0_entryhi());er = EMULATE_FAIL;}}}return er;}kvm_mips_emulate_tlbmiss_ld
實現向Guest中注入TLB load異常,本質為:
設置CP0 Status中的ST0_EXL,然后將PC指向General異常的處理入口,Guest得到調度即可直接跳轉到異常入口執行,實現異常注入。
enum emulation_result kvm_mips_emulate_tlbinv_ld(u32 cause,u32 *opc,struct kvm_run *run,struct kvm_vcpu *vcpu) {struct mips_coproc *cop0 = vcpu->arch.cop0;struct kvm_vcpu_arch *arch = &vcpu->arch;unsigned long entryhi =(vcpu->arch.host_cp0_badvaddr & VPN2_MASK) |(kvm_read_c0_guest_entryhi(cop0) & KVM_ENTRYHI_ASID);/** 讀取Guest C0中Status寄存器,判斷ST0_EXL(標識當前是否已經處于異常狀態,* 如果已經是,且再次觸發tlbmiss異常,此時不會重入,而會觸發General異常,* 即TLB load異常,也可以理解成X86中的缺頁異常),如果沒有設置,則需要進行* 相應設置,以便能向Guest中注入TLB load異常 */if ((kvm_read_c0_guest_status(cop0) & ST0_EXL) == 0) {/* save old pc */kvm_write_c0_guest_epc(cop0, arch->pc);/* 設置ST0_EXL標記 */kvm_set_c0_guest_status(cop0, ST0_EXL);if (cause & CAUSEF_BD)kvm_set_c0_guest_cause(cop0, CAUSEF_BD);elsekvm_clear_c0_guest_cause(cop0, CAUSEF_BD);kvm_debug("[EXL == 0] delivering TLB INV @ pc %#lx\n",arch->pc);/* set pc to the exception entry point *//** 設置當前的PC指針指向General異常的處理入口,這樣,當Guest得到調度* 時,就能直接進入General異常處理流程中,其中會最終走到page fault* 相關流程,之前的文章中有相應的原理描述*/arch->pc = KVM_GUEST_KSEG0 + 0x180;} else {/* 如果已經設置了ST0_EXL標記,則直接修改PC指針即可 */kvm_debug("[EXL == 1] delivering TLB MISS @ pc %#lx\n",arch->pc);arch->pc = KVM_GUEST_KSEG0 + 0x180;}/* 寫入EXCCODE為TLBL */kvm_change_c0_guest_cause(cop0, (0xff),(EXCCODE_TLBL << CAUSEB_EXCCODE));/* setup badvaddr, context and entryhi registers for the guest */kvm_write_c0_guest_badvaddr(cop0, vcpu->arch.host_cp0_badvaddr);/* XXXKYMA: is the context register used by linux??? */kvm_write_c0_guest_entryhi(cop0, entryhi);/* Blow away the shadow host TLBs */kvm_mips_flush_host_tlb(1);return EMULATE_DONE; }kvm_mips_handle_mapped_seg_tlb_fault
主要完成任務:根據Guest TLB中的entry,填充Host TLB中相應條目。
int kvm_mips_handle_mapped_seg_tlb_fault(struct kvm_vcpu *vcpu,struct kvm_mips_tlb *tlb) {unsigned long entryhi = 0, entrylo0 = 0, entrylo1 = 0;struct kvm *kvm = vcpu->kvm;kvm_pfn_t pfn0, pfn1;gfn_t gfn0, gfn1;long tlb_lo[2];int ret;tlb_lo[0] = tlb->tlb_lo[0];tlb_lo[1] = tlb->tlb_lo[1];/** The commpage address must not be mapped to anything else if the guest* TLB contains entries nearby, or commpage accesses will break.*/if (!((tlb->tlb_hi ^ KVM_GUEST_COMMPAGE_ADDR) &VPN2_MASK & (PAGE_MASK << 1)))tlb_lo[(KVM_GUEST_COMMPAGE_ADDR >> PAGE_SHIFT) & 1] = 0;/* 獲取gfn */gfn0 = mips3_tlbpfn_to_paddr(tlb_lo[0]) >> PAGE_SHIFT;gfn1 = mips3_tlbpfn_to_paddr(tlb_lo[1]) >> PAGE_SHIFT;if (gfn0 >= kvm->arch.guest_pmap_npages ||gfn1 >= kvm->arch.guest_pmap_npages) {kvm_err("%s: Invalid gfn: [%#llx, %#llx], EHi: %#lx\n",__func__, gfn0, gfn1, tlb->tlb_hi);kvm_mips_dump_guest_tlbs(vcpu);return -1;}/** 建立gfn和pfn的映射關系,本質是通memslot中獲取對應關系,然后設置guest_pmap線性* 映射數組,后面使用*/if (kvm_mips_map_page(kvm, gfn0) < 0)return -1;if (kvm_mips_map_page(kvm, gfn1) < 0)return -1;/* 獲取pfn */pfn0 = kvm->arch.guest_pmap[gfn0];pfn1 = kvm->arch.guest_pmap[gfn1];/* Get attributes from the Guest TLB *//* 從Guest TLB中獲取entrylo0、entrylo1、entryhi等關鍵信息,以便于后續將其寫入Host TLB */entrylo0 = mips3_paddr_to_tlbpfn(pfn0 << PAGE_SHIFT) |((_page_cachable_default >> _CACHE_SHIFT) << ENTRYLO_C_SHIFT) |(tlb_lo[0] & ENTRYLO_D) |(tlb_lo[0] & ENTRYLO_V);entrylo1 = mips3_paddr_to_tlbpfn(pfn1 << PAGE_SHIFT) |((_page_cachable_default >> _CACHE_SHIFT) << ENTRYLO_C_SHIFT) |(tlb_lo[1] & ENTRYLO_D) |(tlb_lo[1] & ENTRYLO_V);kvm_debug("@ %#lx tlb_lo0: 0x%08lx tlb_lo1: 0x%08lx\n", vcpu->arch.pc,tlb->tlb_lo[0], tlb->tlb_lo[1]);preempt_disable();entryhi = (tlb->tlb_hi & VPN2_MASK) | (KVM_GUEST_KERNEL_MODE(vcpu) ?kvm_mips_get_kernel_asid(vcpu) :kvm_mips_get_user_asid(vcpu));/* 將TLB Entry信息寫入Host TLB,即物理TLB */ret = kvm_mips_host_tlb_write(vcpu, entryhi, entrylo0, entrylo1,tlb->tlb_mask);preempt_enable();return ret; }kvm_mips_map_page
建立gfn和pfn的映射關系,本質是通memslot中獲取對應關系,然后設置guest_pmap線性映射數組
static int kvm_mips_map_page(struct kvm *kvm, gfn_t gfn) {int srcu_idx, err = 0;kvm_pfn_t pfn;/* 已經存在映射 */if (kvm->arch.guest_pmap[gfn] != KVM_INVALID_PAGE)return 0;srcu_idx = srcu_read_lock(&kvm->srcu);/* 將gfn轉換為pfn,通過memslot實現,kvm標準接口 */pfn = gfn_to_pfn(kvm, gfn);if (is_error_noslot_pfn(pfn)) {kvm_err("Couldn't get pfn for gfn %#llx!\n", gfn);err = -EFAULT;goto out;}/* 填入映射關系 */kvm->arch.guest_pmap[gfn] = pfn; out:srcu_read_unlock(&kvm->srcu, srcu_idx);return err; } 原文地址: https://happyseeker.github.io/kernel/2017/01/11/Mips-KVM-Trap&E-implement.html總結
以上是生活随笔為你收集整理的Mips KVM TrapEmulate implemented in Linux的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Tramp data In Kernel
- 下一篇: Mips TLB miss实现in Li