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);
    }
}
posted @   星光映梦  阅读(95)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示