Hongmeng light kernel source code analysis: virtual memory

Abstract: in the form of code + text, this paper introduces the structure of virtual memory management and related macro definitions, and analyzes how to initialize the kernel virtual address space and user process virtual address space.

This article is shared from Huawei cloud community< Hongmeng light kernel A core source code analysis Series IV (2) virtual memory >, author: zhushy.

The source code involved in this article, taking the OpenHarmony LiteOS-A kernel as an example, can be found on the open source site https://gitee.com/openharmony/kernel_liteos_a   obtain. If the development board is involved, it defaults to hispark_ Take Taurus as an example.

We first understand the structure and related macro definitions of virtual memory management, then analyze how the kernel virtual address space and user process virtual address space are initialized, then analyze the common operations of virtual memory interval, including search, application and release, and finally analyze the source code of the application and release interface of dynamic memory heap, And briefly introduce the source code of the memory interval reservation interface.

1. Virtual memory management related structures

In the file kernel/base/include/los_vm_map.h defines the process address space structure LosVmSpace, the process address interval structure LosVmMapRegion and the process address interval range structure LosVmMapRange. Each user state process will create its own process space, and the kernel state will create two process spaces, g respectively_ Kvmspace and g_vMallocSpace. The virtual memory block requested from the process space is represented by the process interval LosVmMapRegion. Each process space maintains a red black tree to link each process interval.

1.1 virtual memory address space structure LosVmSpace

typedef struct VmSpace {
    LOS_DL_LIST         node;           /**< Address space bidirectional linked list */
    LosRbTree           regionRbTree;   /**< Red black tree root node of address interval */
    LosMux              regionMux;      /**< Mutex of red black tree in address interval */
    VADDR_T             base;           /**< Address space start address */
    UINT32              size;           /**< Address space size */
    VADDR_T             heapBase;       /**< Heap start address heapBase of address space */
    VADDR_T             heapNow;        /**< Heap start address heapNow of address space */
    LosVmMapRegion      *heap;          /**< Address interval of address space */
    VADDR_T             mapBase;        /**< Start address of mapping area of address space */
    UINT32              mapSize;        /**< Mapping area size of address space */
    LosArchMmu          archMmu;        /**< MMU structure of address space */
#ifdef LOSCFG_DRIVERS_TZDRIVER
    VADDR_T             codeStart;      /**< User process code area start address */
    VADDR_T             codeEnd;        /**< End address of user process code area */
#endif
} LosVmSpace;

1.2 virtual memory address interval LosVmMapRegion

typedef struct VmMapRange {
    VADDR_T             base;           /**< Virtual memory address range start address */
    UINT32              size;           /**< Virtual memory address interval size */
} LosVmMapRange;
......
struct VmMapRegion;
typedef struct VmMapRegion LosVmMapRegion;
......
struct VmMapRegion {
    LosRbNode           rbNode;         /**<  Address interval red black tree node */
    LosVmSpace          *space;         /**<  Address space of address interval */
    LOS_DL_LIST         node;           /**<  Address interval bidirectional linked list */
    LosVmMapRange       range;          /**<  Address range */
    VM_OFFSET_T         pgOff;          /**<  Address range page offset */
    UINT32              regionFlags;    /**<  Address interval mark: cow, user_wired */
    UINT32              shmid;          /**<  Shared address interval number */
    UINT8               forkFlags;      /**<  Address range fork flag: COPY, ZERO, */
    UINT8               regionType;     /**<  Address interval type: ANON, FILE, DEV */
    union {
        struct VmRegionFile {
            unsigned int fileMagic;
            struct file *file;
            const LosVmFileOps *vmFOps;
        } rf;
        struct VmRegionAnon {
            LOS_DL_LIST  node;          /**< Bidirectional linked list of address interval type */
        } ra;
        struct VmRegionDev {
            LOS_DL_LIST  node;          /**< Bidirectional linked list of address interval type */
            const LosVmFileOps *vmFOps;
        } rd;
    } unTypeData;
};

2. Virtual memory related macro definitions

File kernel/base/include/los_vm_common.h and kernel/base/include/los_vm_zone.h defines macros related to virtual memory. For 32-bit systems, the size of virtual process space is 4GiB. OpenHarmony Hongmeng light kernel currently supports 32-bit systems. ⑴ and ⑵ define the start address and size of the user process virtual address space, ⑶ is the end address of the user virtual process space, and then define the start address and size of the heap area and mapping area of the user virtual process space.

/* user address space, defaults to below kernel space with a 16MB guard gap on either side */
    #ifndef USER_ASPACE_BASE
⑴  #define USER_ASPACE_BASE            ((vaddr_t)0x01000000UL)
    #endif
    #ifndef USER_ASPACE_SIZE
