11. µCOS-Ⅲ的内存管理
一、µC/OS-Ⅲ的内存管理
内存管理是指软件运行时对内存资源的分配和使用的一种计数,其最主要的目的就是为了能够高效且快速地分配,并且在释放的时候释放不再使用的内存空间。例如,电脑上编写 C 程序的时候,会使用标准 C 库中的函数 malloc()和函数 free() 动态地进行内存的申请和释放。同样的在嵌入式开发中,编译器也为开发者提供了用于动态申请和释放内存的函数 malloc() 和函数 free(),但是一般不建议在嵌入式系统中使用函数 malloc()和函数 free(),特别是在使用了实时操作系统的嵌入式开发中,因为函数 malloc() 和函数 free() 在申请和释放内存的时候,会导致产生大量的内存碎片,最后甚至可能出现明明还有大量的空闲内存,但是却无法再申请内存的情况,这是因为大量地内存碎片占据了空闲内存,这些内存碎片数量多、单个占用内存小、地址空间不连续,这将是的无法再分配较大块的内存。
为此 µC/OS-Ⅲ 提供了一个内存管理的方案,µC/OS-Ⅲ 将一块大内存作为一个内存区,一个内存区中有多个大小均相同的内存块组成,如下图所示:
因为在同一个内存区内在内存块大小都相同,因此使用 µC/OS-Ⅲ 的内存管理方案申请和释放内存,就完全不会在内存区内产生内存碎片,这样一来便极大地提高了系统中内存的使用效率。
用户还可以根据实际的需求,创建多个不同的内存区,每个内存区中内存块的数量和大小都可以是不同的,完全由实际的需求决定,如下图所示:
内存区所需的内存空间可以由编译器静态分配,也可以使用 malloc() 函数动态分配,只需要保证分配给内存区的内存空间不被释放即可。因为内存区被分为了多个大小相同的内存块,这就很好与二维数组联系起来,因此在使用静态方式为内存区分配内存时,可以定义一个二维数组,将这个二值数组作为内存区所需的内存空间,如下所示:
static uint8_t buffer[10][32];
使用上面的二维数组作为内存区所需的内存空间,就能够表示一个具有 10 个内存块,且每个内存块大小为 32 字节的内存区,当然,要是理解成一个具有 32 个内存块,且每个内存块大小为 10 字节的内存区也是没有问题的,这仅仅是一种表达方式,只有在创建内存区的时候明确指定的内存区中内存块的数量和大小,才是实际的值。
二、µC/OS-Ⅲ内存管理相关API函数
2.1、内存区的结构体
内存区的结构体,该结构体定义在文件 os.h 中,具体的代码如下所示:
struct os_mem {
#if (OS_OBJ_TYPE_REQ > 0u) // 此宏用于使能检查内核对象的类型
// 对象类型,在软件定时器初始化时,软件定时器的类型会被初始化为 OS_OBJ_TYPE_MEM
OS_OBJ_TYPE Type;
#endif
#if (OS_CFG_DBG_EN > 0u) // 此宏用于使能代码调试功能
// 如果使能了代码调试功能,那么就会为每一个内存区赋一个名称,方便调试
CPU_CHAR *NamePtr;
#endif
void *AddrPtr; // 指向内存区起始地址的指针
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
// 用于第三方调试工具的成员变量
#if (defined(OS_CFG_TRACE_EN) && (OS_CFG_TRACE_EN > 0u))
CPU_ADDR MemID;
#endif
};
2.2、创建一个内存区
void OSMemCreate (OS_MEM *p_mem, // 指向内存区结构体的指针
CPU_CHAR *p_name, // 指向作为内存区名的 ASCII字符串的指针
void *p_addr, // 指向内存区起始地址的指针
OS_MEM_QTY n_blks, // 指向内存区起始地址的指针
OS_MEM_SIZE blk_size, // 内存区中内存块的大小
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSMemCreate() 的错误代码描述,如下所示:
OS_ERR_NONE // 成功创建一个内存区
OS_ERR_ILLEGAL_CREATE_RUN_TIME // 在系统运行过程中非法创建内核对象
OS_ERR_MEM_CREATE_ISR // 在中断中非法调用该函数
OS_ERR_MEM_INVALID_BLKS // 无效的内存块数量
OS_ERR_MEM_INVALID_P_ADDR // 无效的内存区起始地址
OS_ERR_MEM_INVALID_SIZE // 无效的内存块大小
2.3、从内存区中获取一个内存块
// 返回值:指向内存块的起始地址
void *OSMemGet (OS_MEM *p_mem, // 指向内存区结构体的指针
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSMemGet() 的错误代码描述,如下表所示:
OS_ERR_NONE // 成功从内存区中获取内存块
OS_ERR_MEM_INVALID_P_MEM // 指向内存区结构体的指针为空
OS_ERR_MEM_NO_FREE_BLKS // 内存区中没有空闲的内存块
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是内存区
2.4、释放内存块到内存区中
void OSMemPut (OS_MEM *p_mem, // 指向内存区结构体的指针
void *p_blk, // 待释放的内存块
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSMemPut() 的错误代码描述,如下表所示:
OS_ERR_NONE // 成功将内存块释放回内存区
OS_ERR_MEM_FULL // 内存区中所有的内存块都已释放回内存区
OS_ERR_MEM_INVALID_P_BLK // 指向内存块的指针为空
OS_ERR_MEM_INVALID_P_MEM // 指向内存区的指针为空
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是内存区
三、实验例程
main() 函数内容如下:
int main(void)
{
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
UART_Init(&g_usart1_handle, USART1, 115200);
Key_Init();
UC_OS3_Demo();
return 0;
}
µC/OS-Ⅲ 例程入口函数:
/**
* @brief µC/OS-Ⅲ例程入口函数
*
*/
void UC_OS3_Demo(void)
{
OS_ERR error = {0};
OSInit(&error); // 初始化µC/OS-Ⅲ
// 创建开始任务
OSTaskCreate((OS_TCB * ) &start_task_tcb, // 任务控制块
(CPU_CHAR * ) "start_task", // 任务名
(OS_TASK_PTR ) Start_Task, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) START_TASK_PRIORITY, // 任务优先级
(CPU_STK * ) start_task_stack, // 任务堆栈
(CPU_STK_SIZE) START_TASK_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) START_TASK_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
OSStart(&error); // 开始任务调度
}
START_TASK 任务配置:
OS_MEM mem; // 内存区
static uint8_t buffer[10][100]; // 内存区缓冲区
uint8_t *p_mem[sizeof(buffer) / sizeof(buffer[0])]; // 存放申请内存块的地址
/**
* START_TASK 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_SIZE 256
OS_TCB start_task_tcb;
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
void Start_Task(void *p_arg);
/**
* @brief 开始任务的任务函数
*
* @param p_arg 任务参数
*/
void Start_Task(void *p_arg)
{
OS_ERR error = {0};
CPU_INT32U cnts = 0;
CPU_Init(); // 初始化CPU库
CPU_SR_ALLOC(); // 临界区保护
cnts = HAL_RCC_GetSysClockFreq() / OS_CFG_TICK_RATE_HZ;
OS_CPU_SysTickInit(cnts); // 根据配置的节拍频率配置SysTick中断及优先级
// 创建一个内存区
OSMemCreate(&mem, "mem", buffer, sizeof(buffer) / sizeof(buffer[0]), sizeof(buffer[0]), &error);
CPU_CRITICAL_ENTER(); // 进入临界区
// 创建任务1
OSTaskCreate((OS_TCB * ) &task1_tcb, // 任务控制块
(CPU_CHAR * ) "task1", // 任务名
(OS_TASK_PTR ) Task1, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) TASK1_PRIORITY, // 任务优先级
(CPU_STK * ) task1_stack, // 任务堆栈
(CPU_STK_SIZE) TASK1_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) TASK1_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度,设置为0,则默认时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
CPU_CRITICAL_EXIT(); // 退出临界区
OSTaskDel(NULL, &error); // 删除任务
}
TASK1 任务配置:
/**
* @brief 任务1的任务函数
*
* @param p_arg 任务参数
*/
void Task1(void *p_arg)
{
OS_ERR error = {0};
static uint8_t p_next_mem_index = 0;
while (1)
{
switch (Key_Scan(0))
{
case KEY1_PRESS:
// 申请内存
if (p_next_mem_index < sizeof(buffer) / sizeof(buffer[0]))
{
p_mem[p_next_mem_index] = OSMemGet(&mem, &error);
if (p_mem[p_next_mem_index] != NULL)
{
printf("申请内存成功\r\n");
printf("申请的内存地址:%p\r\n", p_mem[p_next_mem_index]);
p_next_mem_index++;
}
else
{
printf("申请内存失败\r\n");
}
}
printf("空闲内存块的数量位:%d\r\n", mem.NbrFree);
break;
case KEY2_PRESS:
// 释放内存
if (p_next_mem_index > 0)
{
if (p_mem[p_next_mem_index - 1] != NULL)
{
OSMemPut(&mem, p_mem[p_next_mem_index - 1], &error);
printf("释放内存成功\r\n");
printf("释放的内存地址:%p\r\n", p_mem[p_next_mem_index - 1]);
p_mem[p_next_mem_index - 1] = NULL;
p_next_mem_index--;
}
}
printf("空闲内存块的数量位:%d\r\n", mem.NbrFree);
break;
default:
break;
}
OSTimeDly(10, OS_OPT_TIME_DLY, &error);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY