在计算系统中,通常存储空间可以分为两种:内部存储空间和外部存储空间。
内部存储空间通常访问速度比较快,能够按照变量地址随机访问,也就是我们通常所说的RAM(随机存储器),可以把它理解为电脑的内存。
外部存储空间内所保存的内容相对来说比较固定,即使掉电后数据也不会丢失,也就是通常所讲的ROM(只读存储器),可以把它理解为电脑的硬盘。
计算机系统中,变量、中间数据一般存放在RAM中,只有在实际使用时才将它们从RAM调入到CPU中进行运算。
一些数据需要的内存大小需要在程序运行过程中根据实际情况确定,这就要求系统具有对内存空间进行动态管理的能力,在用户需要一段内存空间时,向系统申请,系统选择一段合适的内存空间分配给用户,用户使用完毕后,再释放回系统,以便系统将该段内存空间回收再利用。
由于实时系统中对时间的要求非常严格,内存管理往往要比通用操作系统要求苛刻得多:
RT-Thread操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。
总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况:
内存堆管理用于管理一段连续的内存空间。

RT-Thread将“ZI段结尾处”到内存尾部的空间用作内存堆。
内存堆可以在当前资源满足的情况下,根据用户的需求分配任意大小的内存块。
而当用户不需要再使用这些内存块时,又可以释放回堆中共其它应用分配使用。
小内存管理算法主要针对系统资源比较少,一般用于小于2MB内存空间的系统。
小内存管理算法是一个简单的内存分配算法。
初始时,它是一块大的内存。当需要分配内存块时,将这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来。

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

空闲链表指针lfree初始指向32字节的内存块。当用户线程要求再分配一个64字节的内存块时,但此lfree指针指向的内存块只有32字节,并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128 字节时,它满足分配的要求。
因为这个内存块比较大,分配器将此内存块进行拆分,余下的内存块(52字节)继续留在lfree链表中。
另外,在每次分配内存块前,都会留出 12 字节数据头用于 magic、used 信息及链表节点使用。返回给应用的地址实际上是这块内存块 12 字节以后的地址,前面的 12 字节数据头是用户永远不应该碰的部分(注:12 字节数据头长度会与系统对齐差异而有所不同)。
释放时则是相反的过程,但分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块。
在使用内存堆时,必须要在系统初始化的时候进行堆的初始化,可以通过下面的函数接口完成:
void rt_system_heap_init(void* begin_addr, void* end_addr);
这个函数会把参数 begin_addr,end_addr 区域的内存空间作为内存堆来使用。
void *rt_malloc(rt_size_t nbytes);
rt_malloc函数会从系统堆空间中找到合适大小的内存块,然后把内存块可用地址返回给用户。
对rt_malloc的返回值进行判空是非常有必要的。
应用程序使用完从内存分配其中申请的内存后,必须及时释放,否则会造成内存泄漏。
int *pi;
pi = rt_malloc(100);
if(pi == NULL)
{
rt_kprintf("malloc failed\r\n");
}
rt_free函数会把释放内存还给堆管理器中。
在调用这个函数时用户需传递待释放的内存块指针。
在已分配内存块的基础上重新分配内存块的大小(增加或缩小),可以通过下面的函数接口完成:
void *rt_realloc(void *rmem,rt_size_t newsize);
在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断)。
从内存堆中分配连续内存地址的多个内存块。
void *rt_calloc(rt_size_t count, rt_size_t size);
在分配内存块过程中,用户可设置一个钩子函数,调用的函数接口如下:
void rt_malloc_sethook(void (*hook)(void *ptr, rt_size_t size));
设置的钩子函数会在内存分配完成后进行回调。
回调时,会把分配到的内存块地址和大小作为入口参数传递进去。
在释放内存时,用户可设置一个钩子函数,调用的函数接口如下:
void rt_free_sethook(void (*hook)(void *ptr));
设置的钩子函数会在调用内存释放完成前进行回调。回调时,释放的内存块地址会作为入口参数传递进去(此时内存块并没有被释放)。
动态内存使用总结:
常见的动态内存错误:
内存碎片:频繁地调用内存分配和释放接口会导致内存碎片,一个避免内存碎片的策略是使用内存池+内存堆混用的方法。