发 帖  
原厂入驻New
[原创] 灵动微课堂 (第134讲) | 基于MM32 MCU的OS移植与应用——RT-Thread 内存管理
2020-8-28 16:52:11  155
分享
MCU内置的FLASH和SRAM通常都较小,RT-Thread实时操作系统由于本身需要占用一定的存储单元加上用户代码,因此在应用中如何合理的利用、分配好内存资源尤为重要,在本章节中将于大家一起学习RT-Thread的内存管理。

在RT-Thread官网有介绍两种内存管理方式:动态内存堆管理和静态内存池管理,有兴趣深入了解可以登录RT-Thread官网(https://www.rt-thread.org/document/site/programming-manual/memory/memory/)研究。

在本章节中我们主要讲解在MM32 MCU上使用RT-Thread操作系统如何进行内存管理。


01
  概 述  



RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况:


针对小内存块的分配管理(小内存管理算法)

针对大内存块的分配管理(slab管理算法)

针对多内存堆的分配情况(memheap管理算法)

内存堆可以在当前资源满足的情况下,根据用户的需求分配任意大小的内存块。而当用户不需要再使用这些内存块时,又可以释放回堆中供其他应用分配使用。RT-Thread系统为了满足不同的需求,提供了不同的内存管理算法,分别是小内存管理算法、slab管理算法和memheap管理算法

内存堆管理用于管理一段连续的内存空间,如下图所示,RT-Thread将 “ZI 段结尾处” 到内存尾部的空间用作内存堆。

5.jpg 图1 内存空间示意图


小内存管理算法
小内存管理算法主要针对系统资源比较少,一般用于小于2MB内存空间的系统。

slab内存管理算法
slab内存管理算法则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。

memheap管理算法
RT-Thread还有一种针对多内存堆的管理算法,即memheap管理算法。memheap方法适用于系统存在多个内存堆的情况,它可以将多个内存 “粘贴” 在一起,形成一个大的内存堆。


注意
因为内存堆管理器要满足多线程情况下的安全分配,会考虑多线程间的互斥问题,所以不要在中断服务例程中分配或释放动态内存块,因为它可能会引起当前上下文被挂起等待。

针对灵动的MCU,较为适用小内存的内存堆应用,以下介绍小内存管理算法;

小内存管理算法


初始时,它是一块大的内存。当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来,如图所示:
4.jpg 图2 小内存管理算法链表结构示意图

每个内存块(不管是已分配的内存块还是空闲的内存块)都包含一个数据头,其中包括:

magic
变数(或称为幻数),它会被初始化成 0x1ea0(即英文单词 heap),用于标记这个内存块是一个内存管理用的内存数据块;变数不仅仅用于标识这个数据块是一个内存管理用的内存数据块,实质也是一个内存保护字:如果这个区域被改写,那么也就意味着这块内存块被非法改写(正常情况下只有内存管理器才会去碰这块内存)。

used
指示出当前内存块是否已经分配。

next
指向当前内存块的下一个内存块。

prev
指向当前内存块的上一个内存块。


注意
事实上,next与prev这两个字段保存的是目的地址的一个偏移量,偏移的基地址是整个内存堆空间的起始地址。

内存管理的表现主要体现在内存的分配与释放上,小型内存管理算法可以用以下例子体现出来。

如下图所示的内存分配情况,空闲链表指针 lfree 初始指向 32 字节的内存块。当用户线程要再分配一个 64 字节的内存块时,但此 lfree 指针指向的内存块只有 32 字节并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128 字节时,它满足分配的要求。因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块(52 字节)继续留在 lfree 链表中,如下图分配 64 字节后的链表结构所示。
3.jpg 图3 小内存管理算法链表结构示意图
2.jpg 图4 小内存管理算法链表结构示意图
另外,在每次分配内存块前,都会留出 12 字节数据头用于 magic、used 信息及链表节点使用。返回给应用的地址实际上是这块内存块 12 字节以后的地址,前面的 12 字节数据头是用户永远不应该碰的部分(注:12 字节数据头长度会与系统对齐差异而有所不同)。

释放时则是相反的过程,但分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块。



02
数据结构描述



下面代码是rtthread中对内存块的结构描述:

#define MIN_SIZE 12                   //内存申请分配空间的最小值
#define HEAP_MAGIC 0x1ea0             //变数

struct heap_mem
{
    /* magic and used flag */
    rt_uint16_t magic;
    rt_uint16_t used;
    rt_size_t next, prev;
};


内存分配过程:
设定
空闲链表指针IFree初始指向32字节的内存块,当用户线程需要分配一个64字节的内存块时,Ifree指针指向的内存块不能满足要求,内存管理器就会继续寻找下一个内存块,当找到时(128字节),就会进行内存分配,如果内存块比较大,分配器就会把内存块进行拆分,余下的内存(52字节)继续留在Ifree链表中。


注意
在内次分配内存块前,都会留出12字节用于magic、used信息及节点使用,返回给应用的地址实际上是这块内存块 12 字节以后的地址,前面的 12 字节数据头是用户永远不应该碰的部分(注:12 字节数据头长度会与系统对齐差异而有所不同)。

释放
释放内存时,分配器会查看前后相邻的内存是否是空闲,如果空闲,就回合并成一个大的空闲内存块。



03
  函数实现  



我们需要调用的内存堆管理相关的系统函数有5个,如下所示:

//在使用内存分配之前,该函数必须被调用,完成内存堆的初始化。
void rt_system_heap_init(void *begin_addr, void *end_addr);
参数
描述
begin_addr
堆内存区域起始地址
end_addr
堆内存区域结束地址

//内存分配函数
void *rt_malloc(rt_size_t size);
参数
描述
nbytes
需要分配的内存块的大小,单位为字节
返回
——
分配的内存块地址
成功
RT_NULL
失败

//在已分配内存块的基础上重新分配内存块的大小(增加或缩小)
void *rt_realloc(void *rmem, rt_size_t newsize);
参数
描述
rmem
指向已分配的内存块
newsize
重新分配的内存大小
返回
——
重新分配的内存块地址
成功

//从内存堆中分配连续内存地址的多个内存块            
void *rt_calloc(rt_size_t count, rt_size_t size);  
参数
描述
count
内存块数量
size
内存块容量
返回
堆内存区域结束地址
指向第一个内存块地址的指针
成功 ,并且所有分配的内存块都被初始化成零
RT_NULL
分配失败

//内存释放函数
void rt_free(void *rmem);
参数
描述
ptr
待释放的内存块指针

在分配内存块过程中,用户可设置一个钩子函数,置的钩子函数会在内存分配完成后进行回调。回调时,会把分配到的内存块地址和大小做为入口参数传递进去。RT-Thread提供了分配钩子函数、释放内存等函数接口

在释放内存中,会有一个内存节点合并功能函数,被rt_free调用,系统编程无需关心。
//内存释放时节点合并
static void plug_holes(struct heap_mem *mem);


04
代码实现



下面我们来看一下基于MM32L062小内存分配算法代码实现创建一个动态的线程,这个线程会动态申请内存并释放,每次申请更大的内存,当申请不到的时候就结束;
#include <rtthread.h>

#define THREAD_PRIORITY      25
#define THREAD_STACK_SIZE    512
#define THREAD_TIMESLICE     5

/* 线程入口 */
void thread1_entry(void *parameter)
{
    int i;
    char *ptr = RT_NULL; /* 内存块的指针 */

    for (i = 0; ; i++)
    {
        /* 每次分配 (1 << i) 大小字节数的内存空间 */
        ptr = rt_malloc(1 << i);

        /* 如果分配成功 */
        if (ptr != RT_NULL)
        {
            rt_kprintf("get memory :%d byte\n", (1 << i));
            /* 释放内存块 */
            rt_free(ptr);
            rt_kprintf("free memory :%d byte\n", (1 << i));
            ptr = RT_NULL;
        }
        else
        {
            rt_kprintf("try to get %d byte memory faiLED!\n", (1 << i));
            return;
        }
    }
}

int dynmem_sample(void)
{
    rt_thread_t tid = RT_NULL;

    /* 创建线程 1 */
    tid = rt_thread_create("thread1",
                           thread1_entry, RT_NULL,
                           THREAD_STACK_SIZE,
                           THREAD_PRIORITY,
                           THREAD_TIMESLICE);
    if (tid != RT_NULL)
        rt_thread_startup(tid);

    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(dynmem_sample, dynmem sample);




2020灵动MM32协作大会

2020年9月10日 9:00
深圳星河丽思卡尔顿酒店
“2020灵动MM32协作大会”重磅开启!
大会内容:
新品发布 技术分享
市场分析行业专家热点剖析
合作伙伴现场秀、互动小游戏
热销“MM32 INSIDE”应用案例展演等

更多精彩等着您,欢迎扫码报名参会!
1.jpg


0
2020-8-28 16:52:11   评论 分享淘帖

只有小组成员才能发言,加入小组>>

1514个成员聚集在这个小组

加入小组

创建小组步骤

关闭

站长推荐 上一条 /7 下一条

快速回复 返回顶部 返回列表