鸿蒙轻内核A核源码分析系列五 虚实映射(2)虚实映射初始化 原创 精华

zhushangyuan_
发布于 2021-12-1 08:51
浏览
3收藏

鸿蒙轻内核A核源码分析系列五 虚实映射

【#本文正在参与优质创作者激励#】

2、 虚拟映射初始化

在文件kernel/base/vm/los_vm_boot.c中的系统内存初始化函数OsSysMemInit()会调用虚实映射初始化函数OsInitMappingStartUp()。该函数代码定义在文件arch/arm/arm/src/los_arch_mmu.c,代码如下。⑴处函数使TLB失效,清理虚实映射缓存数据,涉及些cp15寄存器和汇编,后续再分析。⑵处函数切换到临时TTB。⑶处设置内核地址空间的映射。下面分别详细这些函数代码。

VOID OsInitMappingStartUp(VOID)
{OsArmInvalidateTlbBarrier();OsSwitchTmpTTB();OsSetKSectionAttr(KERNEL_VMM_BASE, FALSE);
    OsSetKSectionAttr(UNCACHED_VMM_BASE, TRUE);
    OsKSectionNewAttrEnable();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

2.1 函数OsSwitchTmpTTB

函数OsSwitchTmpTTB申请16KiB的内存存放L1页表项数据,把页表项数据从g_firstPageTable复制到申请的内存区域。⑴处获取内核地址空间。L1页表由4096个页表项组成,每个4 bytes,共需要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;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

2.2 函数OsSetKSectionAttr

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

鸿蒙轻内核A核源码分析系列五 虚实映射(2)虚实映射初始化-鸿蒙开发者社区

⑴处计算相对内核虚拟地址空间基地址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);
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.

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));
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

【#本文正在参与优质创作者激励#】

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-1-4 14:25:23修改
5
收藏 3
回复
举报
5
2
3
2条回复
按时间正序
/
按时间倒序
wenfei6316
wenfei6316

2.1 函数OsSwitchTmpTTB

这里应该这么描述合理一些:L1页表由4096个页表项组成,每个页表项占用4Bit,而不是4KB,否则4096*KB = 16MB了

1
回复
2022-1-4 13:00:53
zhushangyuan_
zhushangyuan_ 回复了 wenfei6316
2.1 函数OsSwitchTmpTTB 这里应该这么描述合理一些:L1页表由4096个页表项组成,每个页表项占用4Bit,而不是4KB,否则4096*KB = 16MB了

感谢指正,已修改

1
回复
2022-1-4 14:24:41


回复
    相关推荐