⑵  #define USER_ASPACE_SIZE            ((vaddr_t)KERNEL_ASPACE_BASE - USER_ASPACE_BASE - 0x01000000UL)
    #endif#define USER_ASPACE_TOP_MAX         ((vaddr_t)(USER_ASPACE_BASE + USER_ASPACE_SIZE))
    #define USER_HEAP_BASE              ((vaddr_t)(USER_ASPACE_TOP_MAX >> 2))
    #define USER_MAP_BASE               ((vaddr_t)(USER_ASPACE_TOP_MAX >> 1))
    #define USER_MAP_SIZE               ((vaddr_t)(USER_ASPACE_SIZE >> 3))

The macro definition of the kernel virtual process space is as follows: ⑴ defines the start address and size of the kernel process address space, ⑵ defines the start address and size of the kernel non cached virtual address space, ⑶ defines the start address and size of the virtual dynamic allocation address space, ⑷ defines the start address and size of the peripheral, and ⑤ defines the start address and size of the peripheral cache, (6) define the start address and size of peripheral non cache area at.

#ifdef LOSCFG_KERNEL_MMU
    #ifdef LOSCFG_TEE_ENABLE
    #define KERNEL_VADDR_BASE       0x41000000
    #else
    #define KERNEL_VADDR_BASE       0x40000000
    #endif
    #else
    #define KERNEL_VADDR_BASE       DDR_MEM_ADDR
    #endif
    #define KERNEL_VADDR_SIZE       DDR_MEM_SIZE

    #define SYS_MEM_BASE            DDR_MEM_ADDR
    #define SYS_MEM_END             (SYS_MEM_BASE + SYS_MEM_SIZE_DEFAULT)

    #define _U32_C(X)  X##U
    #define U32_C(X)   _U32_C(X)

    #define KERNEL_VMM_BASE         U32_C(KERNEL_VADDR_BASE)
    #define KERNEL_VMM_SIZE         U32_C(KERNEL_VADDR_SIZE)#define KERNEL_ASPACE_BASE      KERNEL_VMM_BASE
    #define KERNEL_ASPACE_SIZE      KERNEL_VMM_SIZE

    /* Uncached vmm aspace */#define UNCACHED_VMM_BASE       (KERNEL_VMM_BASE + KERNEL_VMM_SIZE)
    #define UNCACHED_VMM_SIZE       DDR_MEM_SIZE#define VMALLOC_START           (UNCACHED_VMM_BASE + UNCACHED_VMM_SIZE)
    #define VMALLOC_SIZE            0x08000000

    #ifdef LOSCFG_KERNEL_MMU
⑷  #define PERIPH_DEVICE_BASE      (VMALLOC_START + VMALLOC_SIZE)
    #define PERIPH_DEVICE_SIZE      U32_C(PERIPH_PMM_SIZE)#define PERIPH_CACHED_BASE      (PERIPH_DEVICE_BASE + PERIPH_DEVICE_SIZE)
    #define PERIPH_CACHED_SIZE      U32_C(PERIPH_PMM_SIZE)#define PERIPH_UNCACHED_BASE    (PERIPH_CACHED_BASE + PERIPH_CACHED_SIZE)
    #define PERIPH_UNCACHED_SIZE    U32_C(PERIPH_PMM_SIZE)
    #else
    #define PERIPH_DEVICE_BASE      PERIPH_PMM_BASE
    #define PERIPH_DEVICE_SIZE      U32_C(PERIPH_PMM_SIZE)
    #define PERIPH_CACHED_BASE      PERIPH_PMM_BASE
    #define PERIPH_CACHED_SIZE      U32_C(PERIPH_PMM_SIZE)
    #define PERIPH_UNCACHED_BASE    PERIPH_PMM_BASE
    #define PERIPH_UNCACHED_SIZE    U32_C(PERIPH_PMM_SIZE)
    #endif

The distribution diagram of virtual address space is as follows:

3. Process address space initialization

Virtual process space is divided into user virtual process space and kernel virtual process space. Each user process will create its own process space. The kernel initializes two process spaces. This is described in detail below.

3.1 kernel virtual address space initialization

3.1.1 function OsKSpaceInit

The function OsKSpaceInit() initializes the virtual address space of the kernel process, and the function at ⑴ initializes the virtual space linked list mutex g_vmSpaceListMux, which needs to hold the mutex when operating the kernel process space. (2) the two functions starting at OsKernVmSpaceInit and OsVMallocSpaceInit respectively initialize the kernel process virtual space g_kVmSpace and kernel dynamically allocate process space g_vMallocSpace. The second parameter passed in is obtained by the function OsGFirstTableGet(), i.e. g_firstPageTable, which is the primary page table base address used by the two process spaces of the kernel, with a size of 0x4000 bytes. It will be used later when setting the conversion table base address MMU virttb. These two functions are analyzed in detail below.

