鴻蒙輕內核A核源碼分析系列之虛實映射(3)虛擬物理內存映射
3、虛實映射函數LOS_ArchMmuMap
從上文可知,用戶程序加載啟動時,會將代碼段、數據段映射進虛擬內存空間,此時并沒有物理頁做實際的映射;程序執行時,如下圖(圖片來自OpenHarmony docs開源站點)粗箭頭所示,CPU訪問虛擬地址,通過MMU查找是否有對應的物理內存,若該虛擬地址無對應的物理地址則觸發缺頁異常,內核申請物理內存并將虛實映射關系及對應的屬性配置信息寫進頁表,并把頁表條目緩存至TLB,接著CPU可直接通過轉換關系訪問實際的物理內存;若CPU訪問已緩存至TLB的頁表條目,無需再訪問保存在內存中的頁表,可加快查找速度。本小節我們就詳細分析下虛實映射函數的實現代碼。

3.1 函數LOS_ArchMmuMap
函數LOS_ArchMmuMap用于映射進程空間虛擬地址區間與物理地址區間,其中輸入參數archMmu為MMU結構體,vaddr和paddr分別是虛擬內存和物理內存的開始地址;count為虛擬地址和物理地址映射的內存頁數量;flags為映射標簽。⑴處進行函數參數校驗,不支持NON-SECURE的標記,虛擬地址和物理地址需要內存頁4KiB對齊,參數檢查函數代碼簡單,自行查看即可。⑵處當虛擬地址、物理地址基于1MiB對齊,并且數量count大于256時,使用Section頁表項格式。⑶處生成L1 section類型頁表項并保存,下文詳細分析該函數OsMapSection()。如果不滿足⑵處條件,需要使用L2映射。首先執行⑷處獲取虛擬地址vaddr對應的L1頁表項,接著執行⑸處判斷是否映射,如果沒有對應的映射,則執行⑹處的函數OsMapL1PTE生成L1 page table類型頁表項并保存,然后執行函數OsMapL2PageContinous生成L2 頁表項并保存。如果已經映射為L1 page table頁表項類型,則執行函數OsMapL2PageContinous生成L2 頁表項并保存。如果不是支持的頁表項類型,則執行LOS_Panic()觸發異常。⑺處統計生成映射的調試,最終會返回映射成功的數量。
可以看出,在給定虛實內存地址和映射的內存頁數后,使用L1頁表映射還是L2頁表映射的判斷條件是:虛實內存地址是否1MiB內存對齊,并且映射數量是否大于256。當使用L1映射時,映射為Section頁表項類型。當使用L2映射時,根據L1頁表項類型,分布處理無效頁表項和Page Table頁表項類型這2種情況。具體的映射方式見下文。
- 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 OsMapSectionL1 Section類型頁表項映射函數
函數OsMapSection生成L1 Section類型頁表項并保存。⑴處把內存區間標簽(這些標簽定義在文件kernel\base\include\los_vm_map.h中,標簽名稱一般為VM_MAP_REGION_FLAG_XXXX)轉換為MMU標簽(定義在arch\arm\arm\include\los_mmu_descriptor_v6.h中,標簽名稱一般為MMU_DESCRIPTOR_L1_TYPE_XXXX)。 ⑵處的函數OsGetPte1Ptr(archMmu->virtTtb, *vaddr)用于獲取虛擬地址對應的頁表項索引地址,等于頁表項基地址加上虛擬地址的高20位;OsTruncPte1(*paddr) | mmuFlags | MMU_DESCRIPTOR_L1_TYPE_SECTION)為物理內存地址的高12位+MMU標簽+頁表項Section類型值。該行語句的作用是把虛擬地址和物理地理進行映射,映射關系維護在頁表項。這行代碼比較關鍵,我們繪制下圖形來表示,見下圖。⑶處把虛擬地址和物理地址增加1MiB的大小,映射數量減去256(1MiB有256個4KiB大小的內存頁)。
- 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 函數OsGetL2Table生成L2頁表項基地址
函數OsGetL2Table用于生成L2頁表,函數參數中archMmu是MMU結構體,l1Index是L1頁表項索引(頁號),ppa屬于輸出參數,保存L2頁表項基地址。⑴處計算L2頁表項偏移值(為啥這么計算? 看不懂 TODO),其中(MMU_DESCRIPTOR_L2_SMALL_SIZE / MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE)的大小等于1024;l1Index & (MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE - 1)為虛擬地址的第20-21位。⑵處通過循環遍歷查詢是否存在L2頁表(為啥查詢4次?TODO),⑶處獲取頁表項基地址,然后判斷是否頁表類型,如果是,則返回L2頁表項基地址。
如果沒有存在的頁表,則為L2頁表申請內存,如果支持虛擬地址LOSCFG_KERNEL_VM,執行⑷使用LOS_PhysPageAlloc申請內存頁,把申請的內存頁掛載頁表鏈表上,并根據內存頁計算虛擬內存地址kvaddr;如果不支持虛擬地址,執行⑸使用LOS_MemAlloc申請內存。⑹處轉換為物理地址,然后加上頁表偏移值l2Offset返回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 OsMapL1PTEL1 Page Table類型頁表項映射函數
和函數OsMapSection對應,函數OsMapL1PTE用于生成L1 Page Table類型頁表項并保存,其中函數參數pte1Ptr是L1頁表項地址。⑴處調用函數OsGetL2Table()獲取L2頁表項基地址TTB,⑵處把L2頁表項基地址加上描述符類型作為L1頁表項數據。⑶處開始的3行代碼為頁表項設置標簽,⑷處為虛擬內存地址vaddr保存頁表項數據。
- 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);
- }
3.5 OsMapL2PageContinuous映射L2頁表函數
函數OsMapL2PageContinuous用于映射L2頁表項,其中函數參數pte1為L1頁表項數據,flags為虛實映射標簽,vaddr為虛擬內存,paddr為物理內存,count為需要映射的內存頁數量。
⑴處根據L1頁表項數據獲取L2頁表項虛擬內存基地址。頁表項的高22位為L2頁表項的物理內存基地址,然后轉換為虛擬內存基地址即可。⑵處把地址區間標簽轉換為L2頁表標簽,⑶處連續設置L2頁表項數據,saveCounts表示映射了多少個L2頁表項。⑷處映射L2頁表項數據后,更新虛擬、物理內存地址,更新映射后的內存頁數量count。由于一個L2頁表項占用4KiB大小,saveCounts個頁表項,需要把saveCounts左移12位來增長內存地址。
- STATIC UINT32 OsMapL2PageContinuous(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\n", __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;
- }