摘要:本文介紹了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)存,解除虛實映射,更改映射屬性,重新映射等常用接口的代碼。感謝閱讀,有什么問題,請留言。


??點擊關(guān)注,第一時間了解華為云新鮮技術(shù)~??