MMU的本質
虛擬地址(VA):就是線性地址,鴻蒙內存部分全是VA的身影,是由編譯器和鏈接器在定位程序時分配的,每個應用程序都使用相同的虛擬內存地址空間,而這些虛擬內存地址空間實際上分別映射到不同的實際物理內存空間上。CPU只知道虛擬地址,向虛擬地址要數據,但在其保護模式下很悲催地址信號在路上被MMU攔截了,MMU把虛擬地址換成了物理地址,從而拿到了真正的數據。
物理地址(PA):程序的指令和常量數據,全局變量數據以及運行時動態申請內存所分配的實際物理內存存放位置。
MMU采用頁表(page table)來實現虛實地址轉換,頁表項除了描述虛擬頁到物理頁直接的轉換外,還提供了頁的訪問權限(讀,寫,可執行)和存儲屬性。MMU的本質是拿虛擬地址的高位(20位)做文章,低12位是頁內偏移地址不會變。也就是說虛擬地址和物理地址的低12位是一樣的,本篇詳細講述MMU是如何變戲法的。
MMU是通過兩級頁表結構:L1和L2來實現映射功能的,鴻蒙內核當然也實現了這兩級頁表轉換的實現。本篇是系列篇關于內存部分最滿意的一篇,也是最不好理解的一篇,強烈建議結合源碼看,鴻蒙內核源碼注釋中文版 【 Gitee倉|CSDN倉|Github倉|Coding倉 】內存部分的注釋已經基本完成 .
一級頁表L1
L1頁表將全部的4G地址空間劃分為4096個1M的節,頁表中每一項(頁表項)32位,其內容是L2頁表基地址或某個1M物理內存的基地址。虛擬地址的高12位用于對頁表項定位,也就是4096個頁面項的索引,L1頁表的基地址,也叫轉換表基地址,存放在CP15的C2(TTB)寄存器中,鴻蒙內核源碼分析(內存匯編篇)中有詳細的描述,自行翻看。
L1頁表項有三種描述格式,鴻蒙源碼如下。
/* 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)
第一種:Fault(INVALID)頁表項,表示對應虛擬地址未被映射,訪問將產生一個數據中止異常。
第二種:PAGE_TABLE頁表項,指向L2頁表的頁表項,意思就是把1M分成更多的頁(256*4K)
第三種:SECTION頁表項 ,指向1M節的頁表項
?
頁表項的最低二位[1:0],用于定義頁表項的類型,section頁表項對應1M的節,直接使用頁表項的最高12位替代虛擬地址的高12位即可得到物理地址。還是直接看鴻蒙源碼來的清晰,每一行都加了詳細的注釋。
LOS_ArchMmuQuery通過虛擬地址查詢物理地址和flags
//通過虛擬地址查詢物理地址 STATUS_T LOS_ArchMmuQuery(const LosArchMmu *archMmu, VADDR_T vaddr, PADDR_T *paddr, UINT32 *flags) {//archMmu->virtTtb:轉換表基地址 PTE_T l1Entry = OsGetPte1(archMmu->virtTtb, vaddr);//獲取PTE vaddr右移20位 得到L1描述子地址 PTE_T l2Entry; PTE_T* l2Base = NULL; if (OsIsPte1Invalid(l1Entry)) {//判斷L1描述子地址是否有效 return LOS_ERRNO_VM_NOT_FOUND;//無效返回虛擬地址未查詢到 } else if (OsIsPte1Section(l1Entry)) {// section頁表項: l1Entry低二位是否為 10 if (paddr != NULL) {//物理地址 = 節基地址(section頁表項的高12位) + 虛擬地址低20位 *paddr = MMU_DESCRIPTOR_L1_SECTION_ADDR(l1Entry) + (vaddr & (MMU_DESCRIPTOR_L1_SMALL_SIZE - 1)); } if (flags != NULL) { OsCvtSecAttsToFlags(l1Entry, flags);//獲取虛擬內存的flag信息 } } else if (OsIsPte1PageTable(l1Entry)) {//PAGE_TABLE頁表項: l1Entry低二位是否為 01 l2Base = OsGetPte2BasePtr(l1Entry);//獲取L2轉換表基地址 if (l2Base == NULL) { return LOS_ERRNO_VM_NOT_FOUND; } l2Entry = OsGetPte2(l2Base, vaddr);//獲取L2描述子地址 if (OsIsPte2SmallPage(l2Entry) || OsIsPte2SmallPageXN(l2Entry)) { if (paddr != NULL) {//物理地址 = 小頁基地址(L2頁表項的高20位) + 虛擬地址低12位 *paddr = MMU_DESCRIPTOR_L2_SMALL_PAGE_ADDR(l2Entry) + (vaddr & (MMU_DESCRIPTOR_L2_SMALL_SIZE - 1)); } if (flags != NULL) { OsCvtPte2AttsToFlags(l1Entry, l2Entry, flags);//獲取虛擬內存的flag信息 } } else if (OsIsPte2LargePage(l2Entry)) {//鴻蒙目前暫不支持64K大頁,未來手機版應該會支持。 LOS_Panic("%s %d, large page unimplemented ", __FUNCTION__, __LINE__); } else { return LOS_ERRNO_VM_NOT_FOUND; } } return LOS_OK; }
這是鴻蒙內核對地址使用最頻繁的功能,通過虛擬地址得到物理地址和flag信息,看下哪些地方會調用到它。
二級頁表L2
L1頁表項表示1M的地址范圍,L2把1M分成更多的小頁,鴻蒙內核 一頁按4K算,所以被分成 256個小頁。
L2頁表中包含256個頁表項,每個32位(4個字節),L2頁表需要 256*4 = 1K的空間,必須按1K對齊,每個L2頁表項將4K的虛擬內存地址轉換為物理地址,每個L2頁面項都給出了一個4K的頁基地址。
L2頁表項有三種格式:
/* 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)
第一種:Fault(INVALID)頁表項,表示對應虛擬地址未被映射,訪問將產生一個數據中止異常。
第二種:大頁表項,包含一個指向64K頁的指針,但鴻蒙內核并沒有實現大頁表的支持,給出了未實現的提示
if (OsIsPte2LargePage(l2Entry)) { LOS_Panic("%s %d, large page unimplemented ", __FUNCTION__, __LINE__); }
第三種:小頁表項,包含一個指向4K頁的指針。
映射初始化的過程
先看調用和被調用的關系
//啟動映射初始化 VOID OsInitMappingStartUp(VOID) { OsArmInvalidateTlbBarrier();//使TLB失效 OsSwitchTmpTTB();//切換到臨時TTB OsSetKSectionAttr();//設置內核段(text,rodata,bss)映射 OsArchMmuInitPerCPU();//初始化CPU與mmu相關信息 }
干脆利落,調用了四個函數,其中三個在鴻蒙內核源碼分析(內存匯編篇)有涉及,不展開講,這里說OsSetKSectionAttr
它實現了內核空間各個區的映射,內核本身也是程序,鴻蒙把內核空間在物理內存上就獨立開來了,也就是說在物理內存上有一段區域是只給內核空間享用的,從根上就把內核和APP 空間隔離了,里面放的是內核的重要數據(包括代碼,常量和全局變量),具體看代碼,代碼很長,整個函數全貼出來了,都加上了注釋。
OsSetKSectionAttr內核空間的設置和映射
typedef struct ArchMmuInitMapping { PADDR_T phys;//物理地址 VADDR_T virt;//虛擬地址 size_t size;//大小 unsigned int flags;//標識 讀/寫/.. VM_MAP_REGION_FLAG_PERM_* const char *name;//名稱 } LosArchMmuInitMapping; VADDR_T *OsGFirstTableGet() { return (VADDR_T *)g_firstPageTable;//UINT8 g_firstPageTable[MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS] } //設置內核空間段屬性,可看出內核空間是固定映射到物理地址 STATIC VOID OsSetKSectionAttr(VOID) { /* every section should be page aligned */ UINTPTR textStart = (UINTPTR)&__text_start;//代碼段開始位置 UINTPTR textEnd = (UINTPTR)&__text_end;//代碼段結束位置 UINTPTR rodataStart = (UINTPTR)&__rodata_start;//常量只讀段開始位置 UINTPTR rodataEnd = (UINTPTR)&__rodata_end;//常量只讀段結束位置 UINTPTR ramDataStart = (UINTPTR)&__ram_data_start;//全局變量段開始位置 UINTPTR bssEnd = (UINTPTR)&__bss_end;//bss結束位置 UINT32 bssEndBoundary = ROUNDUP(bssEnd, MB); LosArchMmuInitMapping mmuKernelMappings[] = { { .phys = SYS_MEM_BASE + textStart - KERNEL_VMM_BASE,//映射物理內存位置 .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 - KERNEL_VMM_BASE,//映射物理內存位置 .virt = rodataStart,//內核常量區 .size = ROUNDUP(rodataEnd - rodataStart, MMU_DESCRIPTOR_L2_SMALL_SIZE),//4K對齊 .flags = VM_MAP_REGION_FLAG_PERM_READ,//常量段只讀 .name = "kernel_rodata" }, { .phys = SYS_MEM_BASE + ramDataStart - KERNEL_VMM_BASE,//映射物理內存位置 .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; paddr_t oldTtPhyBase; int i; LosArchMmuInitMapping *kernelMap = NULL;//內核映射 UINT32 kmallocLength; /* use second-level mapping of default READ and WRITE */ kSpace->archMmu.virtTtb = (PTE_T *)g_firstPageTable;//__attribute__((section(".bss.prebss.translation_table"))) UINT8 g_firstPageTable[MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS]; kSpace->archMmu.physTtb = LOS_PaddrQuery(kSpace->archMmu.virtTtb);//通過TTB虛擬地址查詢TTB物理地址 status = LOS_ArchMmuUnmap(&kSpace->archMmu, KERNEL_VMM_BASE, (bssEndBoundary - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT);//解綁 bssEndBoundary - KERNEL_VMM_BASE 映射 if (status != ((bssEndBoundary - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) {//解綁失敗 VM_ERR("unmap failed, status: %d", status); return; } //映射 textStart - KERNEL_VMM_BASE 區 status = LOS_ArchMmuMap(&kSpace->archMmu, KERNEL_VMM_BASE, SYS_MEM_BASE, (textStart - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE | VM_MAP_REGION_FLAG_PERM_EXECUTE); if (status != ((textStart - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) { VM_ERR("mmap failed, status: %d", status); return; } length = sizeof(mmuKernelMappings) / sizeof(LosArchMmuInitMapping); for (i = 0; i < length; i++) {//對mmuKernelMappings一一映射好 kernelMap = &mmuKernelMappings[i]; 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 = KERNEL_VMM_BASE + SYS_MEM_SIZE_DEFAULT - bssEndBoundary; status = LOS_ArchMmuMap(&kSpace->archMmu, bssEndBoundary, SYS_MEM_BASE + bssEndBoundary - KERNEL_VMM_BASE, kmallocLength >> MMU_DESCRIPTOR_L2_SMALL_SHIFT, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE); if (status != (kmallocLength >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) { VM_ERR("unmap failed, status: %d", status); return; } LOS_VmSpaceReserve(kSpace, kmallocLength, bssEndBoundary); /* we need free tmp ttbase */ oldTtPhyBase = OsArmReadTtbr0();//讀取TTB值 oldTtPhyBase = oldTtPhyBase & MMU_DESCRIPTOR_L2_SMALL_FRAME; OsArmWriteTtbr0(kSpace->archMmu.physTtb | MMU_TTBRx_FLAGS);//內核頁表基地址寫入CP15 c2(TTB寄存器) ISB; /* we changed page table entry, so we need to clean TLB here */ OsCleanTLB();//清空TLB緩沖區 (VOID)LOS_MemFree(m_aucSysMem0, (VOID *)(UINTPTR)(oldTtPhyBase - SYS_MEM_BASE + KERNEL_VMM_BASE));//釋放內存池 }
LOS_ArchMmuMap生成L1,L2頁表項,實現映射的過程
mmu的map 就是生成L1,L2頁表項的過程,以供虛實地址的轉換使用,還是直接看代碼吧,代碼說明一切!
//所謂的 map 就是 生成L1,L2頁表項的過程 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) && //虛擬地址和物理地址對齊 0x100000(1M)時采用 MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(paddr) && //section頁表項格式 count >= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1) { //MMU_DESCRIPTOR_L2_NUMBERS_PER_L1 = 0x100 /* compute the arch flags for L1 sections cache, r ,w ,x, domain and type */ saveCounts = OsMapSection(archMmu, flags, &vaddr, &paddr, &count);//生成L1 section類型頁表項并保存 } else { /* have to use a L2 mapping, we only allocate 4KB for L1, support 0 ~ 1GB */ l1Entry = OsGetPte1(archMmu->virtTtb, vaddr);//獲取L1頁面項 if (OsIsPte1Invalid(l1Entry)) {//L1 fault頁面項類型 OsMapL1PTE(archMmu, &l1Entry, vaddr, flags);//生成L1 page table類型頁表項并保存 saveCounts = OsMapL2PageContinous(l1Entry, flags, &vaddr, &paddr, &count);//生成L2 頁表項目并保存 } else if (OsIsPte1PageTable(l1Entry)) {//L1 page table頁面項類型 saveCounts = OsMapL2PageContinous(l1Entry, flags, &vaddr, &paddr, &count);//生成L2 頁表項目并保存 } else { LOS_Panic("%s %d, unimplemented tt_entry %x ", __FUNCTION__, __LINE__, l1Entry); } } mapped += saveCounts; } return mapped; } STATIC UINT32 OsMapL2PageContinous(PTE_T pte1, UINT32 flags, VADDR_T *vaddr, PADDR_T *paddr, UINT32 *count) { PTE_T *pte2BasePtr = NULL; UINT32 archFlags; UINT32 saveCounts; pte2BasePtr = OsGetPte2BasePtr(pte1); if (pte2BasePtr == NULL) { LOS_Panic("%s %d, pte1 %#x error ", __FUNCTION__, __LINE__, pte1); } /* compute the arch flags for L2 4K pages */ archFlags = OsCvtPte2FlagsToAttrs(flags); saveCounts = OsSavePte2Continuous(pte2BasePtr, OsGetPte2Index(*vaddr), *paddr | archFlags, *count); *paddr += (saveCounts << MMU_DESCRIPTOR_L2_SMALL_SHIFT); *vaddr += (saveCounts << MMU_DESCRIPTOR_L2_SMALL_SHIFT); *count -= saveCounts; return saveCounts; }
OsMapL2PageContinous沒有加注釋,希望你別太懶,趕緊動起來,到這里應該都能看懂了!最好能結合 鴻蒙內核源碼分析(內存匯編篇)一起看理解會更深透。
編輯:hfy
-
MMU
+關注
關注
0文章
92瀏覽量
18627 -
鴻蒙系統
+關注
關注
183文章
2639瀏覽量
67675
發布評論請先 登錄
飛凌嵌入式ElfBoard ELF 1板卡-Regmap子系統之Regmap簡介
請問如何在imx8qm上將PCIe和SMMU用于顯卡?
OpenHarmony源碼編譯后燒錄鏡像教程,RK3566鴻蒙開發板演示

MAC地址的作用范圍,MAC地址怎么申請?

如何使用內存加速存儲訪問速度

嵌入式學習-飛凌嵌入式ElfBoard ELF 1板卡-Linux內核移植之內核簡介
飛凌嵌入式ElfBoard ELF 1板卡-Linux內核移植之內核簡介
怎么綁定IP地址和MAC地址
IP地址歸屬地離線庫——網絡安全行業的基石
【免費分享】OpenHarmony鴻蒙物聯網開發板資料包一網打盡,附教程/視頻/項目/源碼...

如何獲取MAC地址?MAC地址的三個申請條件

評論