摘要:本文以代码+文字的形式,介绍虚拟内存管理的结构体、相关宏定义,分析内核虚拟地址空间和用户进程虚拟地址空间如何初始化等内容。

本文分享自华为云社区《鸿蒙轻内核A核源码分析系列四(2) 虚拟内存》,作者: zhushy 。

本文中所涉及的源码,以OpenHarmony LiteOS-A内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_a 获取。如果涉及开发板,则默认以hispark_taurus为例。

我们首先了解了虚拟内存管理的结构体、相关宏定义,接着会分析内核虚拟地址空间和用户进程虚拟地址空间如何初始化,然后分析虚拟内存区间常用操作包含查找、申请和释放等,最后分析动态内存堆的申请、释放接口的源代码,并简单介绍下内存区间预留接口源代码。

1、 虚拟内存管理相关的结构体

在文件kernel/base/include/los_vm_map.h中定义了进程地址空间结构体LosVmSpace,进程地址区间结构体LosVmMapRegion和进程地址区间范围结构体LosVmMapRange。每个用户态进程会创建自己的进程空间,内核态会创建2个进程空间,分别g_kVmSpace和g_vMallocSpace。从进程空间申请的虚拟内存块使用进程区间LosVmMapRegion来表示。每个进程空间维护一个红黑树来链接各个进程区间。

1.1 虚拟内存地址空间结构体LosVmSpace

typedef struct VmSpace {LOS_DL_LIST         node;           /**< 地址空间双向链表 */LosRbTree           regionRbTree;   /**< 地址区间的红黑树根节点 */LosMux              regionMux;      /**< 地址区间的红黑树的互斥锁 */VADDR_T             base;           /**< 地址空间开始地址 */UINT32              size;           /**< 地址空间大小 */VADDR_T             heapBase;       /**< 地址空间的堆开始地址heapBase */VADDR_T             heapNow;        /**< 地址空间的堆开始地址heapNow */LosVmMapRegion      *heap;          /**< 地址空间的地址区间 */VADDR_T             mapBase;        /**< 地址空间的映射区开始地址 */UINT32              mapSize;        /**< 地址空间的映射区大小 */LosArchMmu          archMmu;        /**< 地址空间的MMU结构体 */
#ifdef LOSCFG_DRIVERS_TZDRIVERVADDR_T             codeStart;      /**< 用户进程代码区开始地址 */VADDR_T             codeEnd;        /**< 用户进程代码区结束地址 */
#endif
} LosVmSpace;

1.2 虚拟内存地址区间LosVmMapRegion

typedef struct VmMapRange {VADDR_T             base;           /**< 虚拟内存地址区间开始地址 */UINT32              size;           /**< 虚拟内存地址区间大小 */
} LosVmMapRange;
......
struct VmMapRegion;
typedef struct VmMapRegion LosVmMapRegion;
......
struct VmMapRegion {LosRbNode           rbNode;         /**<  地址区间红黑树节点 */LosVmSpace          *space;         /**<  地址区间所在的地址空间 */LOS_DL_LIST         node;           /**<  地址区间双向链表 */LosVmMapRange       range;          /**<  地址区间地址范围 */VM_OFFSET_T         pgOff;          /**<  地址区间页偏移 */UINT32              regionFlags;    /**<  地址区间标记: cow, user_wired */UINT32              shmid;          /**<  共享地址区间编号 */UINT8               forkFlags;      /**<  地址区间fork标记: COPY, ZERO, */UINT8               regionType;     /**<  地址区间类型: ANON, FILE, DEV */union {struct VmRegionFile {unsigned int fileMagic;struct file *file;const LosVmFileOps *vmFOps;} rf;struct VmRegionAnon {LOS_DL_LIST  node;          /**< 地址区间类型的双向链表 */} ra;struct VmRegionDev {LOS_DL_LIST  node;          /**< 地址区间类型的双向链表 */const LosVmFileOps *vmFOps;} rd;} unTypeData;
};

2、 虚拟内存相关的宏定义

文件kernel/base/include/los_vm_common.h和kernel/base/include/los_vm_zone.h定义了虚拟内存相关的宏。对于32位系统,虚拟进程空间大小为4GiB,OpenHarmony鸿蒙轻内核当前支持32位系统。⑴和⑵定义了用户进程虚拟地址空间的开始地址和大小,⑶是用户虚拟进程空间的结束地址,接着定义的是用户虚拟进程空间的堆区、映射区的开始地址和大小。

