鸿蒙轻内核A核源码分析系列之虚实映射(2)虚实映射初始化

想了解更多内容,鸿蒙核A核源化请访问:

和华为官方合作共建的轻内鸿蒙技术社区

https://harmonyos.51cto.com

2、 虚拟映射初始化

在文件kernel/base/vm/los_vm_boot.c中的码分系统内存初始化函数OsSysMemInit()会调用虚实映射初始化函数OsInitMappingStartUp()。该函数代码定义在文件arch/arm/arm/src/los_arch_mmu.c,析系虚实虚实代码如下。映射映射

⑴处函数使TLB失效,初始清理虚实映射缓存数据,鸿蒙核A核源化涉及些cp15寄存器和汇编,轻内后续再分析。码分

⑵处函数切换到临时TTB。析系虚实虚实

⑶处设置内核地址空间的映射映射映射。下面分别详细这些函数代码。初始

VOID OsInitMappingStartUp(VOID) {  ⑴   OsArmInvalidateTlbBarrier(); ⑵   OsSwitchTmpTTB(); ⑶  OsSetKSectionAttr(KERNEL_VMM_BASE,鸿蒙核A核源化 FALSE);     OsSetKSectionAttr(UNCACHED_VMM_BASE, TRUE);     OsKSectionNewAttrEnable(); } 

2.1 函数OsSwitchTmpTTB

函数OsSwitchTmpTTB申请16KiB的内存存放L1页表项数据,把页表项数据从g_firstPageTable复制到申请的轻内内存区域。⑴处获取内核地址空间。码分L1页表由4096个页表项组成,每个4KiB,共需要16KiB大小。所以⑵处代码按16KiB对齐申请16KiB大小的内存区域存放L1页表项。⑶处设置内核虚拟内存地址空间的转换表基地址TTB。⑷处把g_firstPageTable页表数据复制到内核地址空间的转换表区域。如果复制失败,则直接使用g_firstPageTable。⑸处设置内核虚拟地址空间的TTB转换地址对应的源码下载物理内存地址,然后调用函数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 函数OsSetKSectionAttr

内部函数OsSetKSectionAttr用于设置内核虚拟地址空间的区间属性,分别针对内核虚拟地址空间的内核区间[KERNEL_ASPACE_BASE,KERNEL_ASPACE_BASE+KERNEL_ASPACE_SIZE]和未缓存区间[UNCACHED_VMM_BASE,UNCACHED_VMM_BASE+UNCACHED_VMM_SIZE]进行设置。内核虚拟地址空间是固定映射到物理内存的,内核地址空间的映射包含代码段、数据段、堆栈区间映射,如下示意图所示:

⑴处计算相对内核虚拟地址空间基地址KERNEL_VMM_BASE的偏移大小。⑵处先计算相对偏移值的text、rodata、data_bss段的虚拟内存地址,然后创建这些段的虚实映射关系数组mmuKernelMappings。⑶处设置内核虚拟地址区间的虚拟转换基地址TTB和物理转换基地址TTB。然后解除虚拟地址virtAddr的虚实映射,解除映射的长度就是代码段、只读数据段、数据BSS段这些内存段的长度。云服务器提供商⑷处按指定的标签flags对text代码段之前的内存区间进行虚实映射。⑸处映射text代码段、rodata只读数据段、data_bss数据段的内存区间,并调用函数LOS_VmSpaceReserve在进程空间中预定地址区间。⑹是BSS段后面的heap区、stack区的映射,映射虚拟地址空间的内存堆栈区间到对应的物理内存区间。

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 函数OsKSectionNewAttrEnable

函数OsKSectionNewAttrEnable设置虚实地址的转换表基地址TTB并清楚TLB缓存。⑴处获取内核虚拟进程空间,⑵处设置进程空间MMU的虚拟地址转换表基地址TTB,然后查询到物理内存地址并设置物理内存地址转换表基地址。⑶处从CP15 C2寄存器读取TTB地址,取高20位。⑷处将内核物理内存页表基地址写入CP15 c2 TTB寄存器。⑸处清空TLB缓冲区,然后释放内存。涉及到了MMU寄存器,后续系列会专门详细讲解。

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)); } 

想了解更多内容,请访问:

和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

源码库
滇ICP备2023000592号-31