鴻蒙輕內(nèi)核A核源碼分析系列之虛實映射(2)虛實映射初始化
2、 虛擬映射初始化
在文件kernel/base/vm/los_vm_boot.c中的系統(tǒng)內(nèi)存初始化函數(shù)OsSysMemInit()會調(diào)用虛實映射初始化函數(shù)OsInitMappingStartUp()。該函數(shù)代碼定義在文件arch/arm/arm/src/los_arch_mmu.c,代碼如下。
⑴處函數(shù)使TLB失效,清理虛實映射緩存數(shù)據(jù),涉及些cp15寄存器和匯編,后續(xù)再分析。
⑵處函數(shù)切換到臨時TTB。
⑶處設置內(nèi)核地址空間的映射。下面分別詳細這些函數(shù)代碼。
- VOID OsInitMappingStartUp(VOID)
- {
- ⑴ OsArmInvalidateTlbBarrier();
- ⑵ OsSwitchTmpTTB();
- ⑶ OsSetKSectionAttr(KERNEL_VMM_BASE, FALSE);
- OsSetKSectionAttr(UNCACHED_VMM_BASE, TRUE);
- OsKSectionNewAttrEnable();
- }
2.1 函數(shù)OsSwitchTmpTTB
函數(shù)OsSwitchTmpTTB申請16KiB的內(nèi)存存放L1頁表項數(shù)據(jù),把頁表項數(shù)據(jù)從g_firstPageTable復制到申請的內(nèi)存區(qū)域。⑴處獲取內(nèi)核地址空間。L1頁表由4096個頁表項組成,每個4KiB,共需要16KiB大小。所以⑵處代碼按16KiB對齊申請16KiB大小的內(nèi)存區(qū)域存放L1頁表項。⑶處設置內(nèi)核虛擬內(nèi)存地址空間的轉(zhuǎn)換表基地址TTB。⑷處把g_firstPageTable頁表數(shù)據(jù)復制到內(nèi)核地址空間的轉(zhuǎn)換表區(qū)域。如果復制失敗,則直接使用g_firstPageTable。⑸處設置內(nèi)核虛擬地址空間的TTB轉(zhuǎn)換地址對應的物理內(nèi)存地址,然后調(diào)用函數(shù)OsArmWriteTtbr0寫入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用于設置內(nèi)核虛擬地址空間的區(qū)間屬性,分別針對內(nèi)核虛擬地址空間的內(nèi)核區(qū)間[KERNEL_ASPACE_BASE,KERNEL_ASPACE_BASE+KERNEL_ASPACE_SIZE]和未緩存區(qū)間[UNCACHED_VMM_BASE,UNCACHED_VMM_BASE+UNCACHED_VMM_SIZE]進行設置。內(nèi)核虛擬地址空間是固定映射到物理內(nèi)存的,內(nèi)核地址空間的映射包含代碼段、數(shù)據(jù)段、堆棧區(qū)間映射,如下示意圖所示:

⑴處計算相對內(nèi)核虛擬地址空間基地址KERNEL_VMM_BASE的偏移大小。⑵處先計算相對偏移值的text、rodata、data_bss段的虛擬內(nèi)存地址,然后創(chuàng)建這些段的虛實映射關系數(shù)組mmuKernelMappings。⑶處設置內(nèi)核虛擬地址區(qū)間的虛擬轉(zhuǎn)換基地址TTB和物理轉(zhuǎn)換基地址TTB。然后解除虛擬地址virtAddr的虛實映射,解除映射的長度就是代碼段、只讀數(shù)據(jù)段、數(shù)據(jù)BSS段這些內(nèi)存段的長度。⑷處按指定的標簽flags對text代碼段之前的內(nèi)存區(qū)間進行虛實映射。⑸處映射text代碼段、rodata只讀數(shù)據(jù)段、data_bss數(shù)據(jù)段的內(nèi)存區(qū)間,并調(diào)用函數(shù)LOS_VmSpaceReserve在進程空間中預定地址區(qū)間。⑹是BSS段后面的heap區(qū)、stack區(qū)的映射,映射虛擬地址空間的內(nèi)存堆棧區(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設置虛實地址的轉(zhuǎn)換表基地址TTB并清楚TLB緩存。⑴處獲取內(nèi)核虛擬進程空間,⑵處設置進程空間MMU的虛擬地址轉(zhuǎn)換表基地址TTB,然后查詢到物理內(nèi)存地址并設置物理內(nèi)存地址轉(zhuǎn)換表基地址。⑶處從CP15 C2寄存器讀取TTB地址,取高20位。⑷處將內(nèi)核物理內(nèi)存頁表基地址寫入CP15 c2 TTB寄存器。⑸處清空TLB緩沖區(qū),然后釋放內(nèi)存。涉及到了MMU寄存器,后續(xù)系列會專門詳細講解。
- 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));
- }