在μC/OS-II中,将大块内存作为一个分区,在系统中有多个分区,每个分区又分为整数个大小相同的内存块,由于大小相同,分配和释放时间相同,应用程序根据需要从不同的分区得到不同的内存块,内存块释放时,放回以前的位置。分区和内存块的关系如下所示:
#define OS_MEM_EN 0 //是否开启内存管理
#define OS_MEM_NAME_SIZE 16 //内存分区名大小
#define OS_MEM_QUERY_EN 1 //是否允许内存分区信息查询
#define OS_MAX_MEM_PART 5 //最多内存分区数
#if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0)
OS_EXT OS_MEM *OSMemFreeList; //指向下一个可用内存分区
OS_EXT OS_MEM OSMemTbl[OS_MAX_MEM_PART];//内存分区控制块数组
#endif
为了跟踪每个内存分区,引入内存控制块OS_MEM进行管理。OSMemAddr为指向该内存分区起始地址的指针,当调用OSMemCreate时初始化,之后不能修改。OSMemFreeList为指向下一个可用内存控制块或下一个空余内存块的指针。OSMemBlkSize在建立该内存分区时建立,表示内存分区中单个内存块的大小。OSMemNBlks表示内存分区中总的内存块数量。OSMemNFree为内存分区中当前空余内存块数量。
为了方便查询内存块信息,引入OS_MEM_DATA结构, 和内存控制块类似,OSAddr表示指向内存分区开始地址,OSFreeList表示指向该分区下一个可用内存块,OSBlkSize表示该分区每个内存块大小,OSNBlks表示该分区总内存块数量,OSNFree表示该分区可用内存块数量,OSNUsed该分区已经使用的内存块数量。
/*
*********************************************************************************************************
* MEMORY PARTITION DATA STRUCTURES
*********************************************************************************************************
*/
#if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0)
typedef struct os_mem { //内存控制块
void *OSMemAddr; //指向内存分区开始地址
void *OSMemFreeList; //指向该分区下一个可用内存分区
INT32U OSMemBlkSize; //该分区单个内存块大小
INT32U OSMemNBlks; //该分区内存块数量
INT32U OSMemNFree; //该分区剩余可用内存块
#if OS_MEM_NAME_SIZE > 1
INT8U OSMemName[OS_MEM_NAME_SIZE]; //分区名
#endif
} OS_MEM;
typedef struct os_mem_data { //内存分区信息结构体
void *OSAddr; //指向内存分区开始地址
void *OSFreeList; //指向该分区下一个可用内存块
INT32U OSBlkSize; //该分区每个内存块大小
INT32U OSNBlks; //该分区总内存块数量
INT32U OSNFree; //该分区可用内存块数量
INT32U OSNUsed; //该分区已经使用的内存块数量
} OS_MEM_DATA;
#endif
/*$PAGE*/
/*
void OS_MemInit (void)
{
#if OS_MAX_MEM_PART == 1//内存分区个数最大值
OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl)); //清空所有内存控制块区域
OSMemFreeList = (OS_MEM *)&OSMemTbl[0];//将OSMemFreeList指向OSMemTbl第一个内存控制块
#if OS_MEM_NAME_SIZE > 1
OSMemFreeList->OSMemName[0] = '?'; //内存分区名初始化为未知名
OSMemFreeList->OSMemName[1] = OS_ASCII_NUL;
#endif
#endif
#if OS_MAX_MEM_PART >= 2//内存分区大小大于2
OS_MEM *pmem;
INT16U i;
OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl)); //清空所有内存控制块区域
pmem = &OSMemTbl[0]; //将OSMemFreeList指向OSMemTbl第一个内存控制块
for (i = 0; i < (OS_MAX_MEM_PART - 1); i++) {
pmem->OSMemFreeList = (void *)&OSMemTbl[i+1]; //将所有内存控制块通过OSMemFreeList指针链接
#if OS_MEM_NAME_SIZE > 1
pmem->OSMemName[0] = '?'; //内存分区名初始化为未知名
pmem->OSMemName[1] = OS_ASCII_NUL;
#endif
pmem++;//指向下一个内存控制块
}
pmem->OSMemFreeList = (void *)0; //最后一个内存控制块的OSMemFreeList为空
#if OS_MEM_NAME_SIZE > 1
pmem->OSMemName[0] = '?'; //内存分区名初始化为未知名
pmem->OSMemName[1] = OS_ASCII_NUL;
#endif
OSMemFreeList = &OSMemTbl[0]; //将OSMemFreeList指向OSMemTbl第一个内存控制块
#endif
}
首先判断最大内存分区数量是否为1,如果是,清空内存控制块,将OSMemFreeList指向OSMemTbl第一个内存控制块,然后分区名是否大于1,大于1的话,初始化分区名为未知名。若分区数量大于等于2,如果是,清空内存控制块,将pmem指向OSMemTbl第一个内存控制块,将所有内存控制块通过OSMemFreeList指针链接,所有分区初始化分区名为未知名,最后一个内存控制块的OSMemFreeList指向空,OSMemFreeList指向OSMemTbl第一个内存控制块。以3个内存控制块初始化为例:
在使用一个内存分区之前,必须建立该内存分区,使用OSMemCreate创建内存分区,函数共四个参数,内存分区的起始地址addr,分区内内存块总数nblks,每个内存块的字节数blksize,指向出错信息的指针perr。
OS_MEM *OSMemCreate (void *addr, INT32U nblks, INT32U blksize, INT8U *perr)
{
OS_MEM *pmem;
INT8U *pblk;
void **plink;
INT32U i;
#if OS_CRITICAL_METHOD == 3 //为CPU状态寄存器分配内存
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (perr == (INT8U *)0) { //传进来的perr为空,不正确,返回空内存控制块地址
return ((OS_MEM *)0);
}
if (addr == (void *)0) { //传进来的addr为空,不正确,返回空内存控制块地址,并且返回错误类型OS_ERR_MEM_INVALID_ADDR,表示无效地址
*perr = OS_ERR_MEM_INVALID_ADDR;
return ((OS_MEM *)0);
}
if (((INT32U)addr & (sizeof(void *) - 1)) != 0){ //地址对齐,为4的倍数
*perr = OS_ERR_MEM_INVALID_ADDR;
return ((OS_MEM *)0);
}
if (nblks < 2) { //分区数至少两个,返回空内存控制块地址,并且返回错误类型OS_ERR_MEM_INVALID_BLKS,表示无效地址
*perr = OS_ERR_MEM_INVALID_BLKS;
return ((OS_MEM *)0);
}
if (blksize < sizeof(void *)) { //必须使单个内存块大小至少能存一个指针,因为同一个分区内存块通过链表串联
*perr = OS_ERR_MEM_INVALID_SIZE;
return ((OS_MEM *)0);
}
#endif
OS_ENTER_CRITICAL();
pmem = OSMemFreeList; //pmem指向空闲内存控制块
if (OSMemFreeList != (OS_MEM *)0) { //指向下一个空闲内存控制块
OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;
}
OS_EXIT_CRITICAL();
if (pmem == (OS_MEM *)0) { //确定是否有一个内存分区
*perr = OS_ERR_MEM_INVALID_PART;
return ((OS_MEM *)0);
}
plink = (void **)addr; //plink为指向指针的指针,将addr进行强制转换
pblk = (INT8U *)((INT32U)addr + blksize); //获取第二个区块的起始地址
for (i = 0; i < (nblks - 1); i++) {
*plink = (void *)pblk; //指向下一个内存块地址
plink = (void **)pblk; //指向指针的指针后移一个内存块
pblk = (INT8U *)((INT32U)pblk + blksize); //获得下一个区块起始地址
}
*plink = (void *)0; //最后一个内存块指向为空
pmem->OSMemAddr = addr; //填充起始地址
pmem->OSMemFreeList = addr; //填充空闲地址
pmem->OSMemNFree = nblks; //填充可用内存块数
pmem->OSMemNBlks = nblks; //填充总内存块数
pmem->OSMemBlkSize = blksize; //填充每个内存块的大小
*perr = OS_ERR_NONE; //返回无错误
return (pmem);
}
首先为CPU状态寄存器分配内存,检查perr是否为空,空的话返回空内存控制块地址,检查addr是否正确,不正确返回空的内存控制块地址,并且返回错误类型OS_ERR_MEM_INVALID_ADDR,即无效地址。检查分区数是否大于两个,如果不是,返回空地址指针,并且返回错误类型OS_ERR_MEM_INVALID_BLKS,即无效的块数量,必须使单个内存块大小至少能存一个指针,因为同一个分区内存块通过链表串联,否则返回空地址指针,并且返回错误类型OS_ERR_MEM_INVALID_SIZE,即无效的块大小。检查完成后,通过OSMemFreeList获取一个空闲块,并将OSMemFreeList指向下一个空闲快,然后检查获得的空闲块是否存在,如果不存在,返回空的内存控制块地址,并且返回错误类型OS_ERR_MEM_INVALID_PART,即无效分区。然后将内存空间中的所有内存块连接成单向链表。plink为指向指针的指针,pblk为指针,指向各个内存块首地址,首先将plink指向第一个内存块的指针域,pblk赋值为第二个内存块指针域的值,最后循环做若下操作,将plink指向的指针域指向下一个内存块指针域,plink指向下一个内存块指针域,然后将下一个内存块指针域的值赋值给pblk,最后一个内存块指向为空。最后填充起始地址,空闲地址,可用内存块数,总内存块数,每个内存块的大小,返回无错误的perr状态。
以创建的分区内存块数为6为例OSMemCreate函数创建分区完成后,内存控制块与对应的内存分区,分区内的内存块之间的关系如下:
以三个内存控制块为例,内存控制块数组变化为:
调用OSMemGet函数,从已经建立的内存分区中申请一个内存块,传入参数为pmem,指向特定分区内存控制块的指针,指向出错信息的指针perr,返回可用内存块地址。
void *OSMemGet (OS_MEM *pmem, INT8U *perr)
{
void *pblk;
#if OS_CRITICAL_METHOD == 3 //为CPU状态寄存器分配内存
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (perr == (INT8U *)0) { //传进来的perr为空,不正确,返回空地址
return ((void *)0);
}
if (pmem == (OS_MEM *)0) { //传进来对应的分区内存控制块为不存在,返回空地址,返回错误信息OS_ERR_MEM_INVALID_PMEM
*perr = OS_ERR_MEM_INVALID_PMEM;
return ((void *)0);
}
#endif
OS_ENTER_CRITICAL();
if (pmem->OSMemNFree > 0) { //检查该分区中是否有可用的内存块
pblk = pmem->OSMemFreeList; //如果是,pllk指向下一个可用内存块
pmem->OSMemFreeList = *(void **)pblk; //调整下一个可用内存块地址
pmem->OSMemNFree--; //可用内存块数量减一
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE; //无错误
return (pblk); //返回可用内存苦熬地址
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_MEM_NO_FREE_BLKS; // 返回无空余内存块
return ((void *)0); //返回空指针
}
首先为CPU状态寄存器分配内存,检查传进来的perr是否为空,是的话返回空地址,判断传进来对应的分区内存控制块是否不存在,不存在返回空地址,返回错误信息OS_ERR_MEM_INVALID_PMEM,即无效的分区内存控制块指针。然后检查该分区中是否有可用的内存块,没有的话返回OS_ERR_MEM_NO_FREE_BLKS,即无空余内存块,返回空指针。如果有可用内存块,如果是,pllk指向可用内存块,调整下一个可用内存块地址,可用内存块数量减一,perr返回无错误,返回可用内存块地址。
当不在使用内存块时,使用函数OSMemPut释放这个内存块,放回到相应的内存分区中。传入参数为特定分区内存控制块指针pmem,内存块指针pblk。
INT8U OSMemPut (OS_MEM *pmem, void *pblk)
{
#if OS_CRITICAL_METHOD == 3 //为CPU状态寄存器分配内存
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (pmem == (OS_MEM *)0) { //检查是否是一个有效的内存控制块指针
return (OS_ERR_MEM_INVALID_PMEM);
}
if (pblk == (void *)0) { //检查是否是一个有效的内存块地址
return (OS_ERR_MEM_INVALID_PBLK);
}
#endif
OS_ENTER_CRITICAL();
if (pmem->OSMemNFree >= pmem->OSMemNBlks) { //内存块全在使用中
OS_EXIT_CRITICAL();
return (OS_ERR_MEM_FULL);
}
*(void **)pblk = pmem->OSMemFreeList; //将归还的内存空间插入空余内存块链表的表头
pmem->OSMemFreeList = pblk; //
pmem->OSMemNFree++; //分区中剩余内存块数量加1
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
首次为CPU状态寄存器分配内存,检查是否是一个有效的内存控制块指针,检查是否是一个有效的内存块地址。检查内存块全未使用,是的话返回OS_ERR_MEM_FULL,空闲内存块已满。将归还的内存空间插入空余内存块链表的表头,分区中剩余内存块数量加1,返回无错误。
可以使用OSMemQuery函数查询特定分区的有关信息,通过该函数能知道内存分区内存块大小,可用内存块数目,已经使用了的内存块数量。
#if OS_MEM_QUERY_EN > 0
INT8U OSMemQuery (OS_MEM *pmem, OS_MEM_DATA *p_mem_data)
{
#if OS_CRITICAL_METHOD == 3 //为CPU状态寄存器分配内存
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (pmem == (OS_MEM *)0) { //检查是否是一个有效的内存控制块指针
return (OS_ERR_MEM_INVALID_PMEM);
}
if (p_mem_data == (OS_MEM_DATA *)0) { //检查是否是一个有效的内存信息单元
return (OS_ERR_MEM_INVALID_PDATA);
}
#endif
OS_ENTER_CRITICAL();
p_mem_data->OSAddr = pmem->OSMemAddr; //内存分区首地址
p_mem_data->OSFreeList = pmem->OSMemFreeList;//空闲内存块链表首地址
p_mem_data->OSBlkSize = pmem->OSMemBlkSize;//每个内存块所含的字节数
p_mem_data->OSNBlks = pmem->OSMemNBlks;//内存分区总的内存块数
p_mem_data->OSNFree = pmem->OSMemNFree;//空闲内存块总数
OS_EXIT_CRITICAL();
p_mem_data->OSNUsed = p_mem_data->OSNBlks - p_mem_data->OSNFree;//正在使用的内存块总数
return (OS_ERR_NONE);
}
#endif
首先,为CPU状态寄存器分配内存,检查是否是一个有效的内存控制块指针,是否是一个有效的内存信息单元,填充内存分区首地址,空闲内存块链表首地址,每个内存块所含的字节数,内存分区总的内存块数,空闲内存块总数,正在使用的内存块总数。
另外还有获取和设置内存分区名的函数
INT8U OSMemNameGet (OS_MEM *pmem, INT8U *pname, INT8U *perr)
void OSMemNameSet (OS_MEM *pmem, INT8U *pname, INT8U *perr)