从零开始学习UCOSII操作系统12--内存管理
前言:
在标准的C语言中,可以用malloc()和free()2个动态的分配内存和
释放内存,但是在嵌入式中,调用malloc()和free()却是非常危险的。
因为多次调用这两个函数,会把原来的很大的一块连续的内存区域逐渐的分割成许多非常小的而且彼此又不相邻的内存块,也就是所谓的内存碎片。这样子的话,使得程序后面连一段非常小的内存都分配不到,另外由于内存管理算法上的原因,malloc()和free()函数执行的时间是不确定的。
1、分区的概念:
操作系统把连续的大块内存按分区来管理,每个分区中包含整数个大小相同的内存块,利用这种机制,UCOSII对malloc和free函数进行了改进。使得他们可以得到和释放固定的大小的内存块。这样子malloc和free函数的执行时间就是确定的了。(为什么?)
函数原型:void * malloc(unsigned int num_bytes);
//分配长度为NUM_BYTES字节的内存块。
返回值是void指针,void*表示未确定的类型的指针,明确说明,这个函数仅仅只是为了申请内存的空间,而不是申请特定内存空间。
所以用户可以根据需要把malloc申请到的内存,强制转换成自己需要的那种模式。但是有一个缺点:malloc只管分配内存空间,并不对空间进行初始化的操作,所以申请到的内存的值是随机分配的,经常会使用memset()进行置0的操作后再重新的使用。
(3)一个简单的调用函数的实例:
int p;
p = (int )malloc(sizeof(int) 128);
//这里需要检查一下,是否分配成功了,分配不成功需要上报一个错误的值
double pd = (double )malloc(sizeof(double) 12);
free(p);
free(pd);
p =NULL;
pd = NULL;
指针赋值完后,需要赋值为NULL是一个良好的习惯。
2、内存控制块
为了便于内存的管理,在UCOSII中使用内存控制块的数据结构跟踪每一个内存分区。系统中的每个内存分区都有它自己的内存控制块。
OSMemAddr:
指向内存分区的起始地址的指针,她在建立内存分区初始化的时候,在此之后就不能更改了。
OSMemFreeList:
指向下一个空余内存控制块或者下一个空余内存块的指针,具体的含义应该要根据内存分区是否已经建立来决定。
OSMemBlkSize:
内存分区中内存块的大小,是建立该内存分区时定义的。
OSMemNBlks:
内存分区中总的内存块的数量,也是建立该内存分区时定义的。
OSMemNFree:
内存分区中当前可以获得的空余的内存块的数量。
typedef struct
{
void * OSMemAddr;
void * OSMemFreeList;
INT32U OSMemBlkSize;
INT32U OSMemBlks;
INT32U OSMemNFree;
}OS_MEM;
3、使用内存管理机制:
建立一个内存分区:OSMEMCreate()
在使用一个内存分区之前,必须先建立该内存分区,这个操作可以通过调用函数OSMemCreate来完成,下面是一个创建100个内存块并且每个内存块大小为32B的内存分区。
OS_MEM * CommTxBuf;
//在创建一块内存分区之前,需要在栈中建立申请一块内存。
INT8U CommTxPart[100][32];
void main(void)
{
INT8U err;
OSInit();
CommTxbuf = OSMemCreate(CommTxPart,100,32,&err);
OSStart();
}
申请一块内存分区OSMemCreate()
OS_MEM OSMemCreate(void addr, INT32U nblks,INT32U blksize,INT8U err)
{
/初始化定义一些 变量/
OS_MEM pmem;
INT8U pblk;
void *plink;
INT32U i;
pmem = OSMemFreeList;
if(OSMemFreeList != (OS_MEM *)0)
{
OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;
}
}
分配一个内存块OSMemGet()
应用程序可以调用OSMemGet()函数,从已经建立的内存分区中申请一个内存块,该函数的唯一的参数就是指向特定内存分区的指针。
void *OSMemGet(OS_MEM * pmem, INT8U *err)
{
void *pblk;
OS_ENTER_CRITICAL();
/* 如果内存空闲块的大小大于0的话 */
if(pmem->OSMemNFree > 0)
{
pblk = pmem->OSMemFreeList;
pmem->OSMemFreeList = *(void **)pblk;
pmem->OSMemNFree--;
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return (pblk);
}
OS_EXIT_CRITICAL();
*err = OS_MEM_NO_FREE_BLKS;
return ((void *)0);
}
PS:可以在中断服务子程序中调用OSMemGet(),因为在暂时没有内存块可用的情况下,OSMemGet()不会等待,而是立即返回NULL指针。
当然有请求一个内存块,就会有一个释放一块内存块:
OSMemPut()
当一个应用程序不再使用某个内存块时,必须及时的把它释放,病放回相应的内存分区中,这个操作由OSMEMPut函数完成。
必须注意的是OSMemPut()并不知道该内存块时属于哪个内存分区的,也就是说,如果用户程序从一个包含32B内存块的分区中分配了一块内存块,那么用完之后,千万不能返还一个包含120B内存块的内存分区,因为,当应用程序下一次申请120B分区中的一个内存块中,它只会得到32B的可用空间。
其他的88B属于其他的任务,这就有可能使得系统崩溃。
OSMemPut()
第一个参数是指向:OSMemput()第一个参数pmem是指向内存控制块的。
pblk指向由pmem管理的内存块。
OSMemPut()函数必须确保传递给它的参数指针非空的,但是遗憾的是,OSMemPut()并不知道要释放的内存块是否属于此内存分区,因此,应用程序必须保证把内存块释放到合理的内存分区中。
INT8U OSMemPut(OS_MEM * pmem, void * pblk)
{
*(void **)pblk = pmem->OSMemFreeList;
pmem->OSMemFreeList = pblk;
pmem->OSMemNFree++;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
4、查询一个内存分区的状态,OSMemQuery()
在UCOSII中可以使用OSMemQuery函数来查询一个特定内存分区的有关信息。通过该函数尅知道特定内存分区中内存块的大小,可用内存块的数目,和已经使用了的内存块数目等信息。
所有的信息都存放在一个叫做OS_MEM_DATA数据结构中。
typedef struct
{
void *OSAddr; //指向内存分区首地址的指针
void *OSFreeList; //指向空余内存块链表首地址的指针
INT32U OSBlkSize; //每个内存块所含有的字节数
INT32U OSNBlks; //内存分区总的内存数
INT32U OSNFree; //空余内存块的总数
INT32U OSNUsed; //正在使用的内存块总数
}OS_MEM_DATA;
5、使用内存分区,理解这个小实例的应用
UCOSII中的动态内存分配功能并利用它进行消息传递。
第一个任务读取并检查模拟量输入的值(如气压、温度以及电压等)如果该值超过一定的阈值,就向第二个任务发送一条消息,该消息中含有出错的信息,出错的通道号以及其他的出错的代码。
AnalogInputTask()
{
for(;;)
{
for(所有模拟量的输入) //不断的轮训来查询任务一的模拟量
读取模拟量的输入值;
if(模拟量超过阈值)
{
得到一个内存块,
得到当前系统的时间
将刚刚错误的几项存入到内存块里面。
}
}
延时任务,直到再次对模拟量进行采样时为止。
}
ErrorHandlerTask()
{
for(;;)
{
等待错误处理队列的消息
得到指向包含有关错误数据的内存块的指针
读入消息,并根据消息的内存执行相应的操作。
将内存块放回到相应的内存分区中
}
}
6、等待内存分区中的一个内存块
有时候,在内存分区暂时没有可用的空余内存块的情况下,让一个申请内存块的任务等待也是可以的,但是UCOSII本身在内存管理上面并不支持这项功能,如果确实需要,则可以通过为特定内存分区增加计数型信号量的方法,实现这个功能。
(2)应用程序为了申请分配内存块,首先得到相应的信号量,然后才能调用OSMemget()函数,如果需要释放内存块,只要将内存块释放到相应的内存分区中,并且发送一个信号量即可。
(3)显然如果系统中只有一个任务使用动态内存块的话,就没有必要使用信号量了,因为这种情况没有必要使用信号量了,这种情况不需要保证内存资源的互斥,事实上,除非要实现多任务的共享内存,否则连内存分区都不需要。
(4)当一个任务运行的时候,只有在信号量有效的时候,才能得到内存块,一旦信号量有效了,就可以申请内存块并且使用它,儿没有必要对OSSemPend()返回的错误代码进行检查。
因为只有在这里,只有当一个内存块被其他的任务释放病返回到内存分区的时候,UCOSII才会返回到该任务中,使得继续运行。
书中的小实例:
OS_EVENT * semaphorePtr;
OS_MEM * PartitionPtr;
INT8U partition[100][32];
OS_STK TaskStk[1000];
void main()
{
INT8U err;
OSInit();
SemaphorePtr = OSSemCreate(100);
PartitionPtr = OSMemCreate(Partition,100,32,&err);
OSTaskCreate(Task,(void *)0, &TaskStk[999], &err);
OSStart();
}
void Task(void * pada)
{
INT8U err;
INT8U *pblock;
for(;;)
{
//这里使用了我请求一个信号后,然后我才释放掉一个信号量
OSSemPend(Semaphore,0,&err);
pblock = OSMemGet(PartitionPtr, &err);
//使用内存块,具体怎么用,按照客户的要求
OSMemPut(PartitionPtr, pblock);
OSSemPost(SemaphorePtr);
}
}