本章开始说内存,内存的管理是极其复杂的模块,涉及到非常多概念,光地址就有逻辑,线性,物理地址三个,网上文章很多,参差不齐,没有很好基础或实战经验的同学基本得懵掉,本篇最后也有这些概念介绍。系列篇打算用三篇来讲述鸿蒙内核的内存管理机制。由浅入深,层层递进。我们换个视角切入,将从进程和线程创建的视角看内存的运作机制。为何从进程和线程角度?

两个原因:1.内存就是给他们使用的,只是分了内核空间和用户空间。用户空间的进程分配用到了虚拟内存,线程(task)需要分配栈空间 2.系列文章对进程和线程的管理和调度已经说完了,但是没有说内存,还有IPC(也是复杂的模块),有了前面的基础我们再来说鸿蒙的内存管理会轻松些。

进程内存描述符LosVmSpace

typedef struct ProcessCB {

//..只留下相关部分
LosVmSpace          *vmSpace;       /**< VMM space for processes */

}LosProcessCB; typedef struct VmSpace {

LOS_DL_LIST         node;           /**< vm space dl list */
LOS_DL_LIST         regions;        /**< region dl list */
LosRbTree           regionRbTree;   /**< region red-black tree root */
LosMux              regionMux;      /**< region list mutex lock */
VADDR_T             base;           /**< vm space base addr */
UINT32              size;           /**< vm space size */
VADDR_T             heapBase;       /**< vm space heap base address */
VADDR_T             heapNow;        /**< vm space heap base now */
LosVmMapRegion      *heap;          /**< heap region */
VADDR_T             mapBase;        /**< vm space mapping area base */
UINT32              mapSize;        /**< vm space mapping area size */
LosArchMmu          archMmu;        /**< vm mapping physical memory */

#ifdef LOSCFG_DRIVERS_TZDRIVER

VADDR_T             codeStart;      /**< user process code area start */
VADDR_T             codeEnd;        /**< user process code area end */

#endif } LosVmSpace;

会加入到全局的g_vmSpaceList

从用户态的第一个进程初始化说起

所有应用程序的爸爸都是“init”这个用户进程fork来的,看代码他是如何初始化的,这个函数之前也讲过,但没有说内存部分,这次专讲内存部分,涉及代码都已经加了注释。用户进程就是只能运行在用户空间,内核空间是通过系统调用访问的。

LITE_OS_SEC_TEXT_INIT UINT32 OsUserInitProcess(VOID)
{

INT32 ret;
UINT32 size;
TSK_INIT_PARAM_S param = { 0 };
VOID *stack = NULL;
VOID *userText = NULL;
CHAR *userInitTextStart = (CHAR *)&__user_init_entry;//*kfy 代码区开始位置
CHAR *userInitBssStart = (CHAR *)&__user_init_bss;//*kyf 未初始化数据区(BSS)。在运行时改变其值
CHAR *userInitEnd = (CHAR *)&__user_init_end;//*kyf 结束地址
UINT32 initBssSize = userInitEnd - userInitBssStart;
UINT32 initSize = userInitEnd - userInitTextStart;

LosProcessCB *processCB = OS_PCB_FROM_PID(g_userInitProcess);
ret = OsProcessCreateInit(processCB, OS_USER_MODE, "Init", OS_PROCESS_USERINIT_PRIORITY);//*kyf 初始化用户进程,它将是所有应用程序的父进程
if (ret != LOS_OK) {
    return ret;
}

userText = LOS_PhysPagesAllocContiguous(initSize >> PAGE_SHIFT);//*kyf 分配连续的物理页
if (userText == NULL) {
    ret = LOS_NOK;
    goto ERROR;
}

(VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);//*kyf 安全copy __user_init_load_addr -> userText
ret = LOS_VaddrToPaddrMmap(processCB->vmSpace, (VADDR_T)(UINTPTR)userInitTextStart, LOS_PaddrQuery(userText),
                           initSize, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |
                           VM_MAP_REGION_FLAG_PERM_EXECUTE | VM_MAP_REGION_FLAG_PERM_USER);//*kyf 虚拟地址与物理地址的映射
if (ret < 0) {
    goto ERROR;
}

(VOID)memset_s((VOID *)((UINTPTR)userText + userInitBssStart - userInitTextStart), initBssSize, 0, initBssSize);//*kyf 除了代码段,其余都清0

stack = OsUserInitStackAlloc(g_userInitProcess, &size);//*kyf 初始化堆栈区
if (stack == NULL) {
    PRINTK("user init process malloc user stack failed!\n");
    ret = LOS_NOK;
    goto ERROR;
}

param.pfnTaskEntry = (TSK_ENTRY_FUNC)userInitTextStart;//*kyf 从代码区开始执行,也就是应用程序main 函数的位置
param.userParam.userSP = (UINTPTR)stack + size;//*kyf 指向栈顶
param.userParam.userMapBase = (UINTPTR)stack;//*kyf 栈底
param.userParam.userMapSize = size;//*kyf 栈大小
param.uwResved = OS_TASK_FLAG_PTHREAD_JOIN;//*kyf 可结合的(joinable)能够被其他线程收回其资源和杀死
ret = OsUserInitProcessStart(g_userInitProcess, &param);//*kyf 创建一个任务,来运行main函数
if (ret != LOS_OK) {
    (VOID)OsUnMMap(processCB->vmSpace, param.userParam.userMapBase, param.userParam.userMapSize);
    goto ERROR;
}

return LOS_OK;

ERROR:

(VOID)LOS_PhysPagesFreeContiguous(userText, initSize >> PAGE_SHIFT);
OsDeInitPCB(processCB);
return ret;

}

鸿蒙现有开源终端设备支持的128KB-128MB

内存

,这三个值需要外部提供,先指定空间大小,内核才能还是管理。

 

    CHAR *userInitTextStart = (CHAR *)&__user_init_entry;//*kfy 代码区开始位置

CHAR *userInitBssStart = (CHAR *)&__user_init_bss;//*kyf 未初始化数据区(BSS)。在运行时改变其值
CHAR *userInitEnd = (CHAR *)&amp;__user_init_end;//*kyf 结束地址</pre>

代码区:应用程序源码码经过编译后,通过加载器加载到这里,第一条指令就是 main()

BSS:英文

全称

叫Block Started by Symbol ,未初始化的全局变量和静态变量的内存空间,并且清0

 

LOS_PhysPagesAllocContiguous: 从物理页分配连续的地址空间。

LOS_VaddrToPaddrMmap:将虚拟内存映射成物理地址。 这两个函数先不展开,放在下篇说,需要了解物理地址页管理部分。

同时通过g_userInitProcess创建了进程的第一个task,回调函数指向了代码区userInitTextStart,也就是像java等上层应用开发的main函数所在位置,等该任务被调度后,CPU的PC寄存器值将会被改成userInitTextStart,程序从这开始执行。明白了吗?

再来,main任务的栈内存是怎么来的?

STATIC VOID *OsUserInitStackAlloc(UINT32 processID, UINT32 *size)
{

LosVmMapRegion *region = NULL;
LosProcessCB *processCB = OS_PCB_FROM_PID(processID);
UINT32 stackSize = ALIGN(OS_USER_TASK_STACK_SIZE, PAGE_SIZE);//*kyf

region = LOS_RegionAlloc(processCB-&gt;vmSpace, 0, stackSize,
                         VM_MAP_REGION_FLAG_PERM_USER | VM_MAP_REGION_FLAG_PERM_READ |
                         VM_MAP_REGION_FLAG_PERM_WRITE, 0);
if (region == NULL) {
    return NULL;
}

LOS_SetRegionTypeAnon(region);
region-&gt;regionFlags |= VM_MAP_REGION_FLAG_STACK;

*size = stackSize;

return (VOID *)(UINTPTR)region-&gt;range.base;

}

struct VmMapRegion {

LosRbNode           rbNode;         /**&lt; region red-black tree node */
LosVmSpace          *space;
LOS_DL_LIST         node;           /**&lt; region dl list */
LosVmMapRange       range;          /**&lt; region address range */
VM_OFFSET_T         pgOff;          /**&lt; region page offset to file */
UINT32              regionFlags;   /**&lt; region flags: cow, user_wired */
UINT32              shmid;          /**&lt; shmid about shared region */
UINT8               protectFlags;   /**&lt; vm region protect flags: PROT_READ, PROT_WRITE, */
UINT8               forkFlags;      /**&lt; vm space fork flags: COPY, ZERO, */
UINT8               regionType;     /**&lt; vm region type: ANON, FILE, DEV */
union {
    struct VmRegionFile {
        unsigned int fileMagic;
        struct file *file;
        const LosVmFileOps *vmFOps;
    } rf;
    struct VmRegionAnon {
        LOS_DL_LIST  node;          /**&lt; region LosVmPage list */
    } ra;
    struct VmRegionDev {
        LOS_DL_LIST  node;          /**&lt; region LosVmPage list */
        const LosVmFileOps *vmFOps;
    } rd;
} unTypeData;

};

VmMapRegion(线性区描述符),该结构体描述了 protectFlags(权限),LosVmMapRange(范围),线性区的类型(regionType)。映射类型(unTypeData):按文件映射,匿名映射,特殊设备映射,这是个联合体,具体下篇再展开讲。

一些概念

一、逻辑地址(有时也称虚拟地址)
  逻辑地址(Logical Address) 是指由程序产生的与段相关的偏移地址部分。例如在C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于当前进程数据段的地址,和绝对物理地址无关。
  只有在Intel处理器的实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,CPU不进行自动地址转换)。逻辑地址也就是在Intel 处理器的保护模式下,程序执行代码段限长内的偏移地址(假定代码段、数据段完全一样)。
  CPU启动保护模式后,程序运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。
  应用程序仅需与逻辑地址打交道,而分段和分页机制仅系统编程涉及,应用程序虽然可以直接操作内存,但是也只能在操作系统分配的内存段中操作。

二、线性地址
  线性地址(Linear Address)是逻辑地址到物理地址转换的中间层。程序代码经编译后会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。
  若启用了分页机制,则线性地址会再此转换产生一个物理地址。若没有启用分页机制,则线性地址就是物理地址。
三、物理地址
  物理地址(Physical Address)是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终地址。若启用了分页机制,则线性地址会使用页目录和页表中的项转换为物理地址。若没有启用分页机制,则线性地址直接就是物理地址。
四、虚拟内存
  虚拟内存(Virtual Memory)是指计算机呈现出比实际拥有的内存大得多的内存量。因此它允许程序员编写并运行比实际系统拥有的内存大得多的程序。这使得许多大型项目也能够在具有有限内存资源的系统上实现。

转发+关注,私信我就可以获取获取更多java架构资料、笔记、源码