RTOS内功修炼记(七)—— 内存管理

内容导读:

第一篇文章讲述了任务的三大元素:任务控制块、任务栈、任务入口函数,并讲述了编写RTOS任务入口函数时三个重要的注意点。

第二篇文章从任务如何切换开始讲起,引出RTOS内核中的就绪列表、优先级表,一层一层为你揭开RTOS内核优先级抢占式调度方法的神秘面纱。

第三篇文章讲述了RTOS内核到底是如何管理中断的?用户该如何编写中断处理函数?以及用户如何设置临界段?

第四篇文章讲述了RTOS内核中所有与时间相关的内容,包括时钟管理、时间管理、任务延时机制、空闲任务、软件定时器、时间片调度:

第五篇文章讲述了RTOS内核中各种任务同步量的实现,虽然多种多样,但是万变不离其宗,一是全局变量,而是pend-post机制。

第六篇文章主要讲述了用于任务间通信的一些内核对象实现,主要有四个:消息队列和优先级消息队列,邮箱队列和优先级邮箱队列。

建议先阅读上文,对RTOS内核的抢占式调度机制和时间片调度机制、RTOS内核对中断的处理机制与裸机的不同之处、RTOS中任务延时的机制、pend-post机制理解之后,再阅读本文也不迟。


1. 知识点回顾

1.1. 静态内存

uint8_t buffer[128];

当你写下这行代码时,就意味着你使用了128字节的静态内存,buffer的空间由编译器静态分配到栈中,且程序运行过程中,buffer的大小无法改变,这就称之为静态内存。

1.2. 动态内存

void *buffer_ptr = NULL;
buffer_ptr = malloc(128 * sizeof(uint8_t));

当你写下这行代码时,就意味着你使用了128字节的动态内存,buffer在程序运行时从堆中分配指定大小的空间,且不用的时候可以使用free释放,归还给堆空间。

2. RTOS中的动态内存管理

2.1. 为什么需要动态内存管理

静态内存由编译器分配,这个没什么好说的~

一般情况下使用malloc申请分配动态内存有两个缺陷:

① 由于分配算法的复杂度,分配的时间不定;

② 在不断申请、释放的过程中,容易因为内存对齐而产生碎片化内存;

这两个缺陷在实时操作系统中是不允许的,所以操作系统必须提供一套有效、合理、时间可确定的动态内存管理机制。

2.2. 如何进行动态内存管理

既然传统malloc存在两个缺陷,那就抱着解决这两个缺陷的目的出发,去建立一套更适合于嵌入式系统的动态内存管理系统。

目前有两种不同的解决方案:动态内存堆管理算法(mmheap)和静态内存池管理算法(mmblk),两种方法各有优缺点,TencentOS-tiny中两种管理算法都提供,接下来依托具体算法进行讲述。

3. 动态内存堆管理算法

3.1. TLSF算法

TLSF全称Two-Level Segregated Fit memory allocator,两级隔离Fit内存分配器,是一款通用的动态内存分配器,专门设计用于满足实时要求。

https://github.com/mattconte/tlsf

这款TLSF动态内存分配器具有以下特点:

  • malloc,free,realloc,memalign的算法复杂度变为O(1);
  • 每次分配的开销极低(4字节);
  • 低碎片化
  • 支持动态添加和删除内存池区域

TLSF主要采用两级位图(Two-Level Bitmap)与分级空闲块链表(Segregated Free List)的数据结构管理动态内存池(memory pool)以及其中的空闲块(free blocks),用Good-Fit的策略进行分配。

本文不对此算法进行深入讲解(博主太菜~),如果兴趣可以自行查找TLSF算法论文阅读,这里我只从应用角度给出一些需要注意的点:

TLSF算法分配速度不一定快,只是说能保证分配的时间是个常数(malloc不能保证);

② TLSF也叫多内存堆管理算法,支持动态增加或者删除多块不连续的内存,将它们作为一个内存堆使用;

3.2. TencentOS-tiny中的实现

TencentOS-tiny中此算法的实现的动态内存分配器在tos_mmheap.htos_mmheap.c中,其中默认指定最大可管理的不连续内存堆有三个,可以自行修改:

#define K_MMHEAP_POOL_MAX 3

TencentOS-tiny中提供了默认的一个缓冲区作为堆空间,在tos_global.c中定义:


uint8_t k_mmheap_default_pool[TOS_CFG_MMHEAP_DEFAULT_POOL_SIZE] __ALIGNED__(4);

大小在tos_config.h中指定:

#define TOS_CFG_MMHEAP_EN               1u

#define TOS_CFG_MMHEAP_DEFAULT_POOL_SIZE        0x8000

3.3. mmheap使用示例