/* 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))

内核虚拟进程空间的宏定义如下,⑴处定义内核进程地址空间开始地址和大小,⑵处定义内核非缓存虚拟地址空间开始地址和大小,⑶处定义虚拟动态分配地址空间开始地址和大小,⑷处定义外设开始地址和大小,⑸处定义外设缓存区开始地址和大小,⑹处定义外设非缓存区开始地址和大小。

    #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

虚拟地址空间分布示意图如下:

3、进程地址空间初始化

虚拟进程空间分用户虚拟进程空间和内核虚拟进程空间,每个用户进程都会创建属于自己的进程空间。内核会初始化2个进程空间。下文详细介绍。

3.1 内核虚拟地址空间初始化

3.1.1 函数OsKSpaceInit

函数OsKSpaceInit()初始化内核进程虚拟地址空间,⑴处的函数初始化虚拟空间链表互斥锁g_vmSpaceListMux,在操作内核进程空间时需要持有该互斥锁。⑵处开始的函数2个函数OsKernVmSpaceInit和OsVMallocSpaceInit分别初始化内核进程虚拟空间g_kVmSpace和内核动态分配进程空间g_vMallocSpace。传入的第2个参数由函数OsGFirstTableGet()获取,即g_firstPageTable,这是内核的2个进程空间使用的一级页表基地址,大小为0x4000字节,后文在设置转化表基地址MMU virtTtb时会使用。下文会详细分析这2个函数。

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

3.1.2 函数OsKernVmSpaceInit

函数OsKernVmSpaceInit()初始化内核进程虚拟地址空间,⑴处设置地址空间的开始地址和大小,⑵处设置地址空间映射区的开始地址和大小,对于内核虚拟地址空间g_kVmSpace,这2个开始地址和大小是一样的。⑶处调用通用的地址空间初始化函数,后文分析此函数。

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_TZDRIVERvmSpace->codeStart = 0;vmSpace->codeEnd = 0;
#endif
⑶   return OsVmSpaceInitCommon(vmSpace, virtTtb);
}

3.1.3 函数OsVMallocSpaceInit

函数OsVMallocSpaceInit()初始化内核堆虚拟空间,设置的虚拟地址空间和映射区地址空间的开始地址和大小也是一样的,代码和函数OsKernVmSpaceInit()类似,不再赘述。

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_TZDRIVERvmSpace->codeStart = 0;vmSpace->codeEnd = 0;
#endifreturn OsVmSpaceInitCommon(vmSpace, virtTtb);
}

3.2 用户进程虚拟地址空间初始化

3.2.1 函数OsCreateUserVmSpace

在创建进程时,会调用函数OsCreateUserVmSpace()创建用户进程的虚拟地址空间。⑴为虚拟地址空间结构体申请内存。⑵申请一个内存页,并调用memset_s()初始化为0,这个内存页虚拟地址会作为页表转换基地址TTB(translation table base,ttb),虚实映射的页表会保存在这个内存区域。在虚实映射章节,会讲述为什么申请4KiB大小内存。⑶处调用函数OsUserVmSpaceInit初始化用户进程虚拟地址空间。⑷处获取虚拟地址对应的物理页结构体地址。如果初始化失败,则释放申请的内存。⑸处把物理页加入虚拟空间中的MMU的页表链表中,这个链表维护该进程空间映射的内存页。

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

函数OsUserVmSpaceInit初始化用户进程虚拟地址空间,⑴处设置虚拟地址空间的开始地址和大小。⑵处设置虚拟空间的映射区的开始地址和大小,开始地址在虚拟空间开始地址的1/2处,大小为用户虚拟空间大小的1/8。⑶处设置虚拟空间的堆区,开始地址为虚拟空间开始地址的1/4处。

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_TZDRIVERvmSpace->codeStart = 0;vmSpace->codeEnd = 0;
#endifreturn OsVmSpaceInitCommon(vmSpace, virtTtb);
}

3.3 虚拟地址空间初始化的通用函数

3.3.1 函数OsVmSpaceInitCommon

函数OsVmSpaceInitCommon用于进程虚拟地址空间的通用部分的初始化,⑴处初始化地址空间的红黑树根节点。⑵处初始化地址空间的地址区间操作互斥锁。⑶处把新创建的地址空间挂在虚拟地址空间双向链表g_vmSpaceList上。⑷处继续调用函数OsArchMmuInit()完成地址空间MMU部分的初始化。

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

函数OsArchMmuInit()用于初始化虚拟地址空间的MMU,MMU在后续系列会详细分析,此处快速了解一下即可。⑴处获取地址空间编号,如果获取失败则返回FALSE。⑵初始化MMU互斥锁,如果初始化失败则返回FALSE。⑶处初始化内存页双向链表。⑷处设置MMU的TTB虚拟地址。⑸处设置MMU的TTB物理地址,TTB虚拟地址基于内核虚拟地址空间开始地址的偏移(UINTPTR)virtTtb - KERNEL_ASPACE_BASE加上物理地址就等于TTB物理地址。

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、虚拟地址区间常用操作

虚拟地址区间操作分为查找、申请、释放等操作。

4.1 函数LOS_RegionFind

⑴处的函数LOS_RegionFind用于在进程虚拟地址空间内查找并返回指定虚拟地址对应的虚拟地址区间,两个传入参数分别是虚拟地址空间和虚拟内存地址。该函数有个兄弟函数LOS_RegionRangeFind(),见⑶处代码,可以用于在进程空间内查找并返回指定地址范围对应的虚拟地址区间,三个传入参数分别指定指定进程空间、虚拟内存开始地址和地址长度(长度单位字节)。这2个函数都调用函数OsFindRegion()实现地址区间的查找,⑵处的第3个参数为1的原因是地址区间是左闭右开区间,区间的结束地址会减1。下文会分析该函数的代码。

⑴   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 函数LOS_RegionAlloc

函数LOS_RegionAlloc用于从地址空间中申请空闲的虚拟地址区间。参数较多,LosVmSpace *vmSpace指定虚拟地址空间,VADDR_T vaddr指定虚拟地址,当为空时,从映射区申请虚拟地址;当不为空时,使用该虚拟地址。如果该虚拟地址已经被映射,会先相应的解除映射处理等。size_t len指定要申请的地区区间的长度。UINT32 regionFlags指定地区区间的标签。VM_OFFSET_T pgoff指定内存页偏移值。

我们具体看下代码,⑴处如果指定的虚拟地址为空,则调用函数OsAllocRange()申请内存。⑵如果指定的虚拟地址不为空,则调用函数OsAllocSpecificRange申请虚拟内存,下文会详细分析这2个申请函数。⑶处创建虚拟内存地址区间,然后指定地址区间的地址空间为当前空间vmSpace。⑷处把创建的地址区间插入地址空间的红黑树中。

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

函数LOS_RegionFree用于释放地区区间到地址空间中。⑴进行参数校验,参数不能为空。⑵处如果开启了虚拟文件系统宏,并且地址区间是有效的文件类型,则调用函数OsFilePagesRemove。⑶处如果开启了共享内存,并且地址区间是共享的,则调用函数OsShmRegionFree释放共享内存区间,分析共享内存部分时再详细看该函数的代码。⑷如果地址区间是设备类型的,则调用函数OsDevPagesRemove解除映射,否则执行⑸。这些函数都涉及虚实映射,会在虚实映射章节分析这些函数。⑹处把地址区间从红黑树上移除,并释放地址区间结构体占用的内存。

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)) {
#else
⑷   if (LOS_IsRegionTypeDev(region)) {
#endifOsDevPagesRemove(&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 虚拟内存内部实现函数

4.4.1 函数OsAllocRange

函数OsAllocRange用于从虚拟地址空间中申请指定长度的内存,返回值为申请到的虚拟地址。⑴处从进程空间中获取映射区开始地址对应的地址区间。当获取的地址区间不为NULL时,执行⑵,获取地址区间的红黑树节点,并获取该地址区间的结束地址。⑶处使用红黑树的宏对RB_MID_SCAN和RB_MID_SCAN_END,循环遍历红黑树节点pstRbNode及其后续节点。⑷处如果当前遍历节点和映射区获取的地址区间有重叠则继续遍历下一个节点。⑸处如果地址区间长度满足要求,则返回虚拟地址,否则执行⑹更新地址区间的结束地址继续遍历。

当从映射区获取的地址区间为NULL时,执行⑺。红黑树的宏对RB_SCAN_SAFE和RB_SCAN_SAFE_END会从第一个树节点循环遍历。循环体内的内容和上文重复,不再赘述。⑻如果映射区没有申请到合适的虚拟地址,则判断下在映射区后的地址区间是否满足条件。如果依旧申请不到合适的虚拟地址,返回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 函数OsAllocSpecificRange

函数OsAllocSpecificRange用于从虚拟地址空间中申请指定长度的内存,如果指定的虚拟地址已经被映射,则取消映射,返回值为申请到的虚拟地址。⑴处验证虚拟内存块是否在虚拟地址空间范围内。⑵处判断虚拟地址是否已经属于某个地址区间,如果不属于任何地址区间,则执行⑸返回该虚拟地址;如果属于某个地址区间,则继续执行⑶,如果地址区间标签包含VM_MAP_REGION_FLAG_FIXED_NOREPLACE,不允许替换,则返回0;如果标签包含VM_MAP_REGION_FLAG_FIXED,则调用LOS_UnMMap取消映射。如果不包含上述标签,则执行⑷,重新申请地址区间。

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

函数OsCreateRegion用于根据虚拟地址、内存大小、地址区间标签等信息创建地址区间。⑴处为地址区间结构体申请内存,⑵处根据参数设置地址区间属性值。代码比较简单,自行阅读即可。

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

函数OsInsertRegion用于把红黑树节点插入红黑树。⑴处调用函数LOS_RbAddNode插入红黑树节点,LosVmMapRegion结构体的第一个成员是LosRbNode类型,二者可以强转。⑵处如果插入节点失败,则打印地址空间信息。代码比较简单。

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

函数OsFindRegion实现根据虚拟内存地址查找地址区间。⑴处设置地址区间范围的开始地址和大小。⑵处调用函数LOS_RbGetNode()从红黑树上获取红黑树节点pstRbNode,获取成功时会继续执行⑶从红黑树节点转换为需要的地址区间。后续会有专门的系列讲解红黑树,届时再分析函数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常用操作

内核动态分配虚拟地址空间操作分为申请和释放2个操作。

5.1 函数LOS_VMalloc

函数LOS_VMalloc用于从VMalloc动态分配内存堆虚拟地址空间中申请内存,参数为需要申请的字节数。⑴处把申请的内存大小进行页对齐,并由字节数计算页数sizeCount。⑵处声明一个内存页双向链表。⑶处申请指定数量的物理内存页并挂载到双向链表pageList上。⑷处从动态内存分配堆进程空间g_vMallocSpace中申请虚拟内存地址区间。此时成功申请了虚拟内存和物理内存,而且页数也是一样的,下面执行⑸循环遍历物理页双向链表上的每一个内存页进行虚实映射。⑹处获取物理内存页的物理内存地址,然后把物理内存页的引用计数自增加1。⑺处进行虚实映射,然后把虚拟内存地址增加一个内存页的大小,继续循环遍历。⑻处返回申请到的虚拟地址区间的内存开始地址。虚实映射函数LOS_ArchMmuMap在MMU虚实映射系列来详细讲解。

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

函数LOS_VFree用于释放从VMalloc动态内存堆虚拟地址空间中申请的虚拟内存,传入参数为虚拟地址。 ⑴处根据虚拟地址获取虚拟地址区间,然后执行⑵释放地址区间,其中函数LOS_RegionFree在前文已经详细讲述。

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 其他

6.1 函数LOS_VmSpaceReserve

函数LOS_VmSpaceReserve用于在在进程空间中预留一块内存空间。⑴处先做参数校验。⑵处先判断虚拟地址和大小在指定的虚拟地址空间内。⑶处查询指定的虚拟地址的映射标签。⑷处加上标签VM_MAP_REGION_FLAG_FIXED申请一段地址区间。

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

总结

本文分析虚拟内存管理的相关源代码,首先介绍虚拟内存管理的结构体、相关宏定义,接着会分析内核虚拟地址空间和用户进程虚拟地址空间如何初始化,然后分析虚拟内存区间常用操作包含查找、申请和释放等,最后分析动态内存堆的申请、释放接口的源代码,并简单介绍下内存区间预留接口源代码。后续也会陆续推出更多的分享文章,敬请期待,有任何问题、建议,都可以留言给我。谢谢。

点击关注,第一时间了解华为云新鲜技术~

鸿蒙轻内核源码分析:虚拟内存相关推荐

  1. 鸿蒙轻内核源码分析:虚拟文件系统 VFS

    本文分享自华为云社区<鸿蒙轻内核M核源码分析系列二一 01 虚拟文件系统VFS>,作者:zhushy . VFS(Virtual File System)是文件系统的虚拟层,它不是一个实际 ...

  2. 鸿蒙轻内核源码分析:MMU协处理器

    摘要:本系列首先了解下ARM CP15协处理器的知识,接着介绍下协处理器相关的汇编指令,最后分析下MMU相关汇编代码. 本文分享自华为云社区<鸿蒙轻内核A核源码分析系列六 MMU协处理器> ...

  3. 鸿蒙轻内核源码分析:异常钩子模块系统中断异常,如何转储异常信息

    摘要:本篇介绍下鸿蒙轻内核中异常钩子模块发生系统中断异常时如何转储异常信息. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十七(3) 异常信息ExcInfo>,作者: zhushy. ...

  4. 鸿蒙轻内核源码分析:掌握信号量使用差异

    摘要:本文带领大家一起剖析鸿蒙轻内核的信号量模块的源代码,包含信号量的结构体.信号量池初始化.信号量创建删除.申请释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十一 信号量Semap ...

  5. 鸿蒙轻内核源码分析:文件系统LittleFS

    摘要:本文先介绍下LFS文件系统结构体的结构体和全局变量,然后分析下LFS文件操作接口. 本文分享自华为云社区<# 鸿蒙轻内核M核源码分析系列二一 02 文件系统LittleFS>,作者: ...

  6. 鸿蒙轻内核源码分析:虚实映射

    摘要:本文介绍了MMU虚实映射的基本概念,运行机制,分析了映射初始化.映射查询.映射虚拟内存和物理内存,解除虚实映射,更改映射属性,重新映射等常用接口的代码. 本文分享自华为云社区<使用MRS ...

  7. v15.03 鸿蒙内核源码分析(内存映射) | 映射真是个好东西 | 百篇博客分析HarmonyOS源码

    子曰:"德不孤,必有邻." <论语>:里仁篇 百篇博客系列篇.本篇为: v15.xx 鸿蒙内核源码分析(内存映射篇) | 映射真是个好东西 内存管理相关篇为: v11. ...

  8. v11.03 鸿蒙内核源码分析(内存分配) | 内存有哪些分配方式 | 百篇博客分析HarmonyOS源码

    子曰:"君子周而不比,小人比而不周."<论语>:为政篇 百篇博客系列篇.本篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式 内存管理相关篇为 ...

  9. v74.01 鸿蒙内核源码分析(编码方式篇) | 机器指令是如何编码的 | 百篇博客分析OpenHarmony源码

    Python微信订餐小程序课程视频 https://blog.csdn.net/m0_56069948/article/details/122285951 Python实战量化交易理财系统 https ...

最新文章

  1. smo算法matlab实现
  2. 如何实现java虚拟机的优化_Java虚拟机JVM优化实战的过程全记录
  3. oracle正则表达式截断,在oracle中使用正则表达式截取字符串
  4. 微软的 Android 计划:邪恶的天才计划或只是邪恶?
  5. python集合类型中的元素是有序的_Python基础-2-变量和数据类型(2)-列表、元组、字典、集合...
  6. 云计算及应用课程知识整理
  7. csdn颜色字体的改变
  8. 如何做一个跨平台的游戏App?
  9. 人生是一场旅程,重要的不是终点,是自己路上的风景
  10. 服务器 战地4 无限载入,战地4卡在loading界面无限载入的解决方法_快吧单机游戏...
  11. Msc.Marc的python开发#2
  12. 多个操作语句的触发器为什么在执行时,只执行了第一句?
  13. U盘装系统:魔方U盘启动制作
  14. thinkPHP 接口访问限制
  15. Android 自定义相机 身份证拍照 自定义身份证相机
  16. [BJDCTF 2nd]燕言燕语-y1ng解析
  17. Java生成动态GIF图片
  18. 计算机网络-拥塞控制/HTTP/URL
  19. [ MessAuto ]: 短信验证码自动填充,理论支持所有浏览器或 APP, Only For Mac
  20. Android中身份证正反面、营业执照横竖版的拍摄和裁剪

热门文章

  1. 调优 | 别再说你不会 JVM 性能监控和调优了
  2. ajax get 不会缓存,ajax的get请求时缓存处理解决方法
  3. spark中dataframe解析_Spark 结构流处理介绍和入门教程
  4. unbuntu管理员的切换
  5. mysql数据库如何配置服务_MySQL服务如何实现安装及配置
  6. gis怎么提取水系_SketchUp+Global Mapper 地形提取,连建模都省了...
  7. oracle获取 小时数,Oracle函数 通过秒数或分钟数获取时间段
  8. python怎样填充颜色_python中如何给图形填充颜色
  9. 由任意二叉树的前序遍历序列和中序遍历序列求二叉树的思想方法_算法与数据结构基础 - 二叉树(Binary Tree)...
  10. python默认参数不能定义为可变对象类型