一.前言
作为一个RTOS操作系统,内存管理是必备的功能,因此UCOSII也具有内存管理能力。通常应用程序可以调用ANSIC编译器的malloc()和free()函数来动态的分配和释放内存,但是嵌入式实时操作系统中最好不要这么做,多次这样的操作会把原来很大的一块连续存储区逐渐分割成许多非常小并且彼此不相邻的存储区域,这就是存储碎片。
UCOSII中将存储空间分成区和块,每个存储区有数量不等大小相同的存储块,在一个系统中可以有多个存储区。
二.内存控制块
操作系统以分区为单位来管理动态内存,而任务以内存块为单位来获得和释放动态内存。内存分区及内存块的使用情况则由内存控制块来记录。
1.内存分区及内存控制块的定义
在内存中定义一个内存分区及其内存块的方法非常简单,只需定义一个二维数组即可。例如:
INT8U IntMemBuf[10][10]; //定义一个内存区,包含10个内存块,每个内存块由10个u8数据构成
2.内存控制块OS_MEM的结构
#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
typedef struct os_mem { /* MEMORY CONTROL BLOCK */
void *OSMemAddr; /* 内存分区的指针 */
void *OSMemFreeList; /* 内存控制块链表指针 */
INT32U OSMemBlkSize; /* 内存块的长度 */
INT32U OSMemNBlks; /* 分区内内存块的数目 */
INT32U OSMemNFree; /* 分区内当前可分配的内存块的数目 */
#if OS_MEM_NAME_EN > 0u
INT8U *OSMemName; /* Memory partition name */
#endif
} OS_MEM;
内存控制块与内存分区和内存块之间的关系:
3.空内存控制块链表
在UCOSII初始化时,会调用内存控制块的初始化函数OS_MemInit()定义并初始化一个空内存控制块链表。
在这个空内存控制块链表中,一个有OS_MAX_MEM_PART(在文件OS_CFG.H中定义的常数)个空内存控制块。这些空内存控制块的指针成员OSMemFreeList暂时作为指向下一个空内存控制块指针。
空内存控制块链表的结构如下图所示:
每当应用程序需要创建一个内存分区时,系统就会从空内存控制块链表中摘取一个控制块,而把链表的头指针OSMemFreeList指向下一个空内存控制块;而每当应用程序释放一个内存分区时,则会把该分区对应的内存控制块归还给空内存控制块链表。
三.动态内存的管理
动态内存管理函数有:
创建动态内存分区函数OSMemCreate();
OS_MEM *OSMemCreate (void *addr, //内存分区的起始地址
INT32U nblks, //分区中内存块的数目
INT32U blksize, //每个内存块的字节数
INT8U *perr) //错误信息
请求获得内存块函数OSMemGet();
void *OSMemGet (OS_MEM *pmem, //内存分区的指针
INT8U *perr) //错误信息
释放内存块函数OSMemPut();
INT8U OSMemPut (OS_MEM *pmem, //内存分区的指针
void *pblk) //待释放内存块的指针
查询动态内存分区状态函数OSMemQuery();
#if OS_MEM_QUERY_EN > 0u
INT8U OSMemQuery (OS_MEM *pmem, //待查询的内存控制块的指针
OS_MEM_DATA *p_mem_data); //存放分区状态信息的结构的指针
其中p_mem_data是一个OS_MEM_DATA 类型的结构。定义如下:
typedef struct os_mem_data {
void *OSAddr; /* 内存分区的指针 */
void *OSFreeList; /* 分区内内存块链表的头指针 */
INT32U OSBlkSize; /* 内存块的长度 */
INT32U OSNBlks; /* 分区内内存块的数目 */
INT32U OSNFree; /* 分区内空闲内存块的数目 */
INT32U OSNUsed; /* 已被分配的内存块数目 */
} OS_MEM_DATA;
四.内存管理应用代码(基于stm32f4)
/**********UCOSII任务堆栈设置*****************/
//1.START 任务
//设置任务优先级
#define START_TASK_PRIO 10 //开始任务的优先级设置为最低
//设置任务堆栈大小
#define START_STK_SIZE 128
//创建任务堆栈空间
OS_STK START_TASK_STK[START_STK_SIZE];
//任务函数接口
void start_task(void *pdata);
//2.main_task主任务
//任务优先级
#define MAIN_TASK_PRIO 4
//任务堆栈大小
#define MAIN_STK_SIZE 128
//任务堆栈
OS_STK MAIN_TASK_STK[MAIN_STK_SIZE];
//任务函数
void main_task(void *pdata);
//3.memmanage_task内存管理任务
//任务优先级
#define MEMMANAGE_TASK_PRIO 5
//任务堆栈大小
#define MEMMANAGE_STK_SIZE 128
//任务堆栈
OS_STK MEMMANAGE_TASK_STK[MEMMANAGE_STK_SIZE];
//任务函数
void memmanage_task(void *pdata);
/********************定义存储区**********************/
//定义一个存储区(内部)
OS_MEM *INTERNAL_MEM; //定义内存控制块指针
//存储区中存储块数量
#define INTERNAL_MEM_NUM 5
//每个存储块大小
//由于一个指针变量占用4字节所以块的大小一定要为4的倍数
//而且必须大于一个指针变量(4字节)占用的空间,否则的话存储块创建不成功
#define INTERNAL_MEMBLOCK_SIZE 100
//存储区的内存池,使用内部RAM
u8 Internal_RamMemp[INTERNAL_MEM_NUM][INTERNAL_MEMBLOCK_SIZE]; //划分分区及内存块
//定义一个存储区(外部)
OS_MEM *EXTERNAL_MEM; //定义内存控制块指针
//存储区中存储块数量
#define EXTRENNAL_MEM_NUM 5
//每个存储块大小
//由于一个指针变量占用4字节所以块的大小一定要为4的倍数
//而且必须大于一个指针变量(4字节)占用的空间,否则的话存储块创建不成功
#define EXTERNAL_MEMBLOCK_SIZE 100
//存储区的内存池,使用外部SRAM(位置:0X68000000)
u8 External_RamMemp[EXTRENNAL_MEM_NUM][EXTERNAL_MEMBLOCK_SIZE] __attribute__((at(0X68000000)));//划分分区及内存块
/********************main主函数**********************/
int main(void)
{
delay_init(168); //延时初始化
uart_init(115200); //串口初始化波特率为115200
LED_Init(); //初始化与LED连接的硬件接口
KEY_Init(); //key初始化
LCD_Init(); //LCD初始化
FSMC_SRAM_Init(); //初始化SRAM
ucos_load_main_ui(); //加载主UI1
OSInit(); //初始化UCOSII
OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//创建起始任务
OSStart(); //开始执行UCOS程序
}
//开始任务
void start_task(void *pdata)
{
u8 err;
OS_CPU_SR cpu_sr=0;
pdata = pdata;
OSStatInit(); //初始化统计任务.这里会延时1秒钟左右
OS_ENTER_CRITICAL(); //进入临界区(无法被中断打断)
//创建一个内部存储分区
INTERNAL_MEM=OSMemCreate(Internal_RamMemp,INTERNAL_MEM_NUM,INTERNAL_MEMBLOCK_SIZE,&err);
//创建一个外部存储分区,使用外部SRAM(位置:0X68000000)
EXTERNAL_MEM=OSMemCreate(External_RamMemp,EXTRENNAL_MEM_NUM,EXTERNAL_MEMBLOCK_SIZE,&err);
OSTaskCreate(main_task,(void *)0,(OS_STK*)&MAIN_TASK_STK[MAIN_STK_SIZE-1],MAIN_TASK_PRIO); //在开始任务中创建main任务
OSTaskCreate(memmanage_task,(void *)0,(OS_STK*)&MEMMANAGE_TASK_STK[MEMMANAGE_STK_SIZE-1],MEMMANAGE_TASK_PRIO); //在开始任务中创建memmanage_task任务
OSTaskSuspend(START_TASK_PRIO); //挂起起始任务.
OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)
}
//主任务的任务函数(完成内存的申请及释放)
void main_task(void *p_arg)
{
u8 key,num,i=0;
static u8 external_memget_num;
u8 *internal_buf[5];
u8 *external_buf;
u8 err;
while(1)
{
key = KEY_Scan(0); //扫描按键
switch(key)
{
case WKUP_PRES: //按下KEY_UP键:申请内部内存
for(i=0;i<5;i++)
{
internal_buf[i]=OSMemGet(INTERNAL_MEM,&err); //循环申请五次内存并放在internal_buf[i]缓存区内
if(err == OS_ERR_NONE) //内存申请成功
{
sprintf((char*)internal_buf[i],"internal_buf[%d]=%d\r\n",i,i);//在申请到的内存区域内放置数据
printf("%s",internal_buf[i]); //显示出来
printf("internal_buf[%d]内存申请之后的地址为:%#x\r\n",i,(u32)(internal_buf[i]));
printf("internal_buf[%d]内存申请成功!\r\n",i);
}
else if(err == OS_ERR_MEM_NO_FREE_BLKS) //内存块不足
{
LCD_ShowString(30,180,200,16,16,"INTERNAL_MEM Empty! ");
}
delay_ms(500); //延时500ms,也就是每500ms申请一次内存,共五次
}
break;
case KEY1_PRES:
for(i=5;i>0;i--)
{
if(internal_buf[i-1] != NULL) //internal_buf不为空就释放内存
{
OSMemPut(INTERNAL_MEM,internal_buf[i-1]);//循环5次释放内部内存
printf("internal_buf[%d]内存释放之后的地址为:%#x\r\n",i-1,(u32)(internal_buf[i-1]));
printf("internal_buf[%d]内存释放成功!\r\n",i-1);
internal_buf[i-1]=NULL; //释放后的缓存区指向NULL!
}
else if(internal_buf[i-1] == NULL) //内存已释放
{
LCD_ShowString(30,180,200,16,16,"INTERNAL_MEM released! ");
}
delay_ms(500); //延时500ms,也就是每500ms申请一次内存,共五次
}
break;
case KEY2_PRES: //按下KEY2键:申请外部内存
external_buf=OSMemGet(EXTERNAL_MEM,&err);
if(err == OS_ERR_NONE) //内存申请成功
{
printf("external_buf内存申请之后的地址为:%#x\r\n",(u32)(external_buf));
LCD_ShowString(30,260,200,16,16,"Memory Get success! ");
external_memget_num++;
POINT_COLOR = BLUE;
sprintf((char*)external_buf,"EXTERNAL_MEM Use %d times",external_memget_num);
LCD_ShowString(30,276,200,16,16,external_buf);
POINT_COLOR = RED;
}
if(err == OS_ERR_MEM_NO_FREE_BLKS) //内存块不足
{
LCD_ShowString(30,260,200,16,16,"EXTERNAL_MEM Empty! ");
}
break;
case KEY0_PRES:
if(external_buf != NULL) //external_buf不为空就释放内存
{
OSMemPut(EXTERNAL_MEM,external_buf);//释放外部内存
printf("external_buf内存释放之后的地址为:%#x\r\n",(u32)(external_buf));
LCD_ShowString(30,260,200,16,16,"Memory Put success! ");
}
break;
}
num++;
if(num==50)
{
num=0;
LED0 = ~LED0;
}
delay_ms(10); //延时10ms
}
}
//内存管理任务(显示总的内存缓存块数以及剩余缓存块的数)
void memmanage_task(void *p_arg)
{
LCD_ShowString(5,164,200,16,16,"Total: Remain:");
LCD_ShowString(5,244,200,16,16,"Total: Remain:");
while(1)
{
POINT_COLOR = BLUE;
LCD_ShowxNum(53,164,INTERNAL_MEM->OSMemNBlks,1,16,0);
LCD_ShowxNum(125,164,INTERNAL_MEM->OSMemNFree,1,16,0);
LCD_ShowxNum(53,244,EXTERNAL_MEM->OSMemNBlks,1,16,0);
LCD_ShowxNum(125,244,EXTERNAL_MEM->OSMemNFree,1,16,0);
POINT_COLOR = RED;
delay_ms(100); //延时100ms
}
}
五.总结
1.通过定义一个二维数组在内存中划分一个内存分区,其中的所有内存块应大小相等。
2.系统通过与内存分区相关联的内存控制块来对内存分区进行管理。
3.划分及创建内存分区根据需要由应用程序负责,而系统只提供了可供任务调用的相关函数。
4.在UCOSII中,在使用和释放动态内存的安全性方便,要由应用程序全权负责。
六. 感谢支持
完结撒花!希望看到这里的小伙伴能点个关注,我后续会持续更新,也欢迎大家广泛交流。
码字实属不易,如果本文对你有10分帮助,就赏个10分把,感谢各位大佬支持!