一、动态内存管理
动态内存管理是一个真实的堆(Heap)内存管理模块,可以在当前资源满足的情况下,根据用户的需求分配任意大小的内存块。而当用户不需要再使用这些内存块时,又可以释放回堆中供其他应用分配使用。RT-Thread系统为了满足不同的需求,提供了两套不同的动态内存管理算法,分别是小内存管理算法和SLAB内存管理算法。小堆内存管理模块主要针对系统资源比较少,一般用于小于2M内存空间的系统;而SLAB内存管理模块则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。
两种内存管理模块在系统运行时只能选择其中之一或者完全不使用动态堆内存管理器。这两种管理模块提供的API接口完全相同。因为动态内存管理器要满足多线程情况下的安全分配,会考虑多线程间的互斥问题,所以请不要在中断服务例程中分配或释放动态内存块。因为它可能会引起当前上下文被挂起等待。
二、小内存管理算法
本文主要介绍小内存管理算法,至于SLAB内存管理算法则在后续文章中介绍。小内存管理算法是一个简单的内存分配算法。初始时,它是一块大的内存。当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来,如 内存块链表图所示:
内存管理的在表现主要体现在内存的分配与释放上,小型内存管理算法可以用以下例子体现出来。
空闲链表指针lfree初始指向32字节的内存块。当用户线程要再分配一个64字节的内存块时,但此lfree指针指向的内存块只有32字节并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128字节时,它满足分配的要求。因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块(52字节)继续留在lfree链表中。另外,在每次分配内存块前,都会留出12字节数据头用于magic,used信息及链表节点使用。返回给应用的地址实际上是这块内存块12字节以后的地址,前面的12字节数据头是用户永远不应该碰的部分。(注:12字节数据头长度会与系统对齐差异而有所不同)。释放时则是相反的过程,但分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块。
数据结构:在src/mem.c中
#define HEAP_MAGIC 0x1ea0
struct heap_mem
{
/* magic and used flag */
rt_uint16_t magic; //如果此内存块被分配了,则(在rt_malloc中设置)置0x1ea0。标志该内存块为一个内存管理系统使用的动态内存数据块,类似于一个内存保护字:如果这个区域被改写,那么也就意味着这块内存块被非法改写 (正常情况下只有内存管理系统才会访问它)
rt_uint16_t used; //0:未分配;1:已分配
rt_size_t next, prev; //后一内存块首地址(包含内存块控制结构),前一内存块首地址(包含内存块控制结构)。注意这里不再是rt_list_t类型(链表类型),它们直接赋值为内存地址
};
三、小内存管理算法函数接口:在src/mem.c中
初始化动态内存堆:
void rt_system_heap_init(void *begin_addr, void *end_addr);
在使用堆内存RT_USING_HEAP时,必须要在系统初始化的时候进行堆内存的初始化。这个函数会把参数begin_addr,end_addr区域的内存空间作为内存堆来使用。
由源代码可知,初始化时小内存管理算法通过传进来的起始地址和末尾地址将动态堆内存初始化为两个内存块:第一个内存块指向动态堆内存首地址,可用空间为整个可分配的内存(不包含两个内存控制块本身所占大小,即减去24字节),此内存块下一指针指向末尾内存控制块;第二个内存块指向最末尾的一个内存控制块,可用空间大小为0,此内存块前一指针和后一指针都指向本身。
分配内存块:
void *rt_malloc(rt_size_t size);
rt_malloc函数会从系统堆空间中找到合适大小的内存块,然后把内存块可用地址返回给用户。
重分配内存块:
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);
用户线程使用完从内存分配器中申请的内存后,必须及时释放,否则会造成内存泄漏,rt_free函数会把待释放的内存换回给堆管理器中。在调用这个函数时用户需传递待释放的内存块指针,如果是空指针直接返回。
内存合并:
static void plug_holes(struct heap_mem *mem);
此函数在重分配内存时调用,将分割出来的后一部分尝试与前后内存块合并;当释放内存时,算法将检查待释放内存的前一内存块和后一内存块,如果为空闲则合并。
四、算法总结
小内存管理算法从整体上来讲,是将一片内存初始化为静态链表来实现的,初始化时只有两个内存块:
第一块除了包含内存块控制块(占用12字节)外,还包含待分配的空间,这个空间就是mem_size_aligned,它是指此算法可用来作分配的动态堆内存总大小,任何待分配的内存都不能比它还大,否则超过极限;
第二块只包含内存控制块本身(占用12字节),不包含待分配的空间,它作为链表尾,且设置使用标志used为1(永久使用)。
整个算法中还有一空闲指针lfree,始终指向动态内存堆中剩余可用空间的第一个空闲内存块,初始化时指向动态内存堆起始地址,该指针变量在分配内存和合并内存时会不断更新。接下来就是分配内存了,分配内存时首先从空闲内存所指向的节点开始扫描,一旦扫描到大小满足的节点,则返回此节点,若此节点所指向的空间足够大,大到还有足够空间分配另一内存块(只含内存块控制结构)时,则分割此节点指向的内存为两块,前一块内存可用空间user data首地址返回,后一块内存设置为未分配(空闲内存); 当释放内存时,算法将检查待释放内存的前一内存块和后一内存块,如果为空闲则合并。