摘要:本文介紹了MMU虛實映射的基本概念,運行機制,分析了映射初始化、映射查詢、映射虛擬內(nèi)存和物理內(nèi)存,解除虛實映射,更改映射屬性,重新映射等常用接口的代碼。
本文分享自華為云社區(qū)??《使用MRS CDL實現(xiàn)實時數(shù)據(jù)同步的極致性能》??,作者: zhushy 。
虛實映射是指系統(tǒng)通過內(nèi)存管理單元(MMU,MemoryManagement Unit)將進程空間的虛擬地址(VA)與實際的物理地址(PA)做映射,并指定相應(yīng)的訪問權(quán)限、緩存屬性等。程序執(zhí)行時,CPU訪問的是虛擬內(nèi)存,通過MMU找到映射的物理內(nèi)存,并做相應(yīng)的代碼執(zhí)行或數(shù)據(jù)讀寫操作。MMU的映射由頁表(Page Table)來描述,其中保存虛擬地址和物理地址的映射關(guān)系以及訪問權(quán)限等。每個進程在創(chuàng)建的時候都會創(chuàng)建一個頁表,頁表由一個個頁表條目(Page Table Entry, PTE)構(gòu)成,每個頁表條目描述虛擬地址區(qū)間與物理地址區(qū)間的映射關(guān)系。頁表數(shù)據(jù)在內(nèi)存區(qū)域存儲位置的開始地址叫做轉(zhuǎn)換表基地址(translation table base,ttb)。MMU中有一塊頁表緩存,稱為快表(TLB, Translation LookasideBuffers),做地址轉(zhuǎn)換時,MMU首先在TLB中查找,如果找到對應(yīng)的頁表條目可直接進行轉(zhuǎn)換,提高了查詢效率。
本文中所涉及的源碼,以O(shè)penHarmonyLiteOS-A內(nèi)核為例,均可以在開源站點??https://gitee.com/openharmony/kernel_liteos_a?? 獲取。如果涉及開發(fā)板,則默認以hispark_taurus為例。MMU相關(guān)的操作函數(shù)主要在文件arch/arm/arm/src/los_arch_mmu.c中定義。
虛實映射其實就是一個建立頁表的過程。MMU支持多級頁表,LiteOS-A內(nèi)核采用二級頁表描述進程空間。首先介紹下一級頁表和二級頁表。
1、一級頁表L1和二級頁表L2
L1頁表將全部的4GiB地址空間劃分為4096份,每份大小1MiB。每份對應(yīng)一個32位的頁表項,內(nèi)容是L2頁表基地址或某個1MiB物理內(nèi)存的基地址。內(nèi)存的高12位記錄頁號,用于對頁表項定位,也就是4096個頁面項的索引;低20位記錄頁內(nèi)偏移值,虛實地址頁內(nèi)偏移值相等。使用虛擬地址中的虛擬頁號查詢頁表得到對應(yīng)的物理頁號,然后與虛擬地址中的頁內(nèi)位移組成物理地址。
對于用戶進程,每個一級頁表條目描述符占用4個字節(jié),可表示1MiB的內(nèi)存空間的映射關(guān)系,即1GiB用戶空間(LiteOS-A內(nèi)核中用戶空間占用1GiB)的虛擬內(nèi)存空間需要1024個。系統(tǒng)創(chuàng)建用戶進程時,在內(nèi)存中申請一塊4KiB大小的內(nèi)存塊作為一級頁表的存儲區(qū)域,系統(tǒng)根據(jù)當前進程的需要會動態(tài)申請內(nèi)存作為二級頁表的存儲區(qū)域。現(xiàn)在我們就知道,在虛擬內(nèi)存章節(jié),用戶進程虛擬地址空間初始化函數(shù)OsCreateUserVmSpace申請了4KiB的內(nèi)存作為頁表存儲區(qū)域的依據(jù)了。每個用戶進程需要申請字節(jié)的頁表地址,對于內(nèi)核進程,頁表存儲區(qū)域是固定的,即UINT8g_firstPageTable[0x4000],大小為16KiB。
L1頁表項的低2位用于定義頁表項的類型,頁表描述符類型有如下3種:
- Invalid 無效頁表項,虛擬地址沒有映射到物理地址,訪問會產(chǎn)生缺頁異常;
- Page Table 指向L2頁表的頁表項;
- Section Section頁表項對應(yīng)1M的節(jié),直接使用頁表項的最高12位替代虛擬地址的高12位即可得到物理地址。
L2頁表把1MiB的地址范圍按4KiB的內(nèi)存頁大小繼續(xù)分成256個小頁。內(nèi)存的高20位記錄頁號,用于對頁表項定位;低12位記錄頁內(nèi)偏移值,虛實地址頁內(nèi)偏移值相等。使用虛擬地址中的虛擬頁號查詢頁表得到對應(yīng)的物理頁號,然后與虛擬地址中的頁內(nèi)位移組成物理地址。每個L2頁表項將4K的虛擬內(nèi)存地址轉(zhuǎn)換為物理地址。
L2頁表描述符類型有如下4種:
- Invalid 無效頁表項,虛擬地址沒有映射到物理地址,訪問會產(chǎn)生缺頁異常;
- Large Page 大頁表項,支持64Kib大頁,暫不支持;
- Small Page 小頁表項,支持4Kib小頁的二級頁表映射;
- Small Page XN 小頁表項擴展。
在文件arch/arm/arm/include/los_mmu_descriptor_v6.h中定義了頁表的描述符類型,代碼如下:
/* L1 descriptor type */ #define MMU_DESCRIPTOR_L1_TYPE_INVALID (0x0 << 0) #define MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE (0x1 << 0) #define MMU_DESCRIPTOR_L1_TYPE_SECTION (0x2 << 0) #define MMU_DESCRIPTOR_L1_TYPE_MASK (0x3 << 0) /* L2 descriptor type */ #define MMU_DESCRIPTOR_L2_TYPE_INVALID (0x0 << 0) #define MMU_DESCRIPTOR_L2_TYPE_LARGE_PAGE (0x1 << 0) #define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE (0x2 << 0) #define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE_XN (0x3 << 0) #define MMU_DESCRIPTOR_L2_TYPE_MASK (0x3 << 0)
1.2 頁表項操作
在文件arch/arm/arm/include/los_pte_ops.h定義了頁表項相關(guān)的操作。
1.2.1 函數(shù)OsGetPte1
函數(shù)OsGetPte1用于獲取指定虛擬地址對應(yīng)的L1頁表項地址。L1頁表項地址由頁表項基地址加上頁表項索引組成,其中頁表項索引等于虛擬地址的高12位。
STATIC INLINE UINT32 OsGetPte1Index(vaddr_t va) { return va >> MMU_DESCRIPTOR_L1_SMALL_SHIFT; } STATIC INLINE PTE_T *OsGetPte1Ptr(PTE_T *pte1BasePtr, vaddr_t va) { return (pte1BasePtr + OsGetPte1Index(va)); } STATIC INLINE PTE_T OsGetPte1(PTE_T *pte1BasePtr, vaddr_t va) { return *OsGetPte1Ptr(pte1BasePtr, va); }
1.2.2 函數(shù)OsGetPte2
函數(shù)OsGetPte2用于獲取指定虛擬地址對應(yīng)的L2頁表項地址。L2頁表項地址由頁表項基地址加上頁表項索引組成,其中頁表項索引等于虛擬地址對1MiB取余后的高20位。(為啥va % MMU_DESCRIPTOR_L1_SMALL_SIZE取余?TODO)。
STATIC INLINE UINT32 OsGetPte2Index(vaddr_t va) { return (va % MMU_DESCRIPTOR_L1_SMALL_SIZE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT; } STATIC INLINE PTE_T OsGetPte2(PTE_T *pte2BasePtr, vaddr_t va) { return *(pte2BasePtr + OsGetPte2Index(va)); }
2、虛擬映射初始化
在文件kernel/base/vm/los_vm_boot.c的系統(tǒng)內(nèi)存初始化函數(shù)OsSysMemInit()會調(diào)用虛實映射初始化函數(shù)OsInitMappingStartUp()。代碼定義在arch/arm/arm/src/los_arch_mmu.c,代碼如下。⑴處函數(shù)使TLB失效,涉及些cp15寄存器和匯編,后續(xù)再分析。⑵處函數(shù)切換到臨時TTV。⑶處設(shè)置內(nèi)核地址空間的映射。下面分別詳細這些函數(shù)代碼。
VOID OsInitMappingStartUp(VOID) { ⑴ OsArmInvalidateTlbBarrier(); ⑵ OsSwitchTmpTTB(); ⑶ OsSetKSectionAttr(KERNEL_VMM_BASE, FALSE); OsSetKSectionAttr(UNCACHED_VMM_BASE, TRUE); OsKSectionNewAttrEnable(); }
2.1 函數(shù)OsSwitchTmpTTB
⑴處獲取內(nèi)核地址空間。L1頁表項由4096個頁表項組成,每個4Kib,共需要16Kib大小。所以⑵處代碼按16Kib對齊申請16Kib大小的內(nèi)存存放L1頁表項。⑶處設(shè)置內(nèi)核虛擬內(nèi)存地址空間的轉(zhuǎn)換表基地址(translation table base,ttb)。⑷處把g_firstPageTable數(shù)據(jù)復制到內(nèi)核地址空間的轉(zhuǎn)換表。如果復制失敗,則直接使用g_firstPageTable。⑸處設(shè)置內(nèi)核虛擬地址空間的物理內(nèi)存基地址,然后寫入MMU寄存器。
STATIC VOID OsSwitchTmpTTB(VOID) { PTE_T *tmpTtbase = NULL; errno_t err; ⑴ LosVmSpace *kSpace = LOS_GetKVmSpace(); /* ttbr address should be 16KByte align */ ⑵ tmpTtbase = LOS_MemAllocAlign(m_aucSysMem0, MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS, MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS); if (tmpTtbase == NULL) { VM_ERR("memory alloc failed"); return; } ⑶ kSpace->archMmu.virtTtb = tmpTtbase; ⑷ err = memcpy_s(kSpace->archMmu.virtTtb, MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS, g_firstPageTable, MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS); if (err != EOK) { (VOID)LOS_MemFree(m_aucSysMem0, tmpTtbase); kSpace->archMmu.virtTtb = (VADDR_T *)g_firstPageTable; VM_ERR("memcpy failed, errno: %d", err); return; } ⑸ kSpace->archMmu.physTtb = LOS_PaddrQuery(kSpace->archMmu.virtTtb); OsArmWriteTtbr0(kSpace->archMmu.physTtb | MMU_TTBRx_FLAGS); ISB; }
2.2 函數(shù)OsSetKSectionAttr
內(nèi)部函數(shù)OsSetKSectionAttr用與設(shè)置內(nèi)核虛擬地址空間段的屬性,分別針對[KERNEL_ASPACE_BASE,KERNEL_ASPACE_BASE+KERNEL_ASPACE_SIZE]和[UNCACHED_VMM_BASE,UNCACHED_VMM_BASE+UNCACHED_VMM_SIZE]進行設(shè)置。內(nèi)核虛擬地址空間是固定映射到物理內(nèi)存的。
⑴處計算相對內(nèi)核虛擬地址空間基地址的偏移。⑵處先計算相對偏移值的text、rodata、data_bss段的虛擬內(nèi)存地址,然后創(chuàng)建這些段的虛實映射關(guān)系。⑶處設(shè)置內(nèi)核虛擬地址區(qū)間的虛擬轉(zhuǎn)換基地址和物理轉(zhuǎn)換基地址。然后解除虛擬地址的虛實映射。⑷處按指定的標簽對text段之前的內(nèi)存區(qū)間進行虛實映射。⑸處映射text、rodata、data_bss段的內(nèi)存區(qū)間,并調(diào)用函數(shù)LOS_VmSpaceReserve在進程空間中保留一段地址區(qū)間(為啥保留 TODO?)。⑹是BSS段后面的heap區(qū),映射虛擬地址空間的內(nèi)存堆區(qū)間。
STATIC VOID OsSetKSectionAttr(UINTPTR virtAddr, BOOL uncached) { ⑴ UINT32 offset = virtAddr - KERNEL_VMM_BASE; /* every section should be page aligned */ ⑵ UINTPTR textStart = (UINTPTR)&__text_start + offset; UINTPTR textEnd = (UINTPTR)&__text_end + offset; UINTPTR rodataStart = (UINTPTR)&__rodata_start + offset; UINTPTR rodataEnd = (UINTPTR)&__rodata_end + offset; UINTPTR ramDataStart = (UINTPTR)&__ram_data_start + offset; UINTPTR bssEnd = (UINTPTR)&__bss_end + offset; UINT32 bssEndBoundary = ROUNDUP(bssEnd, MB); LosArchMmuInitMapping mmuKernelMappings[] = { { .phys = SYS_MEM_BASE + textStart - virtAddr, .virt = textStart, .size = ROUNDUP(textEnd - textStart, MMU_DESCRIPTOR_L2_SMALL_SIZE), .flags = VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_EXECUTE, .name = "kernel_text" }, { .phys = SYS_MEM_BASE + rodataStart - virtAddr, .virt = rodataStart, .size = ROUNDUP(rodataEnd - rodataStart, MMU_DESCRIPTOR_L2_SMALL_SIZE), .flags = VM_MAP_REGION_FLAG_PERM_READ, .name = "kernel_rodata" }, { .phys = SYS_MEM_BASE + ramDataStart - virtAddr, .virt = ramDataStart, .size = ROUNDUP(bssEndBoundary - ramDataStart, MMU_DESCRIPTOR_L2_SMALL_SIZE), .flags = VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE, .name = "kernel_data_bss" } }; LosVmSpace *kSpace = LOS_GetKVmSpace(); status_t status; UINT32 length; int i; LosArchMmuInitMapping *kernelMap = NULL; UINT32 kmallocLength; UINT32 flags; /* use second-level mapping of default READ and WRITE */ ⑶ kSpace->archMmu.virtTtb = (PTE_T *)g_firstPageTable; kSpace->archMmu.physTtb = LOS_PaddrQuery(kSpace->archMmu.virtTtb); status = LOS_ArchMmuUnmap(&kSpace->archMmu, virtAddr, (bssEndBoundary - virtAddr) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT); if (status != ((bssEndBoundary - virtAddr) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) { VM_ERR("unmap failed, status: %d", status); return; } flags = VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE | VM_MAP_REGION_FLAG_PERM_EXECUTE; if (uncached) { flags |= VM_MAP_REGION_FLAG_UNCACHED; } ⑷ status = LOS_ArchMmuMap(&kSpace->archMmu, virtAddr, SYS_MEM_BASE, (textStart - virtAddr) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT, flags); if (status != ((textStart - virtAddr) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) { VM_ERR("mmap failed, status: %d", status); return; } ⑸ length = sizeof(mmuKernelMappings) / sizeof(LosArchMmuInitMapping); for (i = 0; i < length; i++) { kernelMap = &mmuKernelMappings[i]; if (uncached) { kernelMap->flags |= VM_MAP_REGION_FLAG_UNCACHED; } status = LOS_ArchMmuMap(&kSpace->archMmu, kernelMap->virt, kernelMap->phys, kernelMap->size >> MMU_DESCRIPTOR_L2_SMALL_SHIFT, kernelMap->flags); if (status != (kernelMap->size >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) { VM_ERR("mmap failed, status: %d", status); return; } LOS_VmSpaceReserve(kSpace, kernelMap->size, kernelMap->virt); } ⑹ kmallocLength = virtAddr + SYS_MEM_SIZE_DEFAULT - bssEndBoundary; flags = VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE; if (uncached) { flags |= VM_MAP_REGION_FLAG_UNCACHED; } status = LOS_ArchMmuMap(&kSpace->archMmu, bssEndBoundary, SYS_MEM_BASE + bssEndBoundary - virtAddr, kmallocLength >> MMU_DESCRIPTOR_L2_SMALL_SHIFT, flags); if (status != (kmallocLength >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) { VM_ERR("mmap failed, status: %d", status); return; } LOS_VmSpaceReserve(kSpace, kmallocLength, bssEndBoundary); }
2.3 函數(shù)OsKSectionNewAttrEnable
函數(shù)OsKSectionNewAttrEnable釋放臨時TTB。代碼看不懂TODO 以后慢慢看。⑴處獲取內(nèi)核虛擬進程空間,⑵處設(shè)置進程空間MMU的虛擬地址轉(zhuǎn)化表基地址TTB,設(shè)置物理內(nèi)存地址轉(zhuǎn)換表基地址。⑶處從CP15 C2寄存器讀取TTB地址,取高20位。⑷處將內(nèi)核頁表基地址(邏輯與的什么?TODO)寫入CP15 c2 TTB寄存器。⑸處清空TLB緩沖區(qū),然后釋放內(nèi)存。
STATIC VOID OsKSectionNewAttrEnable(VOID) { ⑴ LosVmSpace *kSpace = LOS_GetKVmSpace(); paddr_t oldTtPhyBase; ⑵ kSpace->archMmu.virtTtb = (PTE_T *)g_firstPageTable; kSpace->archMmu.physTtb = LOS_PaddrQuery(kSpace->archMmu.virtTtb); /* we need free tmp ttbase */ ⑶ oldTtPhyBase = OsArmReadTtbr0(); oldTtPhyBase = oldTtPhyBase & MMU_DESCRIPTOR_L2_SMALL_FRAME; ⑷ OsArmWriteTtbr0(kSpace->archMmu.physTtb | MMU_TTBRx_FLAGS); ISB; /* we changed page table entry, so we need to clean TLB here */ ⑸ OsCleanTLB(); (VOID)LOS_MemFree(m_aucSysMem0, (VOID *)(UINTPTR)(oldTtPhyBase - SYS_MEM_BASE + KERNEL_VMM_BASE)); }
3、虛實映射函數(shù)LOS_ArchMmuMap
虛實映射的知識點TODO。
3.1 函數(shù)LOS_ArchMmuMap
函數(shù)LOS_ArchMmuMap用于映射進程空間虛擬地址區(qū)間與物理地址區(qū)間,其中輸入?yún)?shù)archMmu為MMU配置結(jié)構(gòu)體,vaddr和paddr分別是虛擬內(nèi)存和物理內(nèi)存的開始地址;count為虛擬地址和物理地址映射的數(shù)量;flags為映射標簽。⑴處進行函數(shù)參數(shù)校驗,不支持NON-SECURE的標記,虛擬地址和物理地址需要內(nèi)存頁4KiB對齊。⑵處當虛擬地址、物理地址基于1MiB對齊,并且數(shù)量count大于256時,使用Section頁表項格式。⑶處生成L1 section類型頁表項并保存,下文詳細分析該函數(shù)。如果不滿足⑵處條件,需要使用L2映射。首先執(zhí)行⑷處獲取虛擬地址對應(yīng)的L1頁表項,接著執(zhí)行⑸處判斷是否映射,如果沒有對應(yīng)的映射,則執(zhí)行⑹處的函數(shù)OsMapL1PTE生成L1 pagetable類型頁表項并保存,然后執(zhí)行函數(shù)OsMapL2PageContinous生成L2 頁表項目并保存。如果已經(jīng)映射為L1 page table頁表項類型,則重新映射。如果不是支持的頁表項類型,則執(zhí)行LOS_Panic()觸發(fā)異常。⑺處統(tǒng)計生成映射的調(diào)試,最終會返回映射成功的數(shù)量。
status_t LOS_ArchMmuMap(LosArchMmu *archMmu, VADDR_T vaddr, PADDR_T paddr, size_t count, UINT32 flags) { PTE_T l1Entry; UINT32 saveCounts = 0; INT32 mapped = 0; INT32 checkRst; ⑴ checkRst = OsMapParamCheck(flags, vaddr, paddr); if (checkRst < 0) { return checkRst; } /* see what kind of mapping we can use */ while (count > 0) { ⑵ if (MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(vaddr) && MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(paddr) && count >= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1) { /* compute the arch flags for L1 sections cache, r ,w ,x, domain and type */ ⑶ saveCounts = OsMapSection(archMmu, flags, &vaddr, &paddr, &count); } else { /* have to use a L2 mapping, we only allocate 4KB for L1, support 0 ~ 1GB */ ⑷ l1Entry = OsGetPte1(archMmu->virtTtb, vaddr); ⑸ if (OsIsPte1Invalid(l1Entry)) { ⑹ OsMapL1PTE(archMmu, &l1Entry, vaddr, flags); saveCounts = OsMapL2PageContinous(l1Entry, flags, &vaddr, &paddr, &count); } else if (OsIsPte1PageTable(l1Entry)) { saveCounts = OsMapL2PageContinous(l1Entry, flags, &vaddr, &paddr, &count); } else { LOS_Panic("%s %d, unimplemented tt_entry %x/n", __FUNCTION__, __LINE__, l1Entry); } } ⑺ mapped += saveCounts; } return mapped; }
3.2 函數(shù)OsMapSection
函數(shù)OsMapSection生成L1section類型頁表項并保存。⑴處轉(zhuǎn)換為MMU標簽。 ⑵處內(nèi)聯(lián)函數(shù)OsGetPte1Ptr(archMmu->virtTtb,*vaddr)用于獲取虛擬地址對應(yīng)的頁表項索引地址,等于頁表項基地址加上虛擬地址的高20位;OsTruncPte1(*paddr) | mmuFlags |MMU_DESCRIPTOR_L1_TYPE_SECTION)為虛擬地址的高12位+MMU標簽+頁表項Section類型值。該行語句的作用是把虛擬地址和物理地理映射,映射關(guān)系維護在頁表項。⑶處把虛擬地址和物理地址增加1MiB的大小,映射數(shù)量減去256。
STATIC UINT32 OsMapSection(const LosArchMmu *archMmu, UINT32 flags, VADDR_T *vaddr, PADDR_T *paddr, UINT32 *count) { UINT32 mmuFlags = 0; ⑴ mmuFlags |= OsCvtSecFlagsToAttrs(flags); ⑵ OsSavePte1(OsGetPte1Ptr(archMmu->virtTtb, *vaddr), OsTruncPte1(*paddr) | mmuFlags | MMU_DESCRIPTOR_L1_TYPE_SECTION); ⑶ *count -= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1; *vaddr += MMU_DESCRIPTOR_L1_SMALL_SIZE; *paddr += MMU_DESCRIPTOR_L1_SMALL_SIZE; return MMU_DESCRIPTOR_L2_NUMBERS_PER_L1; }
3.3 函數(shù)OsGetL2Table
函數(shù)OsGetL2Table用于生成L2頁表,函數(shù)參數(shù)中archMmu是MMU,l1Index是L1頁表項,ppa屬于輸出參數(shù),保存L2頁表項基地址。⑴處計算L2頁表項偏移值(為啥這么計算 看不懂 TODO)。⑵處查詢遍歷是否存在L2頁表,⑶處獲取頁表項基地址,然后判斷是否頁表類型,如果是則返回L2頁表項基地址。
如果沒有存在的頁表,則為L2頁表申請內(nèi)存,如果支持虛擬地址,執(zhí)行⑷使用LOS_PhysPageAlloc申請內(nèi)存頁;如果不支持虛擬地址,執(zhí)行⑸使用LOS_MemAlloc申請內(nèi)存。⑹處轉(zhuǎn)換為物理地址,然后返回L2頁表項基地址。
STATIC STATUS_T OsGetL2Table(LosArchMmu *archMmu, UINT32 l1Index, paddr_t *ppa) { UINT32 index; PTE_T ttEntry; VADDR_T *kvaddr = NULL; ⑴ UINT32 l2Offset = (MMU_DESCRIPTOR_L2_SMALL_SIZE / MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE) * (l1Index & (MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE - 1)); /* lookup an existing l2 page table */ ⑵ for (index = 0; index < MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE; index++) { ⑶ ttEntry = archMmu->virtTtb[ROUNDDOWN(l1Index, MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE) + index]; if ((ttEntry & MMU_DESCRIPTOR_L1_TYPE_MASK) == MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE) { *ppa = (PADDR_T)ROUNDDOWN(MMU_DESCRIPTOR_L1_PAGE_TABLE_ADDR(ttEntry), MMU_DESCRIPTOR_L2_SMALL_SIZE) + l2Offset; return LOS_OK; } } #ifdef LOSCFG_KERNEL_VM /* not found: allocate one (paddr) */ ⑷ LosVmPage *vmPage = LOS_PhysPageAlloc(); if (vmPage == NULL) { VM_ERR("have no memory to save l2 page"); return LOS_ERRNO_VM_NO_MEMORY; } LOS_ListAdd(&archMmu->ptList, &vmPage->node); kvaddr = OsVmPageToVaddr(vmPage); #else ⑸ kvaddr = LOS_MemAlloc(OS_SYS_MEM_ADDR, MMU_DESCRIPTOR_L2_SMALL_SIZE); if (kvaddr == NULL) { VM_ERR("have no memory to save l2 page"); return LOS_ERRNO_VM_NO_MEMORY; } #endif (VOID)memset_s(kvaddr, MMU_DESCRIPTOR_L2_SMALL_SIZE, 0, MMU_DESCRIPTOR_L2_SMALL_SIZE); /* get physical address */ ⑹ *ppa = LOS_PaddrQuery(kvaddr) + l2Offset; return LOS_OK; }
3.4 函數(shù)OsMapL1PTE
函數(shù)OsMapL1PTE用于生成L1page table類型頁表項并保存,其中函數(shù)參數(shù)pte1Ptr是L1頁表項基地址。⑴處獲取L2頁表項基地址, ⑵處把L2頁表項基地址加上描述符類型賦值給L1頁表項基地址。⑶設(shè)置標簽,⑷處保存頁表項基地址。
STATIC VOID OsMapL1PTE(LosArchMmu *archMmu, PTE_T *pte1Ptr, vaddr_t vaddr, UINT32 flags) { paddr_t pte2Base = 0; ⑴ if (OsGetL2Table(archMmu, OsGetPte1Index(vaddr), &pte2Base) != LOS_OK) { LOS_Panic("%s %d, failed to allocate pagetable/n", __FUNCTION__, __LINE__); } ⑵ *pte1Ptr = pte2Base | MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE; ⑶ if (flags & VM_MAP_REGION_FLAG_NS) { *pte1Ptr |= MMU_DESCRIPTOR_L1_PAGETABLE_NON_SECURE; } *pte1Ptr &= MMU_DESCRIPTOR_L1_SMALL_DOMAIN_MASK; *pte1Ptr |= MMU_DESCRIPTOR_L1_SMALL_DOMAIN_CLIENT; // use client AP ⑷ OsSavePte1(OsGetPte1Ptr(archMmu->virtTtb, vaddr), *pte1Ptr); }
4、虛實映射查詢函數(shù)LOS_ArchMmuQuery
4.1 函數(shù)LOS_ArchMmuQuery
函數(shù)LOS_ArchMmuQuery用于獲取進程空間虛擬地址對應(yīng)的物理地址以及映射屬性,其中輸入?yún)?shù)為虛擬地址vaddr,輸出參數(shù)為物理地址*paddr和標簽*flags。⑴處獲取虛擬地址對應(yīng)的頁表項。⑵處如果虛擬地址對應(yīng)的頁表項描述符類型無效,返回錯誤碼。⑶處如果頁表項描述符類型為Section,則執(zhí)行⑷獲取映射的物理地址,其MMU_DESCRIPTOR_L1_SECTION_ADDR(l1Entry)為頁表項的高12位,(vaddr& (MMU_DESCRIPTOR_L1_SMALL_SIZE - 1))為虛擬地址的低20位,即頁內(nèi)偏移值。⑸處獲取映射的標簽值。
虛擬地址對應(yīng)的頁表項描述符類型為頁表Page Table,則執(zhí)行⑹調(diào)用內(nèi)聯(lián)函數(shù)OsGetPte2BasePtr()計算L2頁表項基地址,計算方法為:取頁表項的高22位,低10位置0,轉(zhuǎn)化為虛擬地址。⑺處計算虛擬地址對應(yīng)的L2頁表項數(shù)值。如果L2頁表項描述符類型為小頁,則執(zhí)行⑻計算物理地址,然后計算相應(yīng)的標簽值。⑼處表示當前輕內(nèi)核還不支持大頁類型。
STATUS_T LOS_ArchMmuQuery(const LosArchMmu *archMmu, VADDR_T vaddr, PADDR_T *paddr, UINT32 *flags) { ⑴ PTE_T l1Entry = OsGetPte1(archMmu->virtTtb, vaddr); PTE_T l2Entry; PTE_T* l2Base = NULL; ⑵ if (OsIsPte1Invalid(l1Entry)) { return LOS_ERRNO_VM_NOT_FOUND; ⑶ } else if (OsIsPte1Section(l1Entry)) { if (paddr != NULL) { ⑷ *paddr = MMU_DESCRIPTOR_L1_SECTION_ADDR(l1Entry) + (vaddr & (MMU_DESCRIPTOR_L1_SMALL_SIZE - 1)); } if (flags != NULL) { ⑸ OsCvtSecAttsToFlags(l1Entry, flags); } } else if (OsIsPte1PageTable(l1Entry)) { ⑹ l2Base = OsGetPte2BasePtr(l1Entry); if (l2Base == NULL) { return LOS_ERRNO_VM_NOT_FOUND; } ⑺ l2Entry = OsGetPte2(l2Base, vaddr); if (OsIsPte2SmallPage(l2Entry) || OsIsPte2SmallPageXN(l2Entry)) { if (paddr != NULL) { ⑻ *paddr = MMU_DESCRIPTOR_L2_SMALL_PAGE_ADDR(l2Entry) + (vaddr & (MMU_DESCRIPTOR_L2_SMALL_SIZE - 1)); } if (flags != NULL) { OsCvtPte2AttsToFlags(l1Entry, l2Entry, flags); } ⑼ } else if (OsIsPte2LargePage(l2Entry)) { LOS_Panic("%s %d, large page unimplemented/n", __FUNCTION__, __LINE__); } else { return LOS_ERRNO_VM_NOT_FOUND; } } return LOS_OK; }
5、虛實映射解除函LOS_ArchMmuUnmap
虛實映射解除函數(shù)LOS_ArchMmuUnmap解除進程空間虛擬地址區(qū)間與物理地址區(qū)間的映射關(guān)系。 ⑴處函數(shù)OsGetPte1用于獲取指定虛擬地址對應(yīng)的L1頁表項地址。⑵處計算需要解除的無效映射的數(shù)量。如果頁表描述符映射類型為Section,并且映射的數(shù)量超過256,則執(zhí)行⑶解除映射Section。如果頁表描述符映射類型為Page Table,則執(zhí)行⑷先解除二級頁表映射,然后解除一級頁表映射,涉及的2個函數(shù)后文詳細分析。⑹處函數(shù)使TLB失效,涉及些cp15寄存器和匯編,后續(xù)再分析。
STATUS_T LOS_ArchMmuUnmap(LosArchMmu *archMmu, VADDR_T vaddr, size_t count) { PTE_T l1Entry; INT32 unmapped = 0; UINT32 unmapCount = 0; while (count > 0) { ⑴ l1Entry = OsGetPte1(archMmu->virtTtb, vaddr); if (OsIsPte1Invalid(l1Entry)) { ⑵ unmapCount = OsUnmapL1Invalid(&vaddr, &count); } else if (OsIsPte1Section(l1Entry)) { if (MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(vaddr) && count >= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1) { ⑶ unmapCount = OsUnmapSection(archMmu, &vaddr, &count); } else { LOS_Panic("%s %d, unimplemented/n", __FUNCTION__, __LINE__); } } else if (OsIsPte1PageTable(l1Entry)) { ⑷ unmapCount = OsUnmapL2PTE(archMmu, vaddr, &count); OsTryUnmapL1PTE(archMmu, vaddr, OsGetPte2Index(vaddr) + unmapCount, MMU_DESCRIPTOR_L2_NUMBERS_PER_L1 - unmapCount); ⑸ vaddr += unmapCount << MMU_DESCRIPTOR_L2_SMALL_SHIFT; } else { LOS_Panic("%s %d, unimplemented/n", __FUNCTION__, __LINE__); } unmapped += unmapCount; } ⑹ OsArmInvalidateTlbBarrier(); return unmapped; }
5.1 函數(shù)OsUnmapL1Invalid
函數(shù)OsUnmapL1Invalid用于解除無效的映射,會把虛擬地址增加,映射的數(shù)量減少。⑴處的MMU_DESCRIPTOR_L1_SMALL_SIZE表示1MiB大小,*vaddr% MMU_DESCRIPTOR_L1_SMALL_SIZE對1MiB取余,向右偏移12位>>MMU_DESCRIPTOR_L2_SMALL_SHIFT表示大小轉(zhuǎn)換為內(nèi)存頁數(shù)量。(為啥相減TODO?)。⑵處把解除映射的內(nèi)存頁數(shù)量左移12位轉(zhuǎn)換為地址長度,然后更新虛擬地址。⑶處減去已經(jīng)解除映射的數(shù)量。
STATIC INLINE UINT32 OsUnmapL1Invalid(vaddr_t *vaddr, UINT32 *count) { UINT32 unmapCount; ⑴ unmapCount = MIN2((MMU_DESCRIPTOR_L1_SMALL_SIZE - (*vaddr % MMU_DESCRIPTOR_L1_SMALL_SIZE)) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT, *count); ⑵ *vaddr += unmapCount << MMU_DESCRIPTOR_L2_SMALL_SHIFT; ⑶ *count -= unmapCount; return unmapCount; }
5.2 函數(shù)OsUnmapSection
函數(shù)OsUnmapSection用于接觸一級頁表的Section映射。⑴處把虛擬地址對應(yīng)的頁表項基地址設(shè)置為0。⑵處使TLB寄存器失效,⑶更新虛擬地址和映射數(shù)量。
STATIC UINT32 OsUnmapSection(LosArchMmu *archMmu, vaddr_t *vaddr, UINT32 *count) { ⑴ OsClearPte1(OsGetPte1Ptr((PTE_T *)archMmu->virtTtb, *vaddr)); ⑵ OsArmInvalidateTlbMvaNoBarrier(*vaddr); ⑶ *vaddr += MMU_DESCRIPTOR_L1_SMALL_SIZE; *count -= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1; return MMU_DESCRIPTOR_L2_NUMBERS_PER_L1; }
5.3 函數(shù)OsUnmapL2PTE
函數(shù)OsUnmapL2PTE用于。⑴處先調(diào)用函數(shù)OsGetPte1計算虛擬地址對應(yīng)頁表項,然后調(diào)用函數(shù)OsGetPte2BasePtr計算二級頁表基地址。⑵處獲取虛擬地址的二級頁表項索引。⑶計算需要解除映射的數(shù)量(為啥取最小值 TODO)。⑷處依次解除各個二級頁表的映射。⑸使TLB失效。
STATIC UINT32 OsUnmapL2PTE(const LosArchMmu *archMmu, vaddr_t vaddr, UINT32 *count) { UINT32 unmapCount; UINT32 pte2Index; PTE_T *pte2BasePtr = NULL; ⑴ pte2BasePtr = OsGetPte2BasePtr(OsGetPte1((PTE_T *)archMmu->virtTtb, vaddr)); if (pte2BasePtr == NULL) { LOS_Panic("%s %d, pte2 base ptr is NULL/n", __FUNCTION__, __LINE__); } ⑵ pte2Index = OsGetPte2Index(vaddr); ⑶ unmapCount = MIN2(MMU_DESCRIPTOR_L2_NUMBERS_PER_L1 - pte2Index, *count); /* unmap page run */ ⑷ OsClearPte2Continuous(&pte2BasePtr[pte2Index], unmapCount); /* invalidate tlb */ ⑸ OsArmInvalidateTlbMvaRangeNoBarrier(vaddr, unmapCount); *count -= unmapCount; return unmapCount; }
6、其他函數(shù)
6.1 映射屬性修改函數(shù)LOS_ArchMmuChangeProt
函數(shù)LOS_ArchMmuChangeProt用于修改進程空間虛擬地址區(qū)間的映射屬性,其中參數(shù)archMmu為進程空間的MMU信息,vaddr為虛擬地址,count為映射的頁數(shù),flags為映射使用的新標簽屬性信息。⑴處對參數(shù)進行校驗,⑵處查詢虛擬地址映射的物理地址,如果沒有映射則執(zhí)行⑶把虛擬地址增加1個內(nèi)存頁大小繼續(xù)修改下一個內(nèi)存頁的屬性。⑷處先解除當前內(nèi)存頁的映射,然后執(zhí)行⑸使用新的映射屬性重新映射,⑹處虛擬地址增加1個內(nèi)存頁大小繼續(xù)修改下一個內(nèi)存頁的屬性。
STATUS_T LOS_ArchMmuChangeProt(LosArchMmu *archMmu, VADDR_T vaddr, size_t count, UINT32 flags) { STATUS_T status; PADDR_T paddr = 0; ⑴ if ((archMmu == NULL) || (vaddr == 0) || (count == 0)) { VM_ERR("invalid args: archMmu %p, vaddr %p, count %d", archMmu, vaddr, count); return LOS_NOK; } while (count > 0) { ⑵ count--; status = LOS_ArchMmuQuery(archMmu, vaddr, &paddr, NULL); if (status != LOS_OK) { ⑶ vaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE; continue; } ⑷ status = LOS_ArchMmuUnmap(archMmu, vaddr, 1); if (status < 0) { VM_ERR("invalid args:aspace %p, vaddr %p, count %d", archMmu, vaddr, count); return LOS_NOK; } ⑸ status = LOS_ArchMmuMap(archMmu, vaddr, paddr, 1, flags); if (status < 0) { VM_ERR("invalid args:aspace %p, vaddr %p, count %d", archMmu, vaddr, count); return LOS_NOK; } ⑹ vaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE; } return LOS_OK; }
6.2 映射轉(zhuǎn)移函數(shù)LOS_ArchMmuMove
函數(shù)LOS_ArchMmuMove用于將進程空間一個虛擬地址區(qū)間的映射關(guān)系轉(zhuǎn)移至另一塊未使用的虛擬地址區(qū)間重新做映射,其中參數(shù)oldVaddr為老的虛擬地址,newVaddr為新的虛擬內(nèi)存地址,flags在重新映射時可以更改映射屬性信息。⑴處先查詢老的虛擬地址映射的物理內(nèi)存。如果沒有映射關(guān)系,把新舊虛擬內(nèi)存都增加一個內(nèi)存頁的大小,⑵處取消老的虛擬地址的映射,⑶處使用新的虛擬內(nèi)存重新映射到查詢到的物理內(nèi)存地址。⑷把新舊虛擬內(nèi)存都增加一個內(nèi)存頁的大小,繼續(xù)處理下一個內(nèi)存頁。
STATUS_T LOS_ArchMmuMove(LosArchMmu *archMmu, VADDR_T oldVaddr, VADDR_T newVaddr, size_t count, UINT32 flags) { STATUS_T status; PADDR_T paddr = 0; if ((archMmu == NULL) || (oldVaddr == 0) || (newVaddr == 0) || (count == 0)) { VM_ERR("invalid args: archMmu %p, oldVaddr %p, newVddr %p, count %d", archMmu, oldVaddr, newVaddr, count); return LOS_NOK; } while (count > 0) { count--; ⑴ status = LOS_ArchMmuQuery(archMmu, oldVaddr, &paddr, NULL); if (status != LOS_OK) { oldVaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE; newVaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE; continue; } // we need to clear the mapping here and remain the phy page. ⑵ status = LOS_ArchMmuUnmap(archMmu, oldVaddr, 1); if (status < 0) { VM_ERR("invalid args: archMmu %p, vaddr %p, count %d", archMmu, oldVaddr, count); return LOS_NOK; } ⑶ status = LOS_ArchMmuMap(archMmu, newVaddr, paddr, 1, flags); if (status < 0) { VM_ERR("invalid args:archMmu %p, old_vaddr %p, new_addr %p, count %d", archMmu, oldVaddr, newVaddr, count); return LOS_NOK; } ⑷ oldVaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE; newVaddr += MMU_DESCRIPTOR_L2_SMALL_SIZE; } return LOS_OK; }
小結(jié)
本文介紹了MMU虛實映射的基本概念,運行機制,分析了映射初始化、映射查詢、映射虛擬內(nèi)存和物理內(nèi)存,解除虛實映射,更改映射屬性,重新映射等常用接口的代碼。感謝閱讀,有什么問題,請留言。