VOID OsKSpaceInit(VOID)
{
⑴  OsVmMapInit();
⑵  OsKernVmSpaceInit(&g_kVmSpace, OsGFirstTableGet());
    OsVMallocSpaceInit(&g_vMallocSpace, OsGFirstTableGet());
}

3.1.2 function OsKernVmSpaceInit

The function OsKernVmSpaceInit() initializes the virtual address space of the kernel process, sets the start address and size of the address space at (1), and sets the start address and size of the address space mapping area at (2). For the kernel virtual address space g_kVmSpace, the two start addresses and sizes are the same. ⑶ call the general address space initialization function at, and this function will be analyzed later.

BOOL OsKernVmSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb)
{
⑴  vmSpace->base = KERNEL_ASPACE_BASE;
    vmSpace->size = KERNEL_ASPACE_SIZE;
⑵  vmSpace->mapBase = KERNEL_VMM_BASE;
    vmSpace->mapSize = KERNEL_VMM_SIZE;
#ifdef LOSCFG_DRIVERS_TZDRIVER
    vmSpace->codeStart = 0;
    vmSpace->codeEnd = 0;
#endifreturn OsVmSpaceInitCommon(vmSpace, virtTtb);
}

3.1.3 function OsVMallocSpaceInit

The function OsVMallocSpaceInit() initializes the kernel heap virtual space, and the start address and size of the set virtual address space and the mapping area address space are the same. The code is similar to the function OsKernVmSpaceInit(), and will not be repeated.

BOOL OsVMallocSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb)
{
    vmSpace->base = VMALLOC_START;
    vmSpace->size = VMALLOC_SIZE;
    vmSpace->mapBase = VMALLOC_START;
    vmSpace->mapSize = VMALLOC_SIZE;
#ifdef LOSCFG_DRIVERS_TZDRIVER
    vmSpace->codeStart = 0;
    vmSpace->codeEnd = 0;
#endif
    return OsVmSpaceInitCommon(vmSpace, virtTtb);
}

3.2 user process virtual address space initialization

3.2.1 function OsCreateUserVmSpace

When creating a process, the function OsCreateUserVmSpace() is called to create the virtual address space of the user process. (1) apply for memory for the virtual address space structure. (2) apply for a memory page and call memset_s() is initialized to 0, the virtual address of this memory page will be used as the translation table base (TTB) of the page table, and the page table of virtual real mapping will be saved in this memory area. In the virtual real mapping chapter, we will talk about why to apply for 4KiB memory. (3) call the function OsUserVmSpaceInit at to initialize the virtual address space of the user process. (4) obtain the physical page structure address corresponding to the virtual address at. If initialization fails, the requested memory is released. (5) add the physical page to the page list of MMU in the virtual space, which maintains the memory page mapped by the process space.

LosVmSpace *OsCreateUserVmSpace(VOID)
{
    BOOL retVal = FALSE;

⑴   LosVmSpace *space = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmSpace));
    if (space == NULL) {
        return NULL;
    }

⑵  VADDR_T *ttb = LOS_PhysPagesAllocContiguous(1);
    if (ttb == NULL) {
        (VOID)LOS_MemFree(m_aucSysMem0, space);
        return NULL;
    }

    (VOID)memset_s(ttb, PAGE_SIZE, 0, PAGE_SIZE);
⑶  retVal = OsUserVmSpaceInit(space, ttb);
⑷  LosVmPage *vmPage = OsVmVaddrToPage(ttb);
    if ((retVal == FALSE) || (vmPage == NULL)) {
        (VOID)LOS_MemFree(m_aucSysMem0, space);
        LOS_PhysPagesFreeContiguous(ttb, 1);
        return NULL;
    }
⑸   LOS_ListAdd(&space->archMmu.ptList, &(vmPage->node));

    return space;
}

3.2.2 function OsUserVmSpaceInit

The function OsUserVmSpaceInit initializes the user process virtual address space, and sets the start address and size of the virtual address space at (1). ⑵ set the start address and size of the mapping area of the virtual space at. The start address is 1 / 2 of the start address of the virtual space, and the size is 1 / 8 of the size of the user's virtual space. (3) set the heap area of the virtual space at, and the start address is 1 / 4 of the start address of the virtual space.

