存储管理简介
通常,应用程序可以调用ANSI C编译器的malloc()函数和free()函数来动态地分配和释放存储空间。然而,在嵌入式实时系统中,这样做可能很危险,多次进行这样地操作会把原来很大的一块连续存储区域,逐渐地分割为许多非常小且彼此不相连的存储区域,这就是存储碎片。由于存储碎片数量的增加,程序到后来可能连一块连续的存储区域都分配不到。另外,由于存储空间管理算法的原因,要得到一块连续的存储块,其大小足以满足malloc()函数的要求,malloc()和free()函数的执行时间也没有办法确定。
UCOSIII中提供了一种替代malloc()和free()函数的方法,它提供了自己的动态内存方案。UCOIII将存储空间分成区和块,一个存储区有数个固定大小的块组成,如下图所示:
实际使用中我们可以根据应用程序对内存需求的不同建立多个存储区,每个存储区中有不同大小、不同数量的存储块,应用程序可以根据所需内存不同从不同的存储区中申请内存使用,使用完以后在释放到相应的存储区中。
存储控制块
UCOSIII中用存储控制块来表示存储区,存储控制块为OS_MEM。
struct os_mem { /* MEMORY CONTROL BLOCK */
OS_OBJ_TYPE Type; /* 类型 */
void *AddrPtr; /* 指向存储区起始地址 */
CPU_CHAR *NamePtr;
void *FreeListPtr; /* 指向空闲存储块 */
OS_MEM_SIZE BlkSize; /* 存储区中存储块大小,单位:字节 */
OS_MEM_QTY NbrMax; /* 存储区中总的存储块数 */
OS_MEM_QTY NbrFree; /* 存储区中空闲存储块数 */
#if OS_CFG_DBG_EN > 0u
OS_MEM *DbgPrevPtr;
OS_MEM *DbgNextPtr;
#endif
};
存储区如图所示:
存储管理相关API函数
如何创建存储区
在UCOSIII中,通常调用OSMemCreate()函数来创建一个存储分区:
void OSMemCreate (OS_MEM *p_mem, //指向存储区控制块地址
CPU_CHAR *p_name, //指向存储区的名字
void *p_addr, //存储区所有存储空间基地址
OS_MEM_QTY n_blks, //存储区中存储块个数
OS_MEM_SIZE blk_size, //存储块大小
OS_ERR *p_err)
{
OS_MEM_QTY i;
OS_MEM_QTY loops;
CPU_INT08U *p_blk;
void **p_link;
CPU_SR_ALLOC();
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* 中断中是不能调用函数OSMemCreate()来创建存储区的 */
*p_err = OS_ERR_MEM_CREATE_ISR;
return;
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u
if (p_addr == (void *)0) { /* 存储区空间基地址不能为0 */
*p_err = OS_ERR_MEM_INVALID_P_ADDR;
return;
}
if (n_blks < (OS_MEM_QTY)2) { /* 存储区中存储块数量n_blks最少为2 */
*p_err = OS_ERR_MEM_INVALID_BLKS;
return;
}
if (blk_size < sizeof(void *)) { /* 每个存储块大小不小于一个指针大小,一个指针大小为4字节,因此每个存储块大小要大于4字节 */
*p_err = OS_ERR_MEM_INVALID_SIZE;
return;
}
align_msk = sizeof(void *) - 1u;
if (align_msk > 0) {
if (((CPU_ADDR)p_addr & align_msk) != 0u){ /* 判断存储区空间基地址是否4字节对齐,存储区的存储空间基地址必须4字节对齐 */
*p_err = OS_ERR_MEM_INVALID_P_ADDR;
return;
}
if ((blk_size & align_msk) != 0u) { /* 存储区中每个存储块的大小要是4的倍数 */
*p_err = OS_ERR_MEM_INVALID_SIZE;
return;
}
}
#endif
p_link = (void **)p_addr; /* Create linked list of free memory blocks */
p_blk = (CPU_INT08U *)p_addr;
loops = n_blks - 1u;
for (i = 0u; i < loops; i++) {
p_blk += blk_size;
*p_link = (void *)p_blk; /* Save pointer to NEXT block in CURRENT block */
p_link = (void **)(void *)p_blk; /* Position to NEXT block */
}
*p_link = (void *)0; /* Last memory block points to NULL */
OS_CRITICAL_ENTER();
p_mem->Type = OS_OBJ_TYPE_MEM; /* Set the type of object */
p_mem->NamePtr = p_name; /* Save name of memory partition */
p_mem->AddrPtr = p_addr; /* Store start address of memory partition */
p_mem->FreeListPtr = p_addr; /* Initialize pointer to pool of free blocks */
p_mem->NbrFree = n_blks; /* Store number of free blocks in MCB */
p_mem->NbrMax = n_blks;
p_mem->BlkSize = blk_size; /* Store block size of each memory blocks */
#if OS_CFG_DBG_EN > 0u
OS_MemDbgListAdd(p_mem);
#endif
OSMemQty++;
OS_CRITICAL_EXIT_NO_SCHED();
*p_err = OS_ERR_NONE;
}
内存申请
使用函数OSMemGet()来获取存储块,函数原型如下:
void *OSMemGet (OS_MEM *p_mem, //要使用的存储区
OS_ERR *p_err)
{
void *p_blk;
CPU_SR_ALLOC();
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return ((void *)0);
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u
if (p_mem == (OS_MEM *)0) { /* 判断存储区p_mem是否存在 */
*p_err = OS_ERR_MEM_INVALID_P_MEM;
return ((void *)0);
}
#endif
CPU_CRITICAL_ENTER();
if (p_mem->NbrFree == (OS_MEM_QTY)0) { /* 判断存储区中是否还有空闲的存储块 */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_MEM_NO_FREE_BLKS; /* No, Notify caller of empty memory partition */
return ((void *)0); /* Return NULL pointer to caller */
}
p_blk = p_mem->FreeListPtr; /* 取出空闲存储块链表中第一个存储块 */
p_mem->FreeListPtr = *(void **)p_blk; /* 空闲存储块链表头FreeListPtr就需要更新 */
p_mem->NbrFree--; /* One less memory block in this partition */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE; /* No error */
return (p_blk); /* Return memory block to caller */
}
从上面的程序中我们可以看出UCOSIII自带的内存管理函数的局限性,每次申请内存的时候用户要先估计所申请的内存是否会超过存储区中存储块的大小。
通过上面的分析可以看出UCOSIII的内存管理很粗糙,不灵活,并不能申请指定大小的内存块。
内存释放
在UCOSIII中内存的释放可以使用函数OSMemPut()来完成,函数原型如下:
void OSMemPut (OS_MEM *p_mem, //指向存储区控制块
void *p_blk, //指向存储块
OS_ERR *p_err)
{
CPU_SR_ALLOC();
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u
if (p_mem == (OS_MEM *)0) { /* 检查存储区是否存在 */
*p_err = OS_ERR_MEM_INVALID_P_MEM;
return;
}
if (p_blk == (void *)0) { /* 检查存储块是否存在 */
*p_err = OS_ERR_MEM_INVALID_P_BLK;
return;
}
#endif
CPU_CRITICAL_ENTER();
if (p_mem->NbrFree >= p_mem->NbrMax) { /* 检查存储区空闲存储块数量是否大于存储区总存储块数量 */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_MEM_FULL;
return;
}
*(void **)p_blk = p_mem->FreeListPtr; /* 将要归还的存储块添加到空闲存储块链表中,这里是添加到表头的位置 */
p_mem->FreeListPtr = p_blk;
p_mem->NbrFree++; /* One more memory block in this partition */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE; /* Notify caller that memory block was released */
}
小结
不要在嵌入式系统中使用malloc()函数和free()函数,因为他们会引起存储空间碎片,但是,只分配存储空间而不再释放时,可以使用malloc()函数。
用户可以创建任意多个存储分区(仅受限于ARM空间容量)。
ISR可以调用OSMemGet()函数和OSMemPut()函数。