鸿蒙轻内核的得力助手:带你掌握 4 种内存调试方法
摘要:内存调测方法旨在辅助定位动态内存相关问题,提供了内存池信息统计、内存泄漏检测和踩内存检测三种调测手段。
本文分享自华为云社区《鸿蒙轻内核-内存调测-内存信息统计》,作者:zhushy 。
内存调测方法旨在辅助定位动态内存相关问题,提供了基础的动态内存池信息统计手段,向用户呈现内存池水线、碎片率等信息;提供了内存泄漏检测手段,方便用户准确定位存在内存泄漏的代码行,也可以辅助分析系统各个模块内存的使用情况;提供了踩内存检测手段,可以辅助定位越界踩内存的场景。
内存信息统计
1、基础概念
内存信息包括内存池大小、内存使用量、剩余内存大小、最大空闲内存、内存水线、内存节点数统计、碎片率等。
内存水线:即内存池的最大使用量,每次申请和释放时,都会更新水线值,实际业务可根据该值,优化内存池大小;
碎片率:衡量内存池的碎片化程度,碎片率高表现为内存池剩余内存很多,但是最大空闲内存块很小,可以用公式(fragment=100-最大空闲内存块大小/剩余内存大小)来度量;
其他参数:通过内存管理模块的调用接口,扫描内存池的节点信息,统计出相关信息。
2、功能配置
LOSCFG_MEM_WATERLINE:开关宏,默认打开;若关闭这个功能,在 target_config.h 中将这个宏定义为 0。如需获取内存水线,需要打开该配置。
3、开发指导
关键结构体介绍:
内存水线获取
调用 LOS_MemInfoGet 接口,第 1 个参数是内存池首地址,第 2 个参数是 LOS_MEM_POOL_STATUS 类型的句柄,其中字段 usageWaterLine 即水线值。
内存碎片率计算
同样调用 LOS_MemInfoGet 接口,可以获取内存池的剩余内存大小和最大空闲内存块大小,然后根据公式(fragment=100-最大空闲内存块大小/剩余内存大小)得出此时的动态内存池碎片率。
4、编程实例
本实例实现如下功能:
1.创建一个监控线程,用于获取内存池的信息;
2.调用 LOS_MemInfoGet 接口,获取内存池的基础信息;
3.利用公式算出使用率及碎片率。
代码实现如下:
编译运行输出的结果如下:
内存泄漏检测机制
1、基础概念
内存泄漏检测机制作为内核的可选功能,用于辅助定位动态内存泄漏问题。开启该功能,动态内存机制会自动记录申请内存时的函数调用关系(下文简称 LR)。如果出现泄漏,就可以利用这些记录的信息,找到内存申请的地方,方便进一步确认。
2、功能配置
LOSCFG_MEM_LEAKCHECK:开关宏,默认关闭;若打开这个功能,在 target_config.h 中将这个宏定义为 1。
LOSCFG_MEM_RECORD_LR_CNT:记录的 LR 层数,默认 3 层;每层 LR 消耗 sizeof(void *)字节数的内存。
LOSCFG_MEM_OMIT_LR_CNT:忽略的 LR 层数,默认 4 层,即从调用 LOS_MemAlloc 的函数开始记录,可根据实际情况调整。为啥需要这个配置?有 3 点原因如下:
- LOS_MemAlloc 接口内部也有函数调用;
- 外部可能对 LOS_MemAlloc 接口有封装;
- LOSCFG_MEM_RECORD_LR_CNT 配置的 LR 层数有限;
正确配置这个宏,将无效的 LR 层数忽略,就可以记录有效的 LR 层数,节省内存消耗。
3、开发指导
3.1 开发流程
该调测功能可以分析关键的代码逻辑中是否存在内存泄漏。开启这个功能,每次申请内存时,会记录 LR 信息。在需要检测的代码段前后,调用 LOS_MemUsedNodeShow 接口,每次都会打印指定内存池已使用的全部节点信息,对比前后两次的节点信息,新增的节点信息就是疑似泄漏的内存节点。通过 LR,可以找到具体申请的代码位置,进一步确认是否泄漏。
调用 LOS_MemUsedNodeShow 接口输出的节点信息格式如下:每 1 行为一个节点信息;第 1 列为节点地址,可以根据这个地址,使用 GDB 等手段查看节点完整信息;第 2 列为节点的大小,等于节点头大小+数据域大小;第 3~5 列为函数调用关系 LR 地址,可以根据这个值,结合汇编文件,查看该节点具体申请的位置。
注意: 开启内存检测会影响内存申请的性能,且每个内存节点都会记录 LR 地址,内存开销也加大。
3.2 编程实例
本实例实现如下功能:构建内存泄漏代码段。
1. 调用 LOS_MemUsedNodeShow 接口,输出全部节点信息打印;
2. 申请内存,但没有释放,模拟内存泄漏;
3. 再次调用 LOS_MemUsedNodeShow 接口,输出全部节点信息打印;
4. 将两次 log 进行对比,得出泄漏的节点信息;
5. 通过 LR 地址,找出泄漏的代码位置;
3.3 示例代码
代码实现如下:
3.4 结果验证
编译运行输出 log 如下:
对比两次 log,差异如下,这些内存节点就是疑似泄漏的内存块:
部分汇编文件如下:
其中,通过查找 0x080041ee,就可以发现该内存节点是在 MemLeakTest 接口里申请的且是没有释放的。
踩内存检测机制
踩内存检测机制作为内核的可选功能,用于检测动态内存池的完整性。通过该机制,可以及时发现内存池是否发生了踩内存问题,并给出错误信息,便于及时发现系统问题,提高问题解决效率,降低问题定位成本。
功能配置
LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK:开关宏,默认关闭;若打开这个功能,在 target_config.h 中将这个宏定义为 1。
1. 开启这个功能,每次申请内存,会实时检测内存池的完整性。
2. 如果不开启该功能,也可以调用 LOS_MemIntegrityCheck 接口检测,但是每次申请内存时,不会实时检测内存完整性,而且由于节点头没有魔鬼数字(开启时才有,省内存),检测的准确性也会相应降低,但对于系统的性能没有影响,故根据实际情况开关该功能。
由于该功能只会检测出哪个内存节点被破坏了,并给出前节点信息(因为内存分布是连续的,当前节点最有可能被前节点破坏)。如果要进一步确认前节点在哪里申请的,需开启内存泄漏检测功能,通过 LR 记录,辅助定位。
注意:
开启该功能,节点头多了魔鬼数字字段,会增大节点头大小。由于实时检测完整性,故性能影响较大;若性能敏感的场景,可以不开启该功能,使用 LOS_MemIntegrityCheck 接口检测。
开发指导
开发流程
通过调用 LOS_MemIntegrityCheck 接口检测内存池是否发生了踩内存,如果没有踩内存问题,那么接口返回 0 且没有 log 输出;如果存在踩内存问题,那么会输出相关 log,详见下文编程实例的结果输出。
编程实例
本实例实现如下功能:
1. 申请两个物理上连续的内存块;
2. 通过 memset 构造越界访问,踩到下个节点的头 4 个字节;
3. 调用 LOS_MemIntegrityCheck 检测是否发生踩内存。
示例代码
代码实现如下:
结果验证
编译运行输出 log 如下:
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/584228113d7980079f88c9f66】。文章转载请联系作者。
评论