BOOL OsUserVmSpaceInit(LosVmSpace *vmSpace, VADDR_T *virtTtb)
{
⑴  vmSpace->base = USER_ASPACE_BASE;
    vmSpace->size = USER_ASPACE_SIZE;
⑵  vmSpace->mapBase = USER_MAP_BASE;
    vmSpace->mapSize = USER_MAP_SIZE;
⑶  vmSpace->heapBase = USER_HEAP_BASE;
    vmSpace->heapNow = USER_HEAP_BASE;
    vmSpace->heap = NULL;
#ifdef LOSCFG_DRIVERS_TZDRIVER
    vmSpace->codeStart = 0;
    vmSpace->codeEnd = 0;
#endif
    return OsVmSpaceInitCommon(vmSpace, virtTtb);
}

3.3 general function of virtual address space initialization

3.3.1 function OsVmSpaceInitCommon

The function OsVmSpaceInitCommon is used to initialize the general part of the process virtual address space. ⑴ initializes the red black tree root node of the address space. (2) operate the mutex in the address interval of the initialized address space. (3) hang the newly created address space in the virtual address space two-way linked list g_vmSpaceList. (4) continue to call the function OsArchMmuInit() to complete the initialization of the MMU part of the address space.

STATIC BOOL OsVmSpaceInitCommon(LosVmSpace *vmSpace, VADDR_T *virtTtb)
{
⑴  LOS_RbInitTree(&vmSpace->regionRbTree, OsRegionRbCmpKeyFn, OsRegionRbFreeFn, OsRegionRbGetKeyFn);

⑵  status_t retval = LOS_MuxInit(&vmSpace->regionMux, NULL);
    if (retval != LOS_OK) {
        VM_ERR("Create mutex for vm space failed, status: %d", retval);
        return FALSE;
    }

    (VOID)LOS_MuxAcquire(&g_vmSpaceListMux);
⑶  LOS_ListAdd(&g_vmSpaceList, &vmSpace->node);
    (VOID)LOS_MuxRelease(&g_vmSpaceListMux);

⑷   return OsArchMmuInit(&vmSpace->archMmu, virtTtb);
}

3.3.2 function OsArchMmuInit

The function OsArchMmuInit() is used to initialize the MMU of the virtual address space. The MMU will be analyzed in detail in the subsequent series. You can quickly learn about it here. (1) obtain the address space number at. If the acquisition fails, FALSE is returned. (2) initialize MMU mutex. If initialization fails, FALSE is returned. (3) initialize the memory page bidirectional linked list at. (4) set TTB virtual address of MMU at. (5) set the TTB physical address of the MMU at. The TTB virtual address is based on the offset of the start address of the kernel virtual address space (UINTPTR)virtTtb - KERNEL_ASPACE_BASE plus physical address equals TTB physical address.

BOOL OsArchMmuInit(LosArchMmu *archMmu, VADDR_T *virtTtb)
{
#ifdef LOSCFG_KERNEL_VM
⑴   if (OsAllocAsid(&archMmu->asid) != LOS_OK) {
        VM_ERR("alloc arch mmu asid failed");
        return FALSE;
    }
#endif

⑵   status_t retval = LOS_MuxInit(&archMmu->mtx, NULL);
    if (retval != LOS_OK) {
        VM_ERR("Create mutex for arch mmu failed, status: %d", retval);
        return FALSE;
    }

⑶  LOS_ListInit(&archMmu->ptList);
⑷  archMmu->virtTtb = virtTtb;
⑸  archMmu->physTtb = (VADDR_T)(UINTPTR)virtTtb - KERNEL_ASPACE_BASE + SYS_MEM_BASE;
    return TRUE;
}

4. Common operations of virtual address interval

Virtual address interval operations are divided into search, application, release and other operations.

4.1 function LOS_RegionFind

(1) function Los at_ Regionfind is used to find and return the virtual address range corresponding to the specified virtual address in the process virtual address space. The two incoming parameters are virtual address space and virtual memory address respectively. This function has a sibling function LOS_RegionRangeFind(), see code 3, which can be used to find and return the virtual address range corresponding to the specified address range in the process space. The three incoming parameters specify the specified process space, virtual memory start address and address length (length unit: bytes). These two functions call the function OsFindRegion() to find the address interval. The reason why the third parameter at ⑵ is 1 is that the address interval is left closed and right open, and the end address of the interval will be reduced by 1. The code of this function is analyzed below.

⑴   LosVmMapRegion *LOS_RegionFind(LosVmSpace *vmSpace, VADDR_T addr)
    {
        LosVmMapRegion *region = NULL;

        (VOID)LOS_MuxAcquire(&vmSpace->regionMux);
⑵      region = OsFindRegion(&vmSpace->regionRbTree, addr, 1);
        (VOID)LOS_MuxRelease(&vmSpace->regionMux);

        return region;
    }
