内存是计算机中十分重要的资源。随着芯片性能的提升,容量的变大,内存资源的管理显得非常重要。内存管理是操作系统中一个基本功能,一般操作系统的功能可以概括为五个部分:处理器管理、内存管理、任务管理、I/O设备管理、文件管理。对于嵌入式操作系统,一个好的内存管理策略,将大大提高系统的性能,对系统稳定性也至关重要。
本文主要从RT-Thread的内存管理策略的角度出发,梳理一下目前RT-Thread系统中的内存管理,同时从实际应用的角度出发,选择合适的方案进行内存管理。
一般来说,静态内存就是系统在启动之前,已经获得了系统运行的所有内存。不需要在程序运行的时候,另外进行内存的分配。这种方式使用内存,一般实时性比较强,省去了单独分配内存的时间。但是由于是静态内存,使用的效率比较低下,不用单独进行管理。
动态内存就是通过分配(malloc)和释放(free)对内存进行一定的管理,这种内存的使用效率比较高,但反复的申请与释放,容易产生内存碎片的问题。
对于RT-Thread的内存管理方式,主要从小内存管理、slab、memheap以及内存池这四种内存管理策略上去分析对比,从而选择最佳的内存管理策略。
rt-thread的小内存管理算法是一种比较简单的内存分配管理算法。应用的场景在内存在1MB以下使用比较合适。其原理是当需要分配时,从内存堆上分配出一块内存供系统使用,后面需要的时候,接着从后面空闲的内存区域进行分配。
当调用rt_system_heap_init(RT_HW_HEAP_BEGIN, RT_HW_HEAP_END);
会分配出一段堆空间,其中lfree指向这段堆空间的头部位置。
此时分配一个32字节的数据,会首先申请一个内存的头部信息,这个头部信息为12字节。
heap_mem一般为12字节。
#define HEAP_MAGIC 0x1ea0
struct heap_mem
{
/* magic and used flag */
rt_uint16_t magic;
rt_uint16_t used;
#ifdef ARCH_CPU_64BIT
rt_uint32_t resv;
#endif
rt_size_t next, prev;
#ifdef RT_USING_MEMTRACE
#ifdef ARCH_CPU_64BIT
rt_uint8_t thread[8];
#else
rt_uint8_t thread[4]; /* thread name */
#endif
#endif
};
其中magic为魔数,为0x1ea0
,英文为heap
。rt_size_t
数据类型为rt_ubase_t
。在32位程序中为4字节。所以合起来,头部字节的大小为12字节。此时依次分配50字节和64字节。内存布局如下所示:
由于lfree永远指向第一个空闲的内存的起始地址,所以此时lfree在为分配的内存块后面。依次向后去分配合适的内存块大小。
此时如果释放了一个50字节的内存块。此时的内存布局如下:
由于空间被释放,所以导致内存的中间有一块空闲的区域。此时如果要申请25字节,那么可以在空闲的这个区域里面找到。但是如果此时申请100字节的时候,那么导致空闲的区域的内存不够了,接着向后面去找空闲的大内存片段。
此时,会向后面的大区域去申请内存块,导致前面的内存区域被空出来了。如果在内存中间有很多这种小的空余的片段出现,那么一定会导致内存碎片的问题。
这种方式内存使用的比较简单,适合内存比较小的情况下使用,并且操作内存的申请和释放不要太频繁的情况下去使用比较理想。一般在内存为1M以下的情况下使用这种算法比较理想。
RT-Thread 的 slab 分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的 slab 分配器基础上,针对嵌入式系统优化的内存分配算法。最原始的 slab 算法是 Jeff Bonwick 为 Solaris 操作系统而引入的一种高效内核内存分配算法。
RT-Thread 的 slab 分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。
在理解slab分配器的时候,需要理解两个概念:ZONE、CHUNK。
CHUNK:
chunk是一段固定大小的空间,一般来说,slab分配器是支持72种不同大小的chunk。数据量分别如下:
8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128、144、160、176、192、208、224、240、256、288、320、352、384、416、448、480、512、576、640、704、770、834、898、962、1024、1152、1280、1408、1536、1664、1792、1920、2048、2304、2560、2816、3072、3328、3584、3840、4096、4608、5120、5632、6144、6656、7168、7680、8192、9216、10240、11264、12288、13312、14336、15360、16384
一般来说,slab分配器只会去寻找上述可以分配的空间,如果分配的chunk大小不相等,那么会通过zoneindex函数去找到最合适的可以分配的内存的chunk大小。
ZONE:
每个zone有范围从32k~128K的字节,每个zone里都包含相同大小的chunk。
#define ZALLOC_MIN_ZONE_SIZE (32 * 1024) /* minimum zone size */
#define ZALLOC_MAX_ZONE_SIZE (128 * 1024) /* maximum zone size */
一般来说,在初始化的时候,进行void rt_system_heap_init(void *begin_addr, void *end_addr)
操作的时候,会根据给系统的heap空间,动态的调整zone的大小。
/* calculate zone size */
zone_size = ZALLOC_MIN_ZONE_SIZE;//32K
while (zone_size < ZALLOC_MAX_ZONE_SIZE && (zone_size << 1) < (limsize / 1024))
zone_size <<= 1;
根据上述的调整方式,可以算出,当heap空间在64M及以下时,zone的大小都为32K。当系统的heap空间在256M及以上时,zone的大小为最大128K。这个值在两者中间时是动态进行调整的。
按照上述的介绍,可以看出这两者的关系如图所示:
采用slab算法的时候,主要有两种情况:
1.底层的页分配器
2.上层的slab分配器
其中页分配器是用page分配的一个片段。在宏定义中RT_MM_PAGE_SIZE
表示page大小,一般定义的大小为4K。
初始化的时候,会将zone_array[NZONES]会被置为空,表示没有可以用的zone。例如此时申请一个30字节的数据空间。当调用rt_malloc(30),那么会通过zoneindex函数找到一个合适的32字节的chunk。找到zone的下标为3,查询到zone_array[3]为空。
(1)当发现zone_array[3]为空时,会通过rt_page_alloc
去分配page。假如动态调整的zone大小为128K。需要分配出32个page,一个page的大小为4K。32*4K=128K。
(2)此时当前zone中的内存布局如下:
由于第一次分配时,可以分配到32字节的chunk1,并标记chunk1被使用,然后下次申请符合这个chunk条件的时候直接可以申请到chunk2。如果中间被释放了,那么就可以被其他申请到了。
如果该zone上的所有的chunk都申请完了,那么可以申请同等类型的zone,继续这样操作。
使用zone管理,可以从一定意义上避免内存片段的问题,但是内存管理起来比较的麻烦,比较耗时。适合内存比较大,并且需要频繁申请和释放内存的时候使用。
memheap 管理算法适用于系统含有多个地址可不连续的内存堆。使用 memheap 内存管理可以简化系统存在多个内存堆时的使用:当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的 memheap 初始化,并开启 memheap 功能就可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配。
这种内存分配的特点在于可以把不连续的空间变得连续起来。具体的使用方法就是当开启了RT_USING_MEMHEAP_AS_HEAP
这个宏定义,第一次需要使用rt_system_heap_init
分配堆空间,然后需要用户定义一个struct rt_memheap
结构体静态变量,然后再使用rt_memheap_init
函数初始化。这样可以把两端不连续的空间作为一块连续的空间来管理。具体来说,比如sram与sdram的地址管理。
关于内存的管理部分,可以查看小内存管理部分。
在使用操作系统做业务逻辑时,往往有这样一种需求。比如做通信协议,或者使用动态内存比较多的情况。这时候可以在内存堆上单独分配一块内存作为具体业务逻辑的内存池使用。
做内存管理的时候,调用下面函数去创建一个内存池。
rt_mp_t rt_mp_create(const char* name,
rt_size_t block_count,
rt_size_t block_size);
内存池中会分配出一些列的内存块。当实际使用时,如果申请内存池中的数据申请不到了,那么无法分配,会使得线程挂起,等待释放内存池中的数据块时,会得到内存块。
采用内存池的好处是自己的任务用自己的内存,不会造成频繁分配释放造成的内存碎片问题。
内存管理是操作系统中比较重要的一个部分,使用得当,会大大提高系统软件的质量。rt-thread提供的小内存管理、slab、memheap、内存池这些内存管理策略,一方面针对不同大小,不同用途的内存做了区分,可以选择合适的内存管理策略对内存进行使用。想要使用好rtos操作系统,需要不断的思考内存的使用方式,静态内存、动态内存、全局变量、局部变量、临界区等等、在注意这些问题的条件下去做设计,才能设计出好的系统架构,才能做出更好的稳定可靠的产品。
- END -