RTT学习笔记之内存管理(动态内存)

来源:互联网 发布:识别文字的软件 编辑:程序博客网 时间:2024/04/28 11:30
RTT学习笔记之内存管理(动态内存)

     动态内存管理是一个真实的堆内存管理模块,可以根据用户的需求(在当前资源满足的情况下)分配任意大小的内存块。 RT-Thread系统中为了满足不同的需求,提供了两套动态内存管理算法,分别是小堆内存管理和SLAB内存管理。下堆内存管理模块主要针对系统资源比较少,一般小于2M内存空间的系统;而SLAB内存管理模块则主要是在系统资源比较丰富时,提供了一种近似的内存池管理算法。两种内存管理模块在系统运行时只能选择其中之一(或者完全不使用动态堆内存管理器),两种动态内存管理模块API形式完全相同。

一、小内存管理模块
     小内存管理算法是一个简单的内存分配算法,当有可用内存的时候,会从中分割出一块来作为分配的内存,而余下的则返回到动态内存堆中。如下图所示:

     当用户线程要分配一个64字节的内存块时,空闲链表指针lfree初始指向0x0001EC00内存块,但此内存块只有32字节并不能满足要求,它会继续寻找下一内存块,此内存块大小为128字节满足分配的要求。分配器将把此内存块进行拆分,余下的内存块( 52字节)继续留在lfree链表中。如下图所示:

     在分配的内存块前约12字节会存放内存分配器管理用的私有数据,用户线程不应访问修改它,这个头的大小会根据配置的对齐字节数稍微有些差别。释放时则是相反的过程,但分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块。

二、SLAB内存管理模块
     RT-Thread 实现的SLAB分配器是在Matthew Dillon在DragonFly BSD中实现的SLAB分配器基础上针对嵌入式系统优化过的内存分配算法。原始的SLAB算法是Jeff Bonwick为 Solaris 操作系统首次引入的一种高效内核内存分配算法。RT-Thread的SLAB分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。 SLAB分配器会根据对象的类型(主要是大小)分成多个区( zone),也可以看成每类对象有一个内存池,如图所示:

     一个zone的大小在32k ~ 128k字节之间,分配器会在堆初始化时根据堆的大小自动调整。系统中最多包括72种对象的zone,最大能够分配16k的内存空间,如果超出了16k那么直接从页分配器中分配。每个zone上分配的内存块大小是固定的,能够分配相同大小内存块的zone会链接在一个链表中,而72种对象的zone链表则放在一个数组( zone arry)中统一管理。

动态内存分配器主要的两种操作:
  • 内存分配:假设分配一个32字节的内存,SLAB内存分配器会先按照32字节的值,从zone array链表表头数组中找到相应的zone链表。如果这个链表是空的,则向页分配器分配一个新的zone,然后从zone中返回第一个空闲内存块。如果链表非空,则这个zone链表 中的第一个zone节点必然有空闲块存在(否则它就不应该放在这个链表中),然后取相应的空闲块。如果分配完成后,导致一个zone中所有空闲内存块都使用完毕,那么分配器需要把这个zone节点从链表中删除。
  • 内存释放:分配器需要找到内存块所在的zone节点,然后把内存块链接到zone的空闲内存块链表中。如果此时zone的空闲链表指示出zone的所有内存块都已经释放,即zone是完全空闲的zone。当中zone链表中,全空闲zone达到一定数目后,会把这个全空闲的zone释放到页面分配器中去。
三、动态内存接口
// 动态内存控制块:#define HEAP_MAGIC 0x1ea0struct heap_mem{    /* magic and used flag */    rt_uint16_t magic;//  如果此内存块被分配了,则置0x1ea0    rt_uint16_t used; // 0:未分配,1:已分配    rt_size_t next, prev;// 前一内存块,后一内存块};// 相关接口:void rt_system_heap_init(void *begin_addr, void *end_addr);// 初始化系统堆空间void *rt_malloc(rt_size_t size);// 分配内存块void *rt_realloc(void *rmem, rt_size_t newsize);// 重新分配内存块void *rt_calloc(rt_size_t count, rt_size_t size);// 分配多内存块void rt_free(void *rmem);// 释放内存块
四、简单示例
#include <rtthread.h>#include "finsh.h"struct rt_thread thread1;char thread1_stack[512];void thread1_entry(void* parameter){     int i;    // 申请一个字符型指针数组用于存放申请到的内存地址     char *ptr[20];    // 初始化指针数组为空     memset(ptr, 0, sizeof(char*)*20);     while(1)     {          for (i = 0; i < 20; i++)          {               // 每次分配(1 << i)大小字节数的内存空间               ptr[i] = rt_malloc(1 << i);               if (ptr[i] != RT_NULL)               {                    rt_kprintf("get memory: 0x%x\n", ptr[i]);                // 申请成功后释放已申请到的内存块                    rt_free(ptr[i]);                // 将指针赋值为空                    ptr[i] = RT_NULL;               }          }     }}int rt_mem_set_init(){     rt_thread_init(&thread1, "thread1", thread1_entry, RT_NULL, &thread1_stack[0], sizeof(thread1_stack), 20, 10);     rt_thread_startup(&thread1);     return 0;}FINSH_FUNCTION_EXPORT(rt_mem_set_init, a Memory test example);
五、示例说明及运行结果
     首先在startup.c文件中会运行rt_system_heap_init(__segment_end("HEAP"), (void*)STM32_SRAM_END);来进行堆的初始化,计算出可用内存数量并初始化堆头控制块和堆尾控制块,通过 rt_malloc(1 << i);来申请内存放到ptr的指针数组中,申请时会对传入的申请大小进行对齐并判断,最小申请内存大小为12字节,用来存放内存分配器管理用的私有数据。通过不断查找链表来找到满足要求的空闲内存,找到后将内存地址返回,没有则返回空。
     申请成功后打印所申请到的内存地址,然后进行内存释放,释放时操作类似,释放过程中需要判断前一块和后裔块内存是否为空闲,如果为空闲则进行合并操作。

0 0
原创粉丝点击