⑶  LosVmMapRegion *LOS_RegionRangeFind(LosVmSpace *vmSpace, VADDR_T addr, size_t len)
    {
        LosVmMapRegion *region = NULL;

        (VOID)LOS_MuxAcquire(&vmSpace->regionMux);
        region = OsFindRegion(&vmSpace->regionRbTree, addr, len);
        (VOID)LOS_MuxRelease(&vmSpace->regionMux);

        return region;
    }

4.2 function LOS_RegionAlloc

Function LOS_RegionAlloc is used to apply for an idle virtual address range from the address space. There are many parameters. LosVmSpace *vmSpace specifies the virtual address space and VADDR_T vaddr specifies the virtual address. When it is empty, it applies for the virtual address from the mapping area; When it is not empty, the virtual address is used. If the virtual address has been mapped, it will be unmapped first. size_t len specifies the length of the region interval to be applied for. UINT32 regionFlags specifies the label of the region. VM_OFFSET_T pgoff specifies the memory page offset value.

Let's look at the code. If the specified virtual address at (1) is empty, call the function OsAllocRange() to apply for memory. (2) if the specified virtual address is not empty, call the function OsAllocSpecificRange to apply for virtual memory. The two application functions will be analyzed in detail below. (3) create a virtual memory address interval at, and then specify the address space of the address interval as the current space vmSpace. (4) insert the created address interval into the red black tree of the address space.

LosVmMapRegion *LOS_RegionAlloc(LosVmSpace *vmSpace, VADDR_T vaddr, size_t len, UINT32 regionFlags, VM_OFFSET_T pgoff)
{
    VADDR_T rstVaddr;
    LosVmMapRegion *newRegion = NULL;
    BOOL isInsertSucceed = FALSE;
    /**
     * If addr is NULL, then the kernel chooses the address at which to create the mapping;
     * this is the most portable method of creating a new mapping.  If addr is not NULL,
     * then the kernel takes it as where to place the mapping;
     */
    (VOID)LOS_MuxAcquire(&vmSpace->regionMux);
    if (vaddr == 0) {
⑴        rstVaddr = OsAllocRange(vmSpace, len);
    } else {
        /* if it is already mmapped here, we unmmap it */
⑵      rstVaddr = OsAllocSpecificRange(vmSpace, vaddr, len, regionFlags);
        if (rstVaddr == 0) {
            VM_ERR("alloc specific range va: %#x, len: %#x failed", vaddr, len);
            goto OUT;
        }
    }
    if (rstVaddr == 0) {
        goto OUT;
    }

⑶  newRegion = OsCreateRegion(rstVaddr, len, regionFlags, pgoff);
    if (newRegion == NULL) {
        goto OUT;
    }
    newRegion->space = vmSpace;
⑷  isInsertSucceed = OsInsertRegion(&vmSpace->regionRbTree, newRegion);
    if (isInsertSucceed == FALSE) {
        (VOID)LOS_MemFree(m_aucSysMem0, newRegion);
        newRegion = NULL;
    }

OUT:
    (VOID)LOS_MuxRelease(&vmSpace->regionMux);
    return newRegion;
}

4.3 function LOS_RegionFree

Function LOS_RegionFree is used to release the region into the address space. (1) perform parameter verification. The parameter cannot be empty. (2) if the virtual file system macro is enabled at and the address range is a valid file type, call the function OsFilePagesRemove. (3) if the shared memory is enabled and the address range is shared, call the function OsShmRegionFree to release the shared memory range. When analyzing the shared memory part, see the code of the function in detail. (4) if the address interval is of device type, call the function OsDevPagesRemove to unmap, otherwise execute (5). These functions involve virtual real mapping, which will be analyzed in the chapter of virtual real mapping. (6) remove the address interval from the red black tree and release the memory occupied by the address interval structure.

STATUS_T LOS_RegionFree(LosVmSpace *space, LosVmMapRegion *region)
{
⑴   if ((space == NULL) || (region == NULL)) {
        VM_ERR("args error, aspace %p, region %p", space, region);
        return LOS_ERRNO_VM_INVALID_ARGS;
    }

    (VOID)LOS_MuxAcquire(&space->regionMux);

#ifdef LOSCFG_FS_VFS
⑵  if (LOS_IsRegionFileValid(region)) {
        OsFilePagesRemove(space, region);
    } else
#endif

#ifdef LOSCFG_KERNEL_SHM
⑶   if (OsIsShmRegion(region)) {
        OsShmRegionFree(space, region);
    } else if (LOS_IsRegionTypeDev(region)) {
#elseif (LOS_IsRegionTypeDev(region)) {
#endif
        OsDevPagesRemove(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT);
    } else {
⑸      OsAnonPagesRemove(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT);
    }

    /* remove it from space */
⑹   LOS_RbDelNode(&space->regionRbTree, &region->rbNode);
    /* free it */
    LOS_MemFree(m_aucSysMem0, region);
    (VOID)LOS_MuxRelease(&space->regionMux);
    return LOS_OK;
}

4.4 virtual memory internal implementation function

4.4.1 function OsAllocRange

The function OsAllocRange is used to apply for a specified length of memory from the virtual address space, and the return value is the applied virtual address. (1) obtain the address interval corresponding to the start address of the mapping area from the process space. When the obtained address interval is not NULL, execute ⑵ to obtain the red black tree node of the address interval and the end address of the address interval. ⑶ use the macro of red black tree to RB_MID_SCAN and RB_MID_SCAN_END, loop through the red black tree node pstRbNode and its subsequent nodes. (4) if the address interval obtained by the current traversing node and the mapping area overlaps, continue to traverse the next node. (5) if the length of the address interval meets the requirements, the virtual address is returned. Otherwise, execute (6) update the end address of the address interval and continue to traverse.

When the address interval obtained from the mapping area is NULL, execute (7). Macro pair RB of red black tree_ SCAN_ Safe and RB_SCAN_SAFE_END loops through the first tree node. The contents in the circulatory system are repeated above and will not be repeated. (8) if the mapping area does not apply for an appropriate virtual address, judge whether the address interval behind the mapping area meets the conditions. If you still cannot apply for the appropriate virtual address, return 0.

VADDR_T OsAllocRange(LosVmSpace *vmSpace, size_t len)
{
    LosVmMapRegion *curRegion = NULL;
    LosRbNode *pstRbNode = NULL;
    LosRbNode *pstRbNodeTmp = NULL;
    LosRbTree *regionRbTree = &vmSpace->regionRbTree;
    VADDR_T curEnd = vmSpace->mapBase;
    VADDR_T nextStart;

⑴  curRegion = LOS_RegionFind(vmSpace, vmSpace->mapBase);
    if (curRegion != NULL) {
⑵      pstRbNode = &curRegion->rbNode;
        curEnd = curRegion->range.base + curRegion->range.size;
⑶      RB_MID_SCAN(regionRbTree, pstRbNode)
            curRegion = (LosVmMapRegion *)pstRbNode;
            nextStart = curRegion->range.base;
⑷          if (nextStart < curEnd) {
                continue;
            }
⑸          if ((nextStart - curEnd) >= len) {
                return curEnd;
            } else {
⑹              curEnd = curRegion->range.base + curRegion->range.size;
            }
        RB_MID_SCAN_END(regionRbTree, pstRbNode)
    } else {
        /* rbtree scan is sorted, from small to big */
⑺      RB_SCAN_SAFE(regionRbTree, pstRbNode, pstRbNodeTmp)
            curRegion = (LosVmMapRegion *)pstRbNode;
            nextStart = curRegion->range.base;
            if (nextStart < curEnd) {
                continue;
            }
            if ((nextStart - curEnd) >= len) {
                return curEnd;
            } else {
                curEnd = curRegion->range.base + curRegion->range.size;
            }
        RB_SCAN_SAFE_END(regionRbTree, pstRbNode, pstRbNodeTmp)
    }

⑻  nextStart = vmSpace->mapBase + vmSpace->mapSize;
    if ((nextStart >= curEnd) && ((nextStart - curEnd) >= len)) {
        return curEnd;
    }

    return 0;
}

4.4.2 function OsAllocSpecificRange

The function OsAllocSpecificRange is used to request a specified length of memory from the virtual address space. If the specified virtual address has been mapped, the mapping will be cancelled, and the return value is the requested virtual address. (1) verify whether the virtual memory block is within the virtual address space. (2) judge whether the virtual address already belongs to an address range. If it does not belong to any address range, execute (5) to return the virtual address; If it belongs to an address range, continue to execute ⑶. If the address range label contains VM_MAP_REGION_FLAG_FIXED_NOREPLACE, if replacement is not allowed, 0 is returned; If the tag contains a VM_MAP_REGION_FLAG_FIXED, then LOS is called_ Unmap unmaps. If the above label is not included, execute (4) and re apply for the address range.

VADDR_T OsAllocSpecificRange(LosVmSpace *vmSpace, VADDR_T vaddr, size_t len, UINT32 regionFlags)
{
    STATUS_T status;

⑴  if (LOS_IsRangeInSpace(vmSpace, vaddr, len) == FALSE) {
        return 0;
    }

⑵   if ((LOS_RegionFind(vmSpace, vaddr) != NULL) ||
        (LOS_RegionFind(vmSpace, vaddr + len - 1) != NULL) ||
        (LOS_RegionRangeFind(vmSpace, vaddr, len - 1) != NULL)) {
⑶      if ((regionFlags & VM_MAP_REGION_FLAG_FIXED_NOREPLACE) != 0) {
            return 0;
        } else if ((regionFlags & VM_MAP_REGION_FLAG_FIXED) != 0) {
            status = LOS_UnMMap(vaddr, len);
            if (status != LOS_OK) {
                VM_ERR("unmmap specific range va: %#x, len: %#x failed, status: %d", vaddr, len, status);
                return 0;
            }
        } else {
⑷          return OsAllocRange(vmSpace, len);
        }
    }

⑸  return vaddr;
}

