本文基于 rthread 4.0.5版本
RT-Thread 的内存管理
RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况:
第一种是针对小内存块的分配管理(小内存管理算法);
第二种是针对大内存块的分配管理(slab 管理算法);
第三种是针对多内存堆的分配情况(memheap 管理算法)。
小内存管理算法是一个简单的内存分配算法。初始时,它是一块大的内存。当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来。
RT-Thread 的 slab 分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的 slab 分配器基础上,针对嵌入式系统优化的内存分配算法。最原始的 slab 算法是 Jeff Bonwick 为 Solaris 操作系统而引入的一种高效内核内存分配算法。
memheap 管理算法适用于系统含有多个地址可不连续的内存堆。使用 memheap 内存管理可以简化系统存在多个内存堆时的使用:当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的 memheap 初始化,并开启 memheap 功能就可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配。
在RT-Thread上如何查找内存泄漏
使用RTOS无法避免的会有内存泄漏,所以我们需要找一种手段来查找内存泄漏并修复
内存泄漏是什么
简单的来说内存泄漏就是应该释放的内存没有释放,那么我们回头来考虑在没有MMU的RTOS,由于分配的地址都是真实的物理地址,一段代码的所有内存都没有释放,系统的内存就会变得越来越少,最终没有内存可以使用。
最终的现象是在RT-Thread的msh上执行free命令可以看到系统没有可用的内存,效果如下:
total memory: 1048544
used memory : 1048534
maximum allocated memory: 1048534
除此之外还有一种泄漏,一段代码的部分内存没有释放,随着这块代码反复调用,虽然系统中可能有内存,但是内存区域被划分成了n个小块,如果这个时候分配一个大内存的区域就会分配时候,但是系统总内存是大于你需要分配的内存大小的。在RT-Thread的内存策略中只有连续的小块内存会在空闲线程中被整合成一块大内存,同时由于没有MMU所以无法利用物理与虚拟地址的转换在后台将小块内存自动整合成大内存。所以这种内存泄漏也是比较难查的问题之一,哪怕一段代码中分配了100块不同大小的内存,其中有一块没有释放也会导致系统没有大内存可用,只是时间问题。
根据上图 ,我们假设系统中虽然有400K的内存,但是这400K的内存可能是被分成了100块区域,最终的区域可能只有4K左右,那么这个时候我去分配8K 10K 20K都肯定是失败的,这种内存泄漏一般在设备运行的早期不会出现,随着运行的时候越长就越容易出现。
RT-Thread 上小内存管理算法就是前面一段内存管理块+真实用户可以使用的的内存区域,具体如下图所示:
既然每块内存都有管理块,那么我们就可用将内存的具体情况遍历打印出来。包括内存使用的情况和内存的大小。这样就可用使用打印信息协助我们某一块内存区域是否有内存泄漏,并能够看到实际的内存情况,并合理的调整内存分配的策略。
在RT-Thread的小内存管理算法中已经支持了该功能,在RT-Thread中该功能被称作memtrace,下图是memtrace的具体打印:
total memory: 1048544
used memory : 58772
maximum allocated memory: 63556
memory heap address:
heap_ptr: 0xc0060000
lfree : 0xc00606b0
heap_end: 0xc015fff0
--memory item information --
[0xc0060000 - 144] espC
[0xc00600a0 - 1K] espC
[0xc00606b0 - 2K]
[0xc00610b0 - 64] main
[0xc0061100 - 144] main
[0xc00611a0 - 2K] main
[0xc00619b0 - 12] main
[0xc00619cc - 108] main
[0xc0061a48 - 156] main
[0xc0061af4 - 20] main
[0xc0061b18 - 20] main
[0xc0061b3c - 16] main
[0xc0061b5c - 16] main
[0xc0061b7c - 36] main
[0xc0061bb0 - 12] main
[0xc0061bcc - 12] main
[0xc0061be8 - 1012] main
[0xc0061fec - 144] main
[0xc006208c - 1K] main
[0xc006249c - 24] main
[0xc00624c4 - 20] main
[0xc00624e8 - 44] main
[0xc0062524 - 68] main
[0xc0062578 - 76] main
[0xc00625d4 - 68] main
[0xc0062628 - 120] main
[0xc00626b0 - 64] main
[0xc0062700 - 144] main
[0xc00627a0 - 3K] main
[0xc0063768 - 144] main
[0xc0063808 - 1K] main
[0xc0063c18 - 144] main
[0xc0063cb8 - 4K] main
[0xc0064cc8 - 24] main
[0xc0064cf0 - 24] main
[0xc0064d18 - 144] main
[0xc0064db8 - 2K] main
[0xc00655c8 - 12] main
[0xc00655e4 - 12] main
[0xc0065600 - 532] main
[0xc0065824 - 144] main
[0xc00658c4 - 7K] main
[0xc0067874 - 144] main
[0xc0067914 - 2K] main
[0xc0068124 - 40] main
[0xc006815c - 2K]
[0xc0068a0c - 144] main
[0xc0068aac - 2K] main
[0xc00692bc - 144] main
[0xc006935c - 1K] main
[0xc006976c - 144] main
[0xc006980c - 1K] main
[0xc0069c1c - 360] ec20
[0xc0069d94 - 40] ec20
[0xc0069dcc - 8K] ec20
[0xc006bddc - 44] ec20
[0xc006be18 - 40] ec20
[0xc006be50 - 40] ec20
[0xc006be88 - 144] ec20
[0xc006bf28 - 1K] ec20
[0xc006c538 - 1012] ec20
[0xc006c93c - 360] espC
[0xc006cab4 - 40] espC
[0xc006caec - 44] espC
[0xc006cb28 - 8K] espC
[0xc006eb38 - 44] espC
[0xc006eb74 - 40] espC
[0xc006ebac - 40] espC
[0xc006ebe4 - 144] espC
[0xc006ec84 - 1K] espC
[0xc006f294 - 1012] espC
[0xc006f698 - 12] ec20
[0xc006f6b4 - 76] ec20
[0xc006f710 - 24] ec20
[0xc006f738 - 128] ec20
[0xc006f7c8 - 12]
[0xc006f7e4 - 16] espC
[0xc006f804 - 76] espC
[0xc006f860 - 961K]
以上打印使用memtrace功能打印了整个系统的当前内存情况。打印的每部分意义可以参考下图理解。
可以看到内存情况对修复系统内存泄漏有什么帮助呢?解决这个疑问之前我们需要了解下动态内存分配的特点:
- 系统启动常驻内存:一般系统初始化的时候会分配大量的内存,这部分会在系统运行的时候就创建伴随系统运行的整个生命周期;
- 系统当前正在运行内存:由于动态内存运行时创建的特点,所以系统在当前时间节点前后会存在一些正在使用的动态内存,但是这块内存在程序运行完毕后就会被释放,所以这块内存一般也不是内存泄漏;
- 系统运行中间时间节点内存:由于系统一般只会存在常驻内存和当前使用的动态内存,这种在系统运行中间时间节点内存出现的内存一般都很有可能是内存泄漏的段。但是也不一定就是内存泄漏,由于系统部分功能可能在运行了一段时间后才会初始化,这部分也可能算做常驻内存。
了解了动态内存分配的特点后,我们得出一些结论:
- 系统刚刚启动时分配的大部分内存一般不属于内存泄漏,哪怕是泄漏的,只要是不会重复有相同大小相同线程分配的,我们都可以不予处理,因为他最多泄漏一次对系统稳定性不会太大影响。
- 系统当前正在运行内存一般也不予考虑,这部分内存无法知道是否有泄漏;
- 系统运行中间时间节点内存需要保持高度怀疑,这个时间区域有相同线程和重复内存大小的反复出现一般都可能是内存泄漏;
- 整个系统中有间接性的使用的内存和没有使用的内存逐渐把内存分成n个小块,这种一般也是由于申请了一片内存有一个没有释放导致。
- 虽然是n个大小相同的内存且同一线程分配的内存也不一定是内存泄漏,这个时候还需要看这大小相同的且同一线程分配的的n个内存是否是同一时间分配,如果是同一时间分配一般都不是泄漏
根据以上规则可以定位到大部分内存泄漏,剩下的工作就是找到这些泄漏的内存是在哪里分配的了
在RT-Thread上使用memtrace功能
这里演示使用Small Memory Algorithm算法下的内存分配
配置宏启动
#define RT_USING_MEMTRACE
注意事项 thread名称只能保存前4个字符
发表回复