/**
 *@brief   打印当前mmeheap使用情况
 *@param   none
 *@retval  none
*/
int list_mmheap_info(void)
{
    k_err_t err;
    k_mmheap_info_t mmheap_info;

    err = tos_mmheap_check(&mmheap_info);
    if (err != K_ERR_NONE) {
        printf("current mmheap info check fail, err = %d\r\n", err);
        return -1;
    }
    else {
        printf("current mmheap info:\r\n\tused: %d[0x%08x] free:%d[0x%08x]\r\n\r\n", mmheap_info.used, mmheap_info.used, mmheap_info.free, mmheap_info.free);
        return 0;
    }
}

void task1_entry(void *arg)
{   
    void *ptr = NULL;
    size_t size;
    
    /* 输出块大小的最大值 */
    printf("K_MMHEAP_BLK_SIZE_MAX is %d(0x%08x) bytes\r\n\r\n", K_MMHEAP_BLK_SIZE_MAX, K_MMHEAP_BLK_SIZE_MAX);
    
    /* 打印当前内存使用情况 */
    list_mmheap_info();
    
    /* 申请一块内存使用 */
    size = 128;
    ptr = tos_mmheap_alloc(size);
    if (ptr == NULL) {
        printf("%d bytes mem alloc fail\r\n", size);
    }
    else {
        printf("%d bytes mem alloc success, ptr is 0x%08x\r\n\r\n", size, (uint32_t)ptr);
    }
    
    /* 打印当前内存使用情况 */
    list_mmheap_info();
    
    /* 释放申请的内存 */
    tos_mmheap_free(ptr);
    printf("mem free success\r\n\r\n");
    
    /* 打印当前内存使用情况 */
    list_mmheap_info();
    
    while (1) {
        tos_task_delay(1000);
    }
}

运行结果为:

4. 静态内存池管理算法

4.1. 管理机制

静态内存池就是将一块内存划分为n个大小相等的块,用户可以动态的申请、释放一个块,假装在使用动态内存。

4.2. TencentOS-tiny中的实现

TencentOS-tiny中静态内存池管理算法的实现在tos_mmblk.htos_mmblk.c中。

提供如下四个API:

//创建一个内存池
__API__ k_err_t tos_mmblk_pool_create(k_mmblk_pool_t *mbp, void *pool_start, size_t blk_num, size_t blk_size);

//销毁一个内存池
__API__ k_err_t tos_mmblk_pool_destroy(k_mmblk_pool_t *mbp);

//申请内存池中的一个空闲块
__API__ k_err_t tos_mmblk_alloc(k_mmblk_pool_t *mbp, void **blk);

//释放回内存池一个内存块
__API__ k_err_t tos_mmblk_free(k_mmblk_pool_t *mbp, void *blk);

4.3. 静态内存块使用示例

typedef struct blk_st {
    int   id;
    char* payload;
} blk_t;

#define BLK_NUM 10

k_mmblk_pool_t mmblk_pool;
uint8_t mmblk_pool_buffer[BLK_NUM * sizeof(blk_t)];

void task1_entry(void *arg)
{   
    blk_t *ptr = NULL;
    k_err_t err;
    
    /* 打印出一个块的大小 */
    printf("block size is %d bytes\r\n", sizeof(blk_t));
    
    /* 申请一个块 */
    err = tos_mmblk_alloc(&mmblk_pool, (void*)&ptr);
    if (err != K_ERR_NONE) {
        printf("a mmblk alloc fail, err = %d\r\n", err);
        return;
    }
    else {
        printf("a mmblk alloc success\r\n");
    }
    
    /* 使用该块 */
    ptr->id = 1;
    ptr->payload = "hello";
    printf("mmblk id:%d payload:%s\r\n", ptr->id, ptr->payload);
    
    /* 使用完毕之后释放 */
    err = tos_mmblk_free(&mmblk_pool, ptr);
    if (err != K_ERR_NONE) {
        printf("a mmblk free fail, err = %d\r\n", err);
        return;
    }
    else {
        printf("a mmblk free success\r\n");
    }
    
    while (1) {
        tos_task_delay(1000);
    }
}

运行结果如图:

5. 总结

本节主要讲述了使用malloc和free的缺点:申请时间未知,内存容易产生碎片。

所以在实时操作系统中诞生了动态堆管理机制和静态内存池管理机制,两种比较如下:

① 内存堆(mmheap)管理机制,每次可以申请和释放不定大小的内存,分配时间虽然不快,但是能保证已知;

② 静态内存池(mmblk)管理机制,每次只能申请和释放一个块(固定大小的内存),分配时间最快,没有碎片。

好啦~今天的文章就到这里,我是喜欢玩板子的Mculover666,下期文章再见!

接收更多精彩文章及资源推送,欢迎订阅我的微信公众号:『mculover666』。

Mculover666 CSDN认证博客专家 嵌入式软件开发 IoT全栈开发
CSDN博客专家,微信公众号mculover666,凭借与生俱来的热爱专注于嵌入式领域,在自己折腾的同时,以文字的方式分享所玩、所思、所想、所悟,作为一个技术人,我们一起前进~
©️2020 CSDN 皮肤主题: 成长之路 设计师:Amelia_0503 返回首页