# 物理内存管理
- [基本概念](#section210891719217)
- [运行机制](#section111355315213)
- [开发指导](#section393116496217)
- [接口说明](#section13210155619214)
- [开发流程](#section178441091231)
- [编程实例](#section1258174015319)
- [结果验证](#section515091342819)
## 基本概念
物理内存是计算机上最重要的资源之一,指的是实际的内存设备提供的、可以通过CPU总线直接进行寻址的内存空间,其主要作用是为操作系统及程序提供临时存储空间。LiteOS-A内核管理物理内存是通过分页实现的,除了内核堆占用的一部分内存外,其余可用内存均以4KiB为单位划分成页帧,内存分配和内存回收便是以页帧为单位进行操作。内核采用伙伴算法管理空闲页面,可以降低一定的内存碎片率,提高内存分配和释放的效率,但是一个很小的块往往也会阻塞一个大块的合并,导致不能分配较大的内存块。
## 运行机制
如下图所示,LiteOS-A内核的物理内存使用分布视图,主要由内核镜像、内核堆及物理页组成。内核堆部分见堆内存管理一节。
**图 1** 物理内存使用分布图
![](figure/物理内存使用分布图.png "物理内存使用分布图")
伙伴算法把所有空闲页帧分成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** 内存申请示意图
![](figure/内存申请示意图.png "内存申请示意图")
- 释放内存
系统释放12KiB内存,即3个页帧,将3个页帧分成2的幂次方之和,即2跟1,尝试查找伙伴进行合并,索引为1的链表上有1个内存块,若地址连续则合并,并将合并后的内存块挂到索引为2的链表上,索引为0的链表上此时也有1个,如果地址连续则进行合并,并将合并后的内存块挂到索引为1的链表上,此时继续判断是否有伙伴,重复上述操作。
**图 3** 内存释放示意图
![](figure/内存释放示意图.png "内存释放示意图")
## 开发指导
### 接口说明
**表 1** 物理内存管理模块接口
功能分类
|
接口名称
|
描述
|
申请物理内存
|
LOS_PhysPageAlloc
|
申请一个物理页
|
LOS_PhysPagesAlloc
|
申请物理页并挂在对应的链表上
|
LOS_PhysPagesAllocContiguous
|
申请多页地址连续的物理内存
|
释放物理内存
|
LOS_PhysPageFree
|
释放一个物理页
|
LOS_PhysPagesFree
|
释放挂在链表上的物理页
|
LOS_PhysPagesFreeContiguous
|
释放多页地址连续的物理内存
|
查询地址
|
LOS_VmPageGet
|
根据物理地址获取其对应的物理页结构体指针
|
LOS_PaddrToKVaddr
|
根据物理地址获取其对应的内核虚拟地址
|
### 开发流程
内存申请时根据需要调用相关接口,小内存申请建议使用堆内存申请相关接口,4KiB及以上内存申请可以使用上述物理内存相关接口。
>![](../public_sys-resources/icon-note.gif) **说明:**
>- 物理内存申请相关接口需要在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
```