OpenHarmony设备开发 小型系统内核(LiteOS-A) 内存管理
版本:V3.2Beta
堆内存管理
基本概念
内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。OpenHarmony LiteOS-A的堆内存管理提供内存初始化、分配、释放等功能。在系统运行过程中,堆内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。
运行机制
堆内存管理,即在内存资源充足的情况下,根据用户需求,从系统配置的一块比较大的连续内存(内存池,也是堆内存)中分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。与静态内存相比,动态内存管理的优点是按需分配,缺点是内存池中容易出现碎片。OpenHarmony LiteOS-A堆内存在TLSF算法的基础上,对区间的划分进行了优化,获得更优的性能,降低了碎片率。动态内存核心算法框图如下:
图1 小型系统动态内存核心算法
根据空闲内存块的大小,使用多个空闲链表来管理。根据内存空闲块大小分为两个部分:[4, 127]和[27, 231],如上图size class所示:
- 对[4,127]区间的内存进行等分,如上图下半部分所示,分为31个小区间,每个小区间对应内存块大小为4字节的倍数。每个小区间对应一个空闲内存链表和用于标记对应空闲内存链表是否为空的一个比特位,值为1时,空闲链表非空。[4,127]区间的31个小区间内存对应31个比特位进行标记链表是否为空。
- 大于127字节的空闲内存块,按照2的次幂区间大小进行空闲链表管理。总共分为24个小区间,每个小区间又等分为8个二级小区间,见上图上半部分的Size Class和Size SubClass部分。每个二级小区间对应一个空闲链表和用于标记对应空闲内存链表是否为空的一个比特位。总共24*8=192个二级小区间,对应192个空闲链表和192个比特位进行标记链表是否为空。
例如,当有40字节的空闲内存需要插入空闲链表时,对应小区间[40,43],第10个空闲链表,位图标记的第10比特位。把40字节的空闲内存挂载第10个空闲链表上,并判断是否需要更新位图标记。当需要申请40字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。当有580字节的空闲内存需要插入空闲链表时,对应二级小区间[2^9,2^9+2^6],第31+2*8=47个空闲链表,并使用位图的第47个比特位来标记链表是否为空。把580字节的空闲内存挂载第47个空闲链表上,并判断是否需要更新位图标记。当需要申请580字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。如果对应的空闲链表为空,则向更大的内存区间去查询是否有满足条件的空闲链表,实际计算时,会一次性查找到满足申请大小的空闲链表。
内存管理结构如下图所示:
图2 小型系统动态内存管理结构图
- 内存池池头部分 内存池池头部分包含内存池信息、位图标记数组和空闲链表数组。内存池信息包含内存池起始地址及堆区域总大小,内存池属性。位图标记数组有7个32位无符号整数组成,每个比特位标记对应的空闲链表是否挂载空闲内存块节点。空闲内存链表包含223个空闲内存头节点信息,每个空闲内存头节点信息维护内存节点头和空闲链表中的前驱、后继空闲内存节点。
- 内存池节点部分 包含3种类型节点:未使用空闲内存节点,已使用内存节点和尾节点。每个内存节点维护一个前序指针,指向内存池中上一个内存节点,还维护内存节点的大小和使用标记。空闲内存节点和已使用内存节点后面的内存区域是数据域,尾节点没有数据域。
开发指导
使用场景
堆内存管理的主要工作是动态分配并管理用户申请到的内存区间,主要用于用户需要使用大小不等的内存块的场景,当用户需要使用内存时,可以通过操作系统的动态内存申请函数索取指定大小的内存块。一旦使用完毕,通过内存释放函数释放所占用内存,使之可以重复使用。
接口说明
OpenHarmony LiteOS-A的堆内存管理主要为用户提供以下功能,接口详细信息可以查看API参考。
表1 堆内存管理接口
功能分类 | 接口描述 |
初始化和删除内存池 | - LOS_MemInit:初始化一块指定的动态内存池,大小为size - LOS_MemDeInit:删除指定内存池,仅打开LOSCFG_MEM_MUL_POOL时有效 |
申请、释放动态内存 | - LOS_MemAlloc:从指定动态内存池中申请size长度的内存 - LOS_MemFree:释放从指定动态内存中申请的内存 - LOS_MemRealloc: - 按size大小重新分配内存块,并将原内存块内容拷贝到新内存块。如果新内存块申请成功,则释放原内存块 - LOS_MemAllocAlign:从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存 |
获取内存池信息 | - LOS_MemPoolSizeGet:获取指定动态内存池的总大小 - LOS_MemTotalUsedGet:获取指定动态内存池的总使用量大小 - LOS_MemInfoGet:获取指定内存池的内存结构信息,包括空闲内存大小、已使用内存大小、空闲内存块数量、已使用的内存块数量、最大的空闲内存块大小 - LOS_MemPoolList:打印系统中已初始化的所有内存池,包括内存池的起始地址、内存池大小、空闲内存总大小、已使用内存总大小、最大的空闲内存块大小、空闲内存块数量、已使用的内存块数量。仅打开LOSCFG_MEM_MUL_POOL时有效 |
获取内存块信息 | LOS_MemFreeNodeShow:打印指定内存池的空闲内存块的大小及数量 |
检查指定内存池的完整性 | LOS_MemIntegrityCheck:对指定内存池做完整性检查,仅打开LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK时有效 |
说明:
- 由于动态内存管理需要管理控制块数据结构来管理内存,这些数据结构会额外消耗内存,故实际用户可使用内存总量小于配置项OS_SYS_MEM_SIZE的大小。
- 对齐分配内存接口LOS_MemAllocAlign/LOS_MemMallocAlign因为要进行地址对齐,可能会额外消耗部分内存,故存在一些遗失内存,当系统释放该对齐内存时,同时回收由于对齐导致的遗失内存。
开发流程
本节介绍使用动态内存的典型场景开发流程。
- 初始化LOS_MemInit。 初始一个内存池后生成一个内存池控制头、尾节点EndNode,剩余的内存被标记为FreeNode内存节点。注:EndNode作为内存池末尾的节点,size为0。
- 申请任意大小的动态内存LOS_MemAlloc。 判断动态内存池中是否存在大于申请量大小的空闲内存块空间,若存在,则划出一块内存块,以指针形式返回,若不存在,返回NULL。如果空闲内存块大于申请量,需要对内存块进行分割,剩余的部分作为空闲内存块挂载到空闲内存链表上。
- 释放动态内存LOS_MemFree。 回收内存块,供下一次使用。调用LOS_MemFree释放内存块,则会回收内存块,并且将其标记为FreeNode。在回收内存块时,相邻的FreeNode会自动合并。
编程实例
本实例执行以下步骤:
- 初始化一个动态内存池。
- 从动态内存池中申请一个内存块。
- 在内存块中存放一个数据。
- 打印出内存块中的数据。
- 释放该内存块。
示例代码如下:
#include "los_memory.h"
#define TEST_POOL_SIZE (2*1024*1024)
__attribute__((aligned(4))) UINT8 g_testPool[TEST_POOL_SIZE];
VOID Example_DynMem(VOID)
{
UINT32 *mem = NULL;
UINT32 ret;
/*初始化内存池*/
ret = LOS_MemInit(g_testPool, TEST_POOL_SIZE);
if (LOS_OK == ret) {
printf("Mem init success!\n");
} else {
printf("Mem init failed!\n");
return;
}
/*分配内存*/
mem = (UINT32 *)LOS_MemAlloc(g_testPool, 4);
if (NULL == mem) {
printf("Mem alloc failed!\n");
return;
}
printf("Mem alloc success!\n");
/*赋值*/
*mem = 828;
printf("*mem = %d\n", *mem);
/*释放内存*/
ret = LOS_MemFree(g_testPool, mem);
if (LOS_OK == ret) {
printf("Mem free success!\n");
} else {
printf("Mem free failed!\n");
}
return;
}
UINT32 ExampleDynMemEntry(VOID)
{
UINT32 ret;
TSK_INIT_PARAM_S initParam = {0};
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_DynMem;
initParam.usTaskPrio = 10;
initParam.pcName = "Example_DynMem";
initParam.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
initParam.uwResved = LOS_TASK_STATUS_DETACHED;
/* 创建高优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */
ret = LOS_TaskCreate(&g_taskHiID, &initParam);
if (ret != LOS_OK) {
LOS_TaskUnlock();
PRINTK("Example_DynMem create Failed! ret=%d\n", ret);
return LOS_NOK;
}
PRINTK("Example_DynMem create Success!\n");
while(1){};
return LOS_OK;
}
结果验证
输出结果如下:
Mem init success!
Mem alloc success!
*mem = 828
Mem free success!
物理内存管理
基本概念
物理内存是计算机上最重要的资源之一,指的是实际的内存设备提供的、可以通过CPU总线直接进行寻址的内存空间,其主要作用是为操作系统及程序提供临时存储空间。LiteOS-A内核管理物理内存是通过分页实现的,除了内核堆占用的一部分内存外,其余可用内存均以4KiB为单位划分成页帧,内存分配和内存回收便是以页帧为单位进行操作。内核采用伙伴算法管理空闲页面,可以降低一定的内存碎片率,提高内存分配和释放的效率,但是一个很小的块往往也会阻塞一个大块的合并,导致不能分配较大的内存块。
运行机制
如下图所示,LiteOS-A内核的物理内存使用分布视图,主要由内核镜像、内核堆及物理页组成。内核堆部分见堆内存管理一节。
图1 物理内存使用分布图
伙伴算法把所有空闲页帧分成9个内存块组,每组中内存块包含2的幂次方个页帧,例如:第0组的内存块包含2的0次方个页帧,即1个页帧;第8组的内存块包含2的8次方个页帧,即256个页帧。相同大小的内存块挂在同一个链表上进行管理。
- 申请内存 系统申请12KiB内存,即3个页帧时,9个内存块组中索引为3的链表挂着一块大小为8个页帧的内存块满足要求,分配出12KiB内存后还剩余20KiB内存,即5个页帧,将5个页帧分成2的幂次方之和,即4跟1,尝试查找伙伴进行合并。4个页帧的内存块没有伙伴则直接插到索引为2的链表上,继续查找1个页帧的内存块是否有伙伴,索引为0的链表上此时有1个,如果两个内存块地址连续则进行合并,并将内存块挂到索引为1的链表上,否则不做处理。图2内存申请示意图
- 释放内存 系统释放12KiB内存,即3个页帧,将3个页帧分成2的幂次方之和,即2跟1,尝试查找伙伴进行合并,索引为1的链表上有1个内存块,若地址连续则合并,并将合并后的内存块挂到索引为2的链表上,索引为0的链表上此时也有1个,如果地址连续则进行合并,并将合并后的内存块挂到索引为1的链表上,此时继续判断是否有伙伴,重复上述操作。图3内存释放示意图
开发指导
接口说明
表1 物理内存管理模块接口
功能分类 | 接口描述 |
申请物理内存 | - LOS_PhysPageAlloc:申请一个物理页 - LOS_PhysPagesAlloc:申请物理页并挂在对应的链表上 - LOS_PhysPagesAllocContiguous:申请多页地址连续的物理内存 |
释放物理内存 | - LOS_PhysPageFree:释放一个物理页 - LOS_PhysPagesFree:释放挂在链表上的物理页 - LOS_PhysPagesFreeContiguous:释放多页地址连续的物理内存 |
查询地址 | - LOS_VmPageGet:根据物理地址获取其对应的物理页结构体指针 - LOS_PaddrToKVaddr:根据物理地址获取其对应的内核虚拟地址 |
开发流程
内存申请时根据需要调用相关接口,小内存申请建议使用堆内存申请相关接口,4KiB及以上内存申请可以使用上述物理内存相关接口。
说明:
- 物理内存申请相关接口需要在OsSysMemInit接口完成初始化之后再使用;
- 内存申请的基本单位是页帧,即4KiB;
- 物理内存申请时,有地址连续要求的使用LOS_PhysPagesAllocContiguous接口,无地址连续的要求尽量使用LOS_PhysPagesAlloc接口,将连续的大块内存留给有需要的模块使用。
编程实例
编程示例主要是调用申请、释放接口对内存进行操作,包括申请一个页以及多个页的示例。
#include "los_vm_phys.h"
#define PHYS_PAGE_SIZE 0x4000
// 申请一个页
VOID OsPhysPagesAllocTest3(VOID)
{
PADDR_T newPaddr;
VOID *kvaddr = NULL;
LosVmPage *newPage = NULL;
newPage = LOS_PhysPageAlloc();
if (newPage == NULL) {
printf("LOS_PhysPageAlloc fail\n");
return;
}
printf("LOS_PhysPageAlloc success\n");
newPaddr = VM_PAGE_TO_PHYS(newPage);
kvaddr = OsVmPageToVaddr(newPage);
// Handle the physical memory
// Free the physical memory
LOS_PhysPageFree(newPage);
}
// 申请多个页,不要求连续
VOID OsPhysPagesAllocTest2(VOID)
{
UINT32 sizeCount;
UINT32 count;
UINT32 size = PHYS_PAGE_SIZE;
LosVmPage *vmPageArray[PHYS_PAGE_SIZE >> PAGE_SHIFT] = { NULL };
UINT32 i = 0;
LosVmPage *vmPage = NULL;
PADDR_T pa;
size = LOS_Align(size, PAGE_SIZE);
if (size == 0) {
return;
}
sizeCount = size >> PAGE_SHIFT;
LOS_DL_LIST_HEAD(pageList);
count = LOS_PhysPagesAlloc(sizeCount, &pageList);
if (count < sizeCount) {
printf("failed to allocate enough pages (ask %zu, got %zu)\n", sizeCount, count);
goto ERROR;
}
printf("LOS_PhysPagesAlloc success\n");
while ((vmPage = LOS_ListRemoveHeadType(&pageList, LosVmPage, node))) {
pa = vmPage->physAddr;
vmPageArray[i++] = vmPage;
// Handle the physical memory
}
// Free the physical memory
for (i = 0; i < sizeCount; ++i) {
LOS_PhysPageFree(vmPageArray[i]);
}
return;
ERROR:
(VOID)LOS_PhysPagesFree(&pageList);
}
// 申请多个连续页
VOID OsPhysPagesAllocTest1(VOID)
{
VOID *ptr = NULL;
LosVmPage *page = NULL;
UINT32 size = PHYS_PAGE_SIZE;
ptr = LOS_PhysPagesAllocContiguous(ROUNDUP(size, PAGE_SIZE) >> PAGE_SHIFT);
if (ptr == NULL) {
printf("LOS_PhysPagesAllocContiguous fail\n");
return;
}
printf("LOS_PhysPagesAllocContiguous success\n");
// Handle the physical memory
// Free the physical memory
page = OsVmVaddrToPage((VOID *)ptr);
LOS_PhysPagesFreeContiguous((VOID *)ptr, size >> PAGE_SHIFT);
}
UINT32 ExamplePhyMemCaseEntry(VOID)
{
OsPhysPagesAllocTest1();
OsPhysPagesAllocTest2();
OsPhysPagesAllocTest3();
return LOS_OK;
}
结果验证
编译运行得到的结果为:
LOS_PhysPagesAllocContiguous success
LOS_PhysPagesAlloc success
LOS_PhysPageAlloc success
虚拟内存管理
基本概念
虚拟内存管理是计算机系统管理内存的一种技术。每个进程都有连续的虚拟地址空间,虚拟地址空间的大小由CPU的位数决定,32位的硬件平台可以提供的最大的寻址空间为0-4GiB。整个4GiB空间分成两部分,LiteOS-A内核占据3GiB的高地址空间,1GiB的低地址空间留给用户态进程使用。各个进程空间的虚拟地址空间是独立的,代码、数据互不影响。
系统将虚拟内存分割为称为虚拟页的内存块,大小一般为4KiB或64KiB,LiteOS-A内核默认的页的大小是4KiB,根据需要可以对MMU(Memory Management Units)进行配置。虚拟内存管理操作的最小单位就是一个页,LiteOS-A内核中一个虚拟地址区间region包含地址连续的多个虚拟页,也可只有一个页。同样,物理内存也会按照页大小进行分割,分割后的每个内存块称为页帧。虚拟地址空间划分:内核态占高地址3GiB(0x40000000 ~ 0xFFFFFFFF),用户态占低地址1GiB(0x01000000 ~ 0x3F000000),具体见下表,详细可以查看或配置los_vm_zone.h。
表1 内核态地址规划:
Zone名称 | 描述 | 属性 |
DMA zone | 供IO设备的DMA使用。 | Uncache |
Normal zone | 加载内核代码段、数据段、堆和栈的地址区间。 | Cache |
high mem zone | 可以分配连续的虚拟内存,但其所映射的物理内存不一定连续。 | Cache |
表2 用户态虚地址规划:
Zone名称 | 描述 | 属性 |
代码段 | 用户态代码段地址区间。 | Cache |
堆 | 用户态堆地址区间。 | Cache |
栈 | 用户态栈地址区间。 | Cache |
共享库 | 用于加载用户态共享库的地址区间,包括mmap所映射的区间。 | Cache |
运行机制
虚拟内存管理中,虚拟地址空间是连续的,但是其映射的物理内存并不一定是连续的,如下图所示。可执行程序加载运行,CPU访问虚拟地址空间的代码或数据时存在两种情况:
- CPU访问的虚拟地址所在的页,如V0,已经与具体的物理页P0做映射,CPU通过找到进程对应的页表条目(详见虚实映射),根据页表条目中的物理地址信息访问物理内存中的内容并返回。
- CPU访问的虚拟地址所在的页,如V2,没有与具体的物理页做映射,系统会触发缺页异常,系统申请一个物理页,并把相应的信息拷贝到物理页中,并且把物理页的起始地址更新到页表条目中。此时CPU重新执行访问虚拟内存的指令便能够访问到具体的代码或数据。
图1内存映射示意图
开发指导
接口说明
表3 获取进程空间系列接口
接口名称 | 描述 |
LOS_CurrSpaceGet | 获取当前进程空间结构体指针 |
LOS_SpaceGet | 获取虚拟地址对应的进程空间结构体指针 |
LOS_GetKVmSpace | 获取内核进程空间结构体指针 |
LOS_GetVmallocSpace | 获取vmalloc空间结构体指针 |
LOS_GetVmSpaceList | 获取进程空间链表指针 |
表4 虚拟地址区间region相关的操作
接口名称 | 描述 |
LOS_RegionFind | 在进程空间内查找并返回指定地址对应的虚拟地址区间 |
LOS_RegionRangeFind | 在进程空间内查找并返回指定地址范围对应的虚拟地址区间 |
LOS_IsRegionFileValid | 判断虚拟地址区间region是否与文件关联映射 |
LOS_RegionAlloc | 申请空闲的虚拟地址区间 |
LOS_RegionFree | 释放进程空间内特定的region |
LOS_RegionEndAddr | 获取指定地址区间region的结束地址 |
LOS_RegionSize | 获取region的大小 |
LOS_IsRegionTypeFile | 判断是否为文件内存映射 |
LOS_IsRegionPermUserReadOnly | 判断地址区间是否是用户空间只读属性 |
LOS_IsRegionFlagPrivateOnly | 判断地址区间是否是具有私有属性 |
LOS_SetRegionTypeFile | 设置文件内存映射属性 |
LOS_IsRegionTypeDev | 判断是否为设备内存映射 |
LOS_SetRegionTypeDev | 设置设备内存映射属性 |
LOS_IsRegionTypeAnon | 判断是否为匿名映射 |
LOS_SetRegionTypeAnon | 设置匿名映射属性 |
表5 地址校验
接口名称 | 描述 |
LOS_IsUserAddress | 判断地址是否在用户态空间 |
LOS_IsUserAddressRange | 判断地址区间是否在用户态空间 |
LOS_IsKernelAddress | 判断地址是否在内核空间 |
LOS_IsKernelAddressRange | 判断地址区间是否在内核空间 |
LOS_IsRangeInSpace | 判断地址区间是否在进程空间内 |
表6 vmalloc操作
接口名称 | 描述 |
LOS_VMalloc | vmalloc申请内存 |
LOS_VFree | vmalloc释放内存 |
LOS_IsVmallocAddress | 判断地址是否是通过vmalloc申请的 |
表7 内存申请系列接口
接口名称 | 描述 |
LOS_KernelMalloc | 当申请的内存小于16KiB时,系统从堆内存池分配内存;当申请的内存超过16KiB时,系统分配多个连续物理页用于内存分配 |
LOS_KernelMallocAlign | 申请具有对齐属性的内存,申请规则同LOS_KernelMalloc接口 |
LOS_KernelFree | 释放由LOS_KernelMalloc和LOS_KernelMallocAlign接口申请的内存 |
LOS_KernelRealloc | 重新分配由LOS_KernelMalloc和LOS_KernelMallocAlign接口申请的内存 |
表8 其他
接口名称 | 描述 |
LOS_PaddrQuery | 根据虚拟地址获取对应的物理地址 |
LOS_VmSpaceFree | 释放进程空间,包括虚拟内存区间、页表等信息 |
LOS_VmSpaceReserve | 在进程空间中预留一块内存空间 |
LOS_VaddrToPaddrMmap | 将指定长度的物理地址区间与虚拟地址区间做映射,需提前申请物理地址区间 |
开发流程
虚拟内存相关接口的使用:
- 根据进程空间获取的系列接口可以得到进程空间结构体,进而可以读取结构体相应信息。
- 对虚拟地址区间做相关操作:
- 通过LOS_RegionAlloc申请虚拟地址区间;
- 通过LOS_RegionFind、LOS_RegionRangeFind可以查询是否存在相应的地址区间;
- 通过LOS_RegionFree释放虚拟地址区间。
- vmalloc接口及内存申请系列接口可以在内核中根据需要申请内存。
说明:
内存申请系列接口申请的内存要求物理内存是连续的,当系统内存无法满足大块连续内存的申请条件时会申请失败,一般适用于小块内存的申请;vmalloc相关接口申请的内存可以获得不连续的物理内存,但其是以页(当前系统一个页为4096字节)为单位的,当需要申请以页为整数倍的内存时可以通过vmalloc申请,例如文件系统中文件读取需要较大的缓存,便可以通过vmalloc相关接口申请内存。
虚实映射
基本概念
虚实映射是指系统通过内存管理单元(MMU,Memory Management Unit)将进程空间的虚拟地址与实际的物理地址做映射,并指定相应的访问权限、缓存属性等。程序执行时,CPU访问的是虚拟内存,通过MMU页表条目找到对应的物理内存,并做相应的代码执行或数据读写操作。MMU的映射由页表(Page Table)来描述,其中保存虚拟地址和物理地址的映射关系以及访问权限等。每个进程在创建的时候都会创建一个页表,页表由一个个页表条目(Page Table Entry, PTE)构成,每个页表条目描述虚拟地址区间与物理地址区间的映射关系。MMU中有一块页表缓存,称为快表(TLB, Translation Lookaside Buffers),做地址转换时,MMU首先在TLB中查找,如果找到对应的页表条目可直接进行转换,提高了查询效率。CPU访问内存或外设的示意图如下:
图1 CPU访问内存或外设的示意图
运行机制
虚实映射其实就是一个建立页表的过程。MMU支持多级页表,LiteOS-A内核采用二级页表描述进程空间。每个一级页表条目描述符占用4个字节,可表示1MiB的内存空间的映射关系,即1GiB用户空间(LiteOS-A内核中用户空间占用1GiB)的虚拟内存空间需要1024个。系统创建用户进程时,在内存中申请一块4KiB大小的内存块作为一级页表的存储区域,系统根据当前进程的需要会动态申请内存作为二级页表的存储区域。
- 用户程序加载启动时,会将代码段、数据段映射进虚拟内存空间(详细可参考动态加载与链接),此时并没有物理页做实际的映射;
- 程序执行时,如下图粗箭头所示,CPU访问虚拟地址,通过MMU查找是否有对应的物理内存,若该虚拟地址无对应的物理地址则触发缺页异常,内核申请物理内存并将虚实映射关系及对应的属性配置信息写进页表,并把页表条目缓存至TLB,接着CPU可直接通过转换关系访问实际的物理内存;
- 若CPU访问已缓存至TLB的页表条目,无需再访问保存在内存中的页表,可加快查找速度。
图2CPU访问内存示意图
开发指导
接口说明
表1 MMU相关操作
接口名称 | 描述 |
LOS_ArchMmuQuery | 获取进程空间虚拟地址对应的物理地址以及映射属性。 |
LOS_ArchMmuMap | 映射进程空间虚拟地址区间与物理地址区间。 |
LOS_ArchMmuUnmap | 解除进程空间虚拟地址区间与物理地址区间的映射关系。 |
LOS_ArchMmuChangeProt | 修改进程空间虚拟地址区间的映射属性。 |
LOS_ArchMmuMove | 将进程空间一个虚拟地址区间的映射关系转移至另一块未使用的虚拟地址区间重新做映射。 |
开发流程
虚实映射相关接口的使用:
- 通过LOS_ArchMmuMap映射一块物理内存。
- 对映射的地址区间做相关操作:
- 通过LOS_ArchMmuQuery可以查询相应虚拟地址区间映射的物理地址区间及映射属性;
- 通过LOS_ArchMmuChangeProt修改映射属性;
- 通过LOS_ArchMmuMove做虚拟地址区间的重映射。
- 通过LOS_ArchMmuUnmap解除映射关系。
说明:
上述接口的使用都是基于MMU初始化完成以及相关进程页表的建立,MMU在系统启动阶段已完成初始化,进程创建的时候会建立页表,开发者无需介入操作。