动态内存管理,即在内存资源充足的情况下,从系统配置的一块比较大的连续内存根据用户需求,分配任意大小的内存块,当用户不需要该内存块时,又可以释放回系统供下一次使用。静态内存实质上是一块静态数组,静态内存池内的块大小需要用户在初始化时设定,初始化后块大小不可变更。与静态内存管理相比,动态内存管理的好处是按需分配,缺点是内存池中容易出现碎片。
Liteos静态内存管理同其他OS一样,实际上操作的是一个静态数组,这个数组就是一个内存池,里面包含有一个控制块和若干个相同大小的内存块。内存的申请,清除,释放都是以内存块为单位。控制块位于内存池的头部,用于内存块的管理,结构体如下:
typedef struct
{
UINT32 uwBlkSize; /* 内存块大小 */
UINT32 uwBlkNum; /* 可用内存块总个数 */
UINT32 uwBlkCnt; /* 已经被申请的内存块个数 */
LOS_MEMBOX_NODE stFreeList; /* 空闲内存块链表 */
} LOS_MEMBOX_INFO;
Liteos静态内存管理主要用到的函数:
LITE_OS_SEC_TEXT_INIT UINT32 LOS_MemboxInit(VOID *pBoxMem, UINT32 uwBoxSize, UINT32 uwBlkSize) /* 内存池初始化 */
LITE_OS_SEC_TEXT VOID *LOS_MemboxAlloc(VOID *pBoxMem) /* 内存块申请 */
LITE_OS_SEC_TEXT UINT32 LOS_MemboxFree(VOID *pBoxMem, VOID *pBox) /* 内存块释放 */
LITE_OS_SEC_TEXT_MINOR VOID LOS_MemboxClr(VOID *pBoxMem, VOID *pBox) /* 内存块清除 */
内存池初始化:
#define MemBoxSize 160 //内存池大小
#define MemBlkSize 12 //内存块大小
static UINT8 MemBox[MemBoxSize]; //内存池
UINT32 uwRet = LOS_OK;
uwRet = LOS_MemboxInit(MemBox,MemBoxSize,MemBlkSize);
if(uwRet != LOS_OK)
{
PRINT_ERR("Membox Error Code:0x%X\n",uwRet);
return uwRet;
}
内存池初始化成功之后,控制块的内容和内存块空闲链表就会更新。需要注意的是内存池的起始地址必须是4字节对齐,在初始化之前会进行检查,对齐宏定义是OS_BOXMEM_BASE_ALIGN。初始化后内存池如下:
控制块共占用16个字节的大小,除空闲链表地址字段外,其余字段通过计算得出。
uwBlkSize :每个内存块实际上占用的空间为用户定义的内存块的大小加上4字节(LOS_MEMBOX_MAGIC_SIZE),这4个字节用来保存下一个空闲内存块地址或者标记控制块是否被使用。(MemBlkSize+LOS_MEMBOX_MAGIC_SIZE)
uwBlkNum :可用内存块的总个数计算时需要用内存池的大小减去控制块大小之后除以单个内存块的大小。为了避免浪费资源,可以通过计算来合理设置内存池大小。((MemBoxSize-16)/uwBlkSize )
uwBlkCnt :初始化后已经申请的内存块个数为0。
stFreeList:下一个空闲内存块地址。除控制块外,每个内存块前4个字节用来保存下一个空闲内存块地址,最后一个内存块中为NULL。
pstBoxInfo->uwBlkSize = LOS_MEMBOX_ALIGNED(uwBlkSize + LOS_MEMBOX_MAGIC_SIZE);
pstBoxInfo->uwBlkNum = (uwBoxSize - sizeof(LOS_MEMBOX_INFO)) / pstBoxInfo->uwBlkSize;
pstBoxInfo->uwBlkCnt = 0;
连续申请内存并写入数据
UINT32 *pAddr = NULL;
UINT32 count = 1;
pAddr = (UINT32 *)LOS_MemboxAlloc(MemBox);
if(pAddr == NULL)
{
PRINT_ERR("No Memory\r\n");
}
else
{
PRINT_INFO("Memory Alloc Ok:0x%X\r\n",(UINT32)pAddr);
sprintf((char *)pAddr,"hello:%d",(count++));
PRINT_INFO("Memory Write Data:%s\r\n",(char *)pAddr);
}
申请到的内存地址实际上是每个内存块的起始地址向后偏移4个字节,内存块被申请后,前4个字节会被标记为已使用0xa55a5aa5。每申请一个内存块,控制块中的uwBlkCnt字段会加1,同时stFreeList字段会指向下一个空闲内存块。
异常
在使用内存块的时候需要注意写入数据的长度,不能超过初始化时定义的每个内存块的大小MemBlkSize,因为内存池是连续的,如果写入数据过长,会导致覆盖掉下一个内存块的节点信息,导致系统异常。
例如写入如下数据,长度超过MemBlkSize,导致下一个内存块的节点信息被覆盖,系统进入异常。由于使用的接管中断方式,因此进入异常处理函数osHwiDefaultHandler,中断号为3,即Hard Fault Handler。
sprintf((char *)pAddr,"hello world:%d",(count++));
清除内存块
LOS_MemboxClr(MemBox,pAddr);
清除内存块会把已经被申请的内存块除标记字段外清零,函数无返回值。
释放内存块
uwRet = LOS_MemboxFree(MemBox,pAddr);
if(uwRet != LOS_OK)
{
PRINT_ERR("Memory Free Fail\r\n");
}
释放内存块后,当前释放内存块的前4个字节会取消标记并指向释放前控制块中指向的空闲内存块。控制块中的uwBlkCnt字段会减1,stFreeList字段会指向当前释放的内存块。