Hongmeng OS source code analysis: mapping of physical addresses

Hongmeng Kernel Source Code Analysis (Memory Mapping) | What is the most important implementation basis for memory? | Chinese Annotation HarmonyOS Source Code | v12.02

The essence of MMU

MMU is the abbreviation of Memory Management Unit, the Chinese name is memory management unit, sometimes called paged memory management unit (English: paged memory management unit, abbreviated as PMMU). It is a kind of computer hardware responsible for processing memory access requests from the central processing unit (CPU). Its functions include virtual address to physical address conversion (ie virtual memory management), memory protection, and control of the central processing unit cache. In a relatively simple computer architecture, it is responsible for bus arbitration and bank switching (bank switching, Especially on 8-bit systems).

Virtual address (VA): It is a linear address. The memory of Hongmeng is all VA, which is allocated by the compiler and linker when locating the program. Each application program uses the same virtual memory address space, and these virtual memory The address space is actually mapped to different actual physical memory spaces. The CPU only knows the virtual address and asks for data from the virtual address. However, in its protected mode, it is very sad that the address signal is intercepted by the MMU on the road. The MMU replaces the virtual address with a physical address to get the real data.

Physical address (PA): Program instructions and constant data, global variable data, and the actual physical memory storage location allocated by the dynamic application memory during runtime.

MMU uses page table (pagetable) to realize virtual and real address conversion. In addition to describing the direct conversion from virtual page to physical page, page table entries also provide page access permissions (read, write, executable) and storage attributes. The essence of MMU is to make a fuss about the high-order (20-bit) of the virtual address, and the low-order 12-bit means that the offset address within the page will not change. That is to say, the lower 12 bits of the virtual address and the physical address are the same. This article describes in detail how the MMU conjures.

The MMU implements the mapping function through a two-level page table structure: L1 and L2. Of course, the Hongmeng core also implements the conversion of the two-level page table. This article is the most satisfactory article in the series about the memory part, and the one that is the most difficult to understand. It is strongly recommended to read it in conjunction with the source code. Hongmeng kernel source code annotation Chinese version [Gitee warehouse|CSDN warehouse|Github warehouse|Coding warehouse] Memory part The comments have been basically completed.

Level 1 page table L1

The L1 page table divides the entire 4G address space into 4096 1M sections. Each item (page table entry) in the page table is 32 bits, and its content is the base address of the L2 page table or the base address of a 1M physical memory. The upper 12 bits of the virtual address are used to locate page table entries, that is, the index of 4096 page entries. The base address of the L1 page table, also called the conversion table base address, is stored in the C2 (TTB) register of CP15, Hongmeng core There is a detailed description in the source code analysis (memory assembly article), please read it yourself.

There are three description formats for the entries on the L1 page. The source code of Hongmeng is as follows.

/*L1descriptortype*/
#defineMMU_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)

The first type: Fault (INVALID) page table entry, indicating that the corresponding virtual address is not mapped, and the access will generate a data abort exception.

The second type: PAGE_TABLE page table entry, which points to the page table entry of the L2 page table, meaning that 1M is divided into more pages (256*4K)

The third type: SECTION page table entry, pointing to
Insert picture description here
the lowest two bits of the page table entry of the 1M section [1:0], used to define the type of the page table entry, the section page table entry corresponds to the 1M section, and the page is used directly The upper 12 bits of the table entry replace the upper 12 bits of the virtual address to obtain the physical address. It's clear by looking directly at the source code of Hongmeng, with detailed comments on each line.

arch\arm\arm\src los_arch_mmu.c

LOS_ArchMmuQuery queries physical addresses and flags through virtual addresses

//通过虚拟地址查询物理地址/******************************************************************************
本函数是内核高频函数,通过MMU查询虚拟地址是否映射过,
带走映射的物理地址和权限
******************************************************************************/
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\n", __FUNCTION__, __LINE__);
        } else {
    
    
            return LOS_ERRNO_VM_NOT_FOUND;
        }
    }

    return LOS_OK;
}

This is the most frequently used function of the Hongmeng kernel. The physical address and flag information are obtained through the virtual address and see where it will be called.

Insert picture description here
Secondary page table L2

The L1 page table entry represents the address range of 1M, L2 divides 1M into more small pages, and the Hongmeng core page is calculated at 4K, so it is divided into 256 small pages.

The L2 page table contains 256 page table entries, each of which is 32 bits (4 bytes). The L2 page table needs 256*4=1K space and must be aligned by 1K. Each L2 page table entry will have 4K of virtual memory The address is converted to a physical address, and each L2 page entry gives a 4K page base address.

There are three formats for L2 page table entries:

/*L2descriptortype*/
#defineMMU_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)

The first type: Fault (INVALID) page table entry, indicating that the corresponding virtual address is not mapped, and the access will generate a data abort exception.

The second type: large page table entry, which contains a pointer to a 64K page, but the Hongmeng kernel does not implement large page table support, and gives a hint that it is not implemented

if(OsIsPte2LargePage(l2Entry)){
    
    LOS_Panic("%s%d,largepageunimplemented\n",__FUNCTION__,__LINE__);}

Insert picture description here
The process of mapping initialization

First look at the relationship between calling and being called
Insert picture description here

//启动映射初始化
VOIDOsInitMappingStartUp(VOID){
    
    OsArmInvalidateTlbBarrier();
//使TLB失效
OsSwitchTmpTTB();
//切换到临时
TTBOsSetKSectionAttr();
//设置内核段(text,rodata,bss)映射
OsArchMmuInitPerCPU();
//初始化CPU与mmu相关信息
}

Simply and neatly, four functions are called, three of which are involved in the Hongmeng kernel source code analysis (memory assembly), not to expand, here is OsSetKSectionAttr

It realizes the mapping of various areas of the kernel space. The kernel itself is also a program. Hongmeng separates the kernel space from the physical memory, which means that there is a section of the physical memory that is only used by the kernel space. The kernel and the APP space are isolated, and the important data of the kernel (including code, constants and global variables) are placed inside. The code is very long, the entire function is posted, and comments are added.

Setting and mapping of OsSetKSectionAttr kernel space

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 generates L1 and L2 page table entries, and the process of realizing the mapping.
Mmu's map is the process of generating L1 and L2 page table entries for the conversion of virtual and real addresses. Let's look at the code directly. The code explains everything!

//所谓的 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\n", __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\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;
}

Insert picture description here
OsMapL2PageContinous has no comments, I hope you don’t be too lazy, and move quickly, you should be able to understand it here! It is best to combine Hongmeng kernel source code analysis (memory assembly) together to see and understand more deeply.

Guess you like

Origin blog.csdn.net/weixin_45394002/article/details/113773221