4.4.3 function OsCreateRegion

The OsCreateRegion function is used to create an address range according to virtual address, memory size, address range label and other information. (1) apply for memory for the address interval structure, and (2) set the address interval attribute value according to the parameters. The code is relatively simple. You can read it yourself.

LosVmMapRegion *OsCreateRegion(VADDR_T vaddr, size_t len, UINT32 regionFlags, unsigned long offset)
{
⑴  LosVmMapRegion *region = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmMapRegion));
    if (region == NULL) {
        VM_ERR("memory allocate for LosVmMapRegion failed");
        return region;
    }

⑵  region->range.base = vaddr;
    region->range.size = len;
    region->pgOff = offset;
    region->regionFlags = regionFlags;
    region->regionType = VM_MAP_REGION_TYPE_NONE;
    region->forkFlags = 0;
    region->shmid = -1;
    return region;
}

4.4.4 function OsInsertRegion

The function OsInsertRegion is used to insert the red black tree node into the red black tree. (1) call function Los at_ Rbaddnode is inserted into the red black tree node. The first member of the LosVmMapRegion structure is of type LosRbNode, which can be forcibly converted. (2) if the node insertion fails, the address space information will be printed. The code is relatively simple.

BOOL OsInsertRegion(LosRbTree *regionRbTree, LosVmMapRegion *region)
{
⑴   if (LOS_RbAddNode(regionRbTree, (LosRbNode *)region) == FALSE) {
        VM_ERR("insert region failed, base: %#x, size: %#x", region->range.base, region->range.size);
⑵       OsDumpAspace(region->space);
        return FALSE;
    }
    return TRUE;
}

4.4.5 OsFindRegion function

The OsFindRegion function searches the address range according to the virtual memory address. (1) set the start address and size of the address range at. (2) call the function Los at_ Rbgetnode() obtains the red black tree node pstRbNode from the red black tree. If it succeeds, it will continue to execute ⑶ convert from the red black tree node to the required address range. There will be a special series to explain the red black tree, and then analyze the function LOS_RbGetNode().

LosVmMapRegion *OsFindRegion(LosRbTree *regionRbTree, VADDR_T vaddr, size_t len)
{
    LosVmMapRegion *regionRst = NULL;
    LosRbNode *pstRbNode = NULL;
    LosVmMapRange rangeKey;
⑴  rangeKey.base = vaddr;
    rangeKey.size = len;

⑵  if (LOS_RbGetNode(regionRbTree, (VOID *)&rangeKey, &pstRbNode)) {
⑶      regionRst = (LosVmMapRegion *)LOS_DL_LIST_ENTRY(pstRbNode, LosVmMapRegion, rbNode);
    }
    return regionRst;
}

5. VMalloc common operations

The kernel dynamic allocation of virtual address space is divided into two operations: application and release.

5.1 function LOS_VMalloc

Function LOS_VMalloc is used to apply for memory from VMalloc's dynamically allocated memory heap virtual address space. The parameter is the number of bytes to be applied. (1) page align the requested memory size, and calculate the page sizeCount from the number of bytes. (2) declare a memory page bidirectional linked list at. (3) apply for a specified number of physical memory pages and mount them on the two-way linked list pageList. (4) allocate heap process space from dynamic memory G_ Apply for virtual memory address range in vmallocspace. At this time, the virtual memory and physical memory are successfully applied, and the number of pages is the same. Next, execute (5) cycle through each memory page on the two-way linked list of physical pages for virtual real mapping. (6) obtain the physical memory address of the physical memory page, and then increase the reference count of the physical memory page by 1. (7) perform virtual real mapping at, then increase the virtual memory address by one memory page, and continue the circular traversal. (8) return the memory start address of the applied virtual address range. Virtual real mapping function LOS_ArchMmuMap is explained in detail in the MMU virtual real mapping series.

