作者:liu2guang
上期回顾:嵌入式中是否应该使用动态内存?
之前探讨了关于动态内存的东西,那么我们回到本次的主题内存泄漏。笔者前面说到使用RTOS无法避免的会有内存泄漏,所以我们需要找一种手段来查找内存泄漏并修复。笔者多次提到了内存泄漏,那内存泄漏到底是什么呢?
什么是内存泄漏
1
简单的来说内存泄漏就是应该释放的内存没有释放,那么我们回头来考虑在没有MMU的RTOS,由于分配的地址都是真实的物理地址,一段代码的所有内存都没有释放,系统的内存就会变得越来越少,最终没有内存可以使用。
最终的现象是在RT-Thread的msh上执行free命令可以看到系统没有可用的内存,效果如下:
1memheap pool size max used size available size
2-------- ---------- ------------- --------------
3sram1 131072 131000 72
4heap 413872 413000 872
除此之外还有一种泄漏,一段代码的部分内存没有释放,随着这块代码反复调用,内存区域被划分成了n个小块。通过free命令可以看到系统中可能还有很多内存,但是这个时候分配一个大内存的区域就可能会分配失败,因为虽然系统总内存是大于你需要分配的内存大小的,但是最大的连续块缺比你需要分配的小,这种情况也被叫做内存碎片。在RT-Thread的内存策略中只有连续的小块内存会在空闲线程中被整合成一块大内存,同时由于没有MMU所以无法利用物理与虚拟地址的转换在后台将小块内存自动整合成大内存。所以这种内存泄漏也是比较难查的问题之一,哪怕一段代码中分配了100块不同大小的内存,其中有一块没有释放也会导致系统没有大内存可用,只是时间问题。
1memheap pool size max used size available size
2-------- ---------- ------------- --------------
3sram1 131072 31000 100072
4heap 413872 313000 300872
根据上图我们可以知道,系统中虽然有400K的内存,但是这400K的内存可能是被分成了100块区域,最终的区域可能只有4K左右,那么这个时候我去分配8K 10K 20K都肯定是失败的,这种内存泄漏一般在设备运行的早期不会出现,随着运行的时候越长就越容易出现。
小内存管理算法
2
在了解了内存泄漏的原理后,我们在回想一下在上一篇文章中笔者在内存管理那一块说到的小内存管理算法。其实在 RT-Thread 上小内存管理算法就是前面一段内存管理块+真实用户可以使用的的内存区域,具体如下图所示:
既然每块内存都有管理块,那么我们就可用将内存的具体情况打印出来。包括内存使用的情况和内存的大小。这样就可用使用打印信息协助我们某一块内存区域是否有内存泄漏,并能够看到实际的内存情况,并合理的调整内存分配的策略。
在RT-Thread的小内存管理算法中已经支持了该功能,在RT-Thread中该功能被称作memtrace,下图是memtrace的具体打印:
1memory information
2[0x009000a4 - 20 00000000] NONE : USED
3[0x009000dc - 64 00000000] NONE : USED
4[0x00900140 - 20 00000000] NONE : USED
5[0x00900178 - 128 00000000] NONE : USED
6[0x0090021c - 20 00000000] NONE : USED
7[0x00900254 - 64 00000000] NONE : USED
8[0x009002b8 - 20 00000000] NONE : USED
9[0x009002f0 - 16 00000001] main : USED
10[0x00900324 - 108 00000001] main : USED
11[0x009003b4 - 116 00000001] main : USED
12[0x0090044c - 68 00000225] main : USED
13[0x009004b4 - 12 00000225] main : USED
14[0x009004e4 - 100 00000225] main : USED
15[0x0090056c - 12 00000225] main : USED
16[0x0090059c - 2K 00000227] main : USED
17[0x00900dc0 - 28 00000227] main : USED
18[0x00900e00 - 80 00000227] main : USED
19[0x00900e74 - 5K 00000233] main : USED
20[0x00902298 - 72 00000233] main : USED
21[0x00902304 - 72 00000240] main : USED
22[0x00902370 - 428 00000298] main : USED
23[0x00902540 - 16 00000298] main : USED
24[0x00902574 - 32 00000298] main : USED
25[0x009025b8 - 12 00000298] : FREED
26[0x009025e8 - 12 00000298] main : USED
27[0x00902618 - 2K 00000298] main : USED
28[0x00902e48 - 12 00000298] : FREED
29[0x00902e78 - 12 00000298] main : USED
30[0x00902ea8 - 12 00000298] main : USED
31[0x00902ed8 - 12 00000298] main : USED
32[0x00902f08 - 520 00000298] main : USED
33[0x00903134 - 12 00000304] main : USED
34[0x00903164 - 12 00000322] main : USED
35[0x00903194 - 4K 00000322] main : USED
36[0x009041f4 - 16 00000355] init_thr: USED
37[0x00904228 - 80 00000355] init_thr: USED
38[0x0090429c - 16 00000355] main : USED
39[0x009042d0 - 1K 00000355] main : USED
40[0x009047f4 - 16K 00000355] main : USED
41[0x00908868 - 128K 00000355] main : USED
42[0x0092888c - 48 00000355] : USED
43[0x009288e0 - 16 00000355] main : USED
44[0x00928914 - 120 00000361] : USED
45[0x009289b0 - 40 00000361] main : USED
46[0x009289fc - 120 00000361] : USED
47[0x00928a98 - 36 00000411] user mai: USED
48[0x00928ae0 - 16 00000411] user mai: USED
49[0x00928b14 - 40 00000411] user mai: USED
50[0x00928b60 - 93K 00000429] : FREED
以上打印使用memtrace功能打印了整个系统的当前内存情况。打印的每部分意义可以参考下图理解。
如何找到内存泄漏
3
那么有同学有疑问了,我们可以看到内存情况对修复系统内存泄漏有什么帮助呢?解决这个疑问之前我们需要了解下动态内存分配的特点:
1. 系统启动常驻内存:一般系统初始化的时候会分配大量的内存,这部分会在系统运行的时候就创建伴随系统运行的整个生命周期;
2. 系统当前正在运行内存:由于动态内存运行时创建的特点,所以系统在当前时间节点前后会存在一些正在使用的动态内存,但是这块内存在程序运行完毕后就会被释放,所以这块内存一般也不是内存泄漏;
3. 系统运行中间时间节点内存:由于系统一般只会存在常驻内存和当前使用的动态内存,这种在系统运行中间时间节点内存出现的内存一般都很有可能是内存泄漏的段。但是也不一定就是内存泄漏,由于系统部分功能可能在运行了一段时间后才会初始化,这部分也可能算做常驻内存。
了解了动态内存分配的特点后,我们得出一些结论:
1. 系统刚刚启动时分配的大部分内存一般不属于内存泄漏,哪怕是泄漏的,只要是不会重复有相同大小相同线程分配的,我们都可以不予处理,因为他最多泄漏一次对系统稳定性不会太大影响。
2. 系统当前正在运行内存一般也不予考虑,这部分内存无法知道是否有泄漏;
3. 系统运行中间时间节点内存需要保持高度怀疑,这个时间区域有相同线程和重复内存大小的反复出现一般都可能是内存泄漏;
4. 整个系统中有间接性的使用的内存和没有使用的内存逐渐把内存分成n个小块,这种一般也是由于申请了一片内存有一个没有释放导致。
5. 虽然是n个大小相同的内存且同一线程分配的内存也不一定是内存泄漏,这个时候还需要看这大小相同的且同一线程分配的的n个内存是否是同一时间分配,如果是同一时间分配一般都不是泄漏。
根据以上规则可以定位到大部分内存泄漏,剩下的工作就是找到这些泄漏的内存是在哪里分配的了。
在RT-Thread上使用memtrace功能
目前memtrace只支持Small Memory Algorithm算法。我们在bsp下使用menuconfig配置如下,生成工程后编译下载就可以在msh中看到对应的命令了。
最后的工作就是定位到是哪里的代码分配的这块泄漏内存,那么内存泄漏的代码点你就找到了!
只要掌握了这套内存泄漏的调试方法就可以愉快的在RT-Thread使用动态内存了。
RT-Thread
让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。
长按二维码,关注我们