VOID *LOS_VMalloc(size_t size)
{
    LosVmSpace *space = &g_vMallocSpace;
    LosVmMapRegion *region = NULL;
    size_t sizeCount;
    size_t count;
    LosVmPage *vmPage = NULL;
    VADDR_T va;
    PADDR_T pa;
    STATUS_T ret;

⑴  size = LOS_Align(size, PAGE_SIZE);
    if ((size == 0) || (size > space->size)) {
        return NULL;
    }
    sizeCount = size >> PAGE_SHIFT;

⑵   LOS_DL_LIST_HEAD(pageList);
    (VOID)LOS_MuxAcquire(&space->regionMux);

⑶  count = LOS_PhysPagesAlloc(sizeCount, &pageList);
    if (count < sizeCount) {
        VM_ERR("failed to allocate enough pages (ask %zu, got %zu)", sizeCount, count);
        goto ERROR;
    }

    /* allocate a region and put it in the aspace list */
⑷   region = LOS_RegionAlloc(space, 0, size, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE, 0);
    if (region == NULL) {
        VM_ERR("alloc region failed, size = %x", size);
        goto ERROR;
    }

    va = region->range.base;
⑸  while ((vmPage = LOS_ListRemoveHeadType(&pageList, LosVmPage, node))) {
⑹      pa = vmPage->physAddr;
        LOS_AtomicInc(&vmPage->refCounts);
⑺      ret = LOS_ArchMmuMap(&space->archMmu, va, pa, 1, region->regionFlags);
        if (ret != 1) {
            VM_ERR("LOS_ArchMmuMap failed!, err;%d", ret);
        }
        va += PAGE_SIZE;
    }

    (VOID)LOS_MuxRelease(&space->regionMux);
⑻   return (VOID *)(UINTPTR)region->range.base;

ERROR:
    (VOID)LOS_PhysPagesFree(&pageList);
    (VOID)LOS_MuxRelease(&space->regionMux);
    return NULL;
}

5.2 function LOS_VFree

Function LOS_VFree is used to release the virtual memory applied from the virtual address space of VMalloc dynamic memory heap. The passed in parameter is the virtual address. (1) obtain the virtual address interval according to the virtual address, and then execute (2) release the address interval, where the function LOS_RegionFree has been described in detail above.

VOID LOS_VFree(const VOID *addr)
{
    LosVmSpace *space = &g_vMallocSpace;
    LosVmMapRegion *region = NULL;
    STATUS_T ret;

    if (addr == NULL) {
        VM_ERR("addr is NULL!");
        return;
    }

    (VOID)LOS_MuxAcquire(&space->regionMux);

⑴  region = LOS_RegionFind(space, (VADDR_T)(UINTPTR)addr);
    if (region == NULL) {
        VM_ERR("find region failed");
        goto DONE;
    }

⑵   ret = LOS_RegionFree(space, region);
    if (ret) {
        VM_ERR("free region failed, ret = %d", ret);
    }

DONE:
    (VOID)LOS_MuxRelease(&space->regionMux);
}

6 others

6.1 function LOS_VmSpaceReserve

Function LOS_VmSpaceReserve is used to reserve a memory space in the process space. (1) check the parameters first. (2) first judge that the virtual address and size are within the specified virtual address space. (3) query the mapping label of the specified virtual address at. (4) label VM at_ MAP_ REGION_ FLAG_ Fixed applies for an address range.

STATUS_T LOS_VmSpaceReserve(LosVmSpace *space, size_t size, VADDR_T vaddr)
{
    UINT32 regionFlags = 0;

⑴  if ((space == NULL) || (size == 0) || (!IS_PAGE_ALIGNED(vaddr) || !IS_PAGE_ALIGNED(size))) {
        return LOS_ERRNO_VM_INVALID_ARGS;
    }

⑵  if (!LOS_IsRangeInSpace(space, vaddr, size)) {
        return LOS_ERRNO_VM_OUT_OF_RANGE;
    }

    /* lookup how it's already mapped */
⑶  (VOID)LOS_ArchMmuQuery(&space->archMmu, vaddr, NULL, &regionFlags);

    /* build a new region structure */
⑷  LosVmMapRegion *region = LOS_RegionAlloc(space, vaddr, size, regionFlags | VM_MAP_REGION_FLAG_FIXED, 0);

    return region ? LOS_OK : LOS_ERRNO_VM_NO_MEMORY;
}

summary

This paper analyzes the relevant source code of virtual memory management. Firstly, it introduces the structure and related macro definitions of virtual memory management, then analyzes how to initialize the kernel virtual address space and user process virtual address space, then analyzes the common operations of virtual memory interval, including search, application and release, and finally analyzes the source code of the application and release interface of dynamic memory heap, And briefly introduce the source code of the memory interval reservation interface. More sharing articles will be launched in the future. Please look forward to it. If you have any questions or suggestions, you can leave a message to me. thank you.

 

Click focus to learn about Huawei cloud's new technologies for the first time~

Keywords: OpenHarmony

Added by Cerebral Cow on Fri, 12 Nov 2021 07:19:37 +0200