嵌入式系统动态内存管理及故障检测
2018-11-21田宇马朝阳赵昶宇
田宇,马朝阳,赵昶宇
嵌入式系统动态内存管理及故障检测
田宇1,马朝阳2,赵昶宇3
(1.海军驻天津八三五七所军事代表室,天津 300308;2.海军舟山地区装备修理监修室,浙江 舟山 316000; 3.天津津航计算技术研究所,天津 300308)
针对嵌入式软件内存管理具有快速性、可靠性和高效性的特点,建立了动态内存管理模型,分析了嵌入式系统内存池管理策略和内存分配算法,详细阐述了内存泄露检查和内存重复释放检查的具体算法,并对嵌入式软件的内存操作提出了建议。
VxWorks;内存池;内存泄露;内存释放
快速性、可靠性和高效性是嵌入式开发对内存管理的基本要求,对实时性要求很高的VxWorks操作系统,内存管理机制是研究的重要领域。VxWorks系统中最基本的内存分配方案有静态分配和动态分配。静态分配是指在编译或链接时将程序所需要的内存空间分配好,采用这种分配方案的程序段,其大小一般在编译时就能确定。而动态分配是指系统运行时根据需要,动态地分配内存空间。这两种分配策略各有利弊。静态分配内存、系统的可靠性高,但失去了灵活性,而且浪费也较多;动态分配具有灵活、利用率高等优点,但会导致响应时间的不确定及容易产生碎片等问题。本文重点分析VxWorks动态内存管理机制,并对内存泄露和内存重复释放等内存故障问题的检测和诊断给出了解决方案。
1 动态内存管理机制
1.1 动态内存管理模型
动态内存是一个自由存储区,编程人员根据需要进行动态内存的分配、访问、回收。对动态内存的操作与系统当前的动态内存状态有关。在使用动态内存之前,必须向自由存储区申请内存。由于自由存储区的容量是有限的,因此在动态内存分配之后和动态内存使用之前还要进行分配结果的检测,以保证后续操作的正确性;在程序结束前,要进行动态内存的释放操作,防止内存发生泄露。图1描述了一个动态内存管理过程,图中的节点表示动态内存管理过程中的几个阶段,图中的连线表示各阶段之间的迁移。
图1刻画了内存管理的几个阶段:①Start。程序开始。②Memory allocate。对指针进行动态内存分配,若没有足够的动态内存或者分配过程发生错误,则将该指针置为NULL。③Check memory。用来检查分配的结果,以保证后续动态内存操作的正确性。④Access memory。对动态内存中的内容进行读、写、修改等操作。⑤Free memory。动态内存操作的结束,进行动态内存释放。⑥End。程序结束。
图1中实心箭头表示程序正确执行时的动态内存管理路径,而虚线箭头表示的路径指出动态内存使用后未被释放,即发生了动态内存泄露。
图1 动态内存管理过程模型
VxWorks内存管理函数库分为完全内存管理函数库(库名称为memLib)和核心内存管理函数库(库名称为memPartLib)。其中核心内存管理库为内存分区的分配、管理提供了核心函数,以mem Part开头的包含了创建、管理、分配、释放内存分区的函数,其余的提供了与ANSI标准相兼容的对系统内存分区的操作接口。当应用程序从系统分区中再创建其他分区时,就得调用函数malloc进行动态分配。一般系统中只有1个内存分区,即系统分区,所有任务所需要的内存直接调用malloc从系统分区中进行分配,使用完后调用free进行内存释放,通过free释放的内存将被聚合形成更大的空闲块,这就是VxWorks的动态内存分配机制。
1.2 内存池管理策略
应用程序申请使用系统内存区时,调用函数malloc/free 虽然灵活方便,但它有时间不确定、会产生过多的碎片、不能用于中断服务程序、降低系统性能等缺点,所以不宜频繁使用。一般在系统设计时,采用动态分配与静态分配相结合的方法来管理系统内存池,来避免单一内存分配所带来的弊端。内存池的引入可以极大加快内存分配/释放过程,减少动态分配、释放内存时的消耗,并且可以有效减少内存碎片,避免内存泄露。一般内存池结构可由单元尺寸、单元数量、空闲链表、调试参数等组成。由于利用了内存池的内存分配方案在VxWorks系统中,使得系统减少了malloc/free的调用次数,减少了碎片,同时用户可自由添加一些用于内存分配和释放的调试函数,更好地监视了内存的使用情况。
1.3 内存分配算法
嵌入式实时动态内存的传统分配方式,往往是系统的碎片随物理内存的增加呈线性增长,导致碎片的组合操作所付出的代价很高,目前常用的解决方案是以页表作为管理结构,页面作为管理基础,实现了对内存分配与回收的实时管理。
VxWorks内存管理是基于一种Flat模式,从宏观的层次可以分成Partition(分区)、Block(块)、Pool(池)的框架。由于系统只有一个分区,所有任务所需内存直接由malloc从其中分配。最常用算法为First-Fit(最先分配)算法,此算法思想是:空闲内存块按地址大小递增排列,对于要求分配的分区容量size,从头开始比较,直至找到满足不小于size的块为止,并从链表相应块中分配出相应size大小的指针。从空闲链表中查找内存块,从高地址开始,当找到满足第一个分配请求空闲块时就分配所需的内存,并修改该空闲块的大小,空闲块的剩余部分仍然保留在空闲链表中。动态内存释放时,根据块头中的信息判断相邻的内存块是否空闲,如果将空闲块合并,并修改长度,否则就把新释放的内存插入到空闲链表中。
First-Fit算法在提高系统实时性的同时,也出现一些问题,比如2个任务占用2个连续的Block,当Block1操作越界后,Block2数据会被破坏,同时在系统分配时会产生很多碎片等,这些都会对系统运行产生影响。
2 内存故障测试和诊断
2.1 内存泄露诊断
内存泄露一般是指堆内存的泄漏。堆内存是指程序从堆中分配的、使用完毕后必须释放的内存。也就是说,程序在运行过程中,如果需要动态内存来协助程序执行,则会向操作系统从堆中申请一块内存,待使用完毕之后,将该内存空间释放,及时归还给操作系统。内存泄漏是指由于疏忽或者错误程序未能释放已经不再使用的内存的情况。
如果运行在嵌入式平台上的软件程序存在内存泄露,轻则会降低系统的性能,最糟糕的情况是所有堆内存被分配,程序无法再次从堆内存中申请到程序执行所必须的内存,从而导致全部或者部分程序、设备无法正常工作,造成系统瘫痪甚至崩溃。
在VxWorks系统下,memShow可以显示系统内存分配情况,包括已分配内存和空闲内存大小、块数、平均大小等信息。本文提出的内存泄漏诊断算法的基本思想是:在应用程序申请堆内存的时候,将申请信息记录在一个后台程序管理的全局数据结构中;在应用程序释放堆内存的时候,将后台程序管理的全局数据结构中的记录删除;当程序运行到一个稳态,即程序被认为不应该有堆内存未被释放的状态,将后台程序管理的这个全局数据结构中的记录输出到指定文件,供测试人员检查。后台全局数据结构中记录的信息应该至少包括申请的堆内存的首地址、申请的堆内存的大小、申请该堆内存的代码行号和文件名。后台程序在一个单独的任务中实现。
2.1.1 增加一个内存泄露检查跟踪结点模块
该模块在嵌入式软件程序中申请堆内存的时候被调用,同时,“申请成功的堆内存首地址”“申请成功的堆内存长度”“成功申请堆内存程序的源文件名称”和“成功申请堆内存程序代码所在源文件中的行号”作为该模块的输入,该模块将在堆内存检查管理链表中添加一个结点并将输入信息记录。本算法中,使用带有表头的单向链表来记录和管理程序的堆内存申请信息。每申请一次内存,增加一个结点,释放一次内存,删除一个结点。程序详细流程如图2所示。
图2 增加一个内存泄露检查跟踪结点模块流程图
2.1.2 删除一个内存泄露检查跟踪结点模块
该模块在嵌入式软件程序当释放堆内存的时候被调用,同时,“申请成功的堆内存首地址”“申请成功的堆内存长度”“成功申请堆内存程序的源文件名称”和“成功申请堆内存程序代码所在源文件中的行号”作为该模块的输入,该模块将在堆内存检查管理链表中添加一个结点并将输入信息记录。程序详细流程如图3所示。
2.1.3 内存泄露检查跟踪结点信息输出模块
该模块在一个测试用例被执行完毕的时候被调用,也可以在使用者认为需要的时候调用。程序详细流程如图4所示。
2.2 内存重复释放诊断
重复释放是指在程序申请到一块堆内存之后,经过使用将这块内存释放,但没有将指向这块内存的所有指针回收,并在程序的其他部分再次将指向同一块内存单元的指针交给内存分配器执行堆内存释放的操作。堆内存重复释放检查算法是在内存泄漏检查算法的基础上开发与设计的。重复释放检查算法使用一个单向链表记录程序的重复释放信息,在测试用例执行结束之前将该信息输出。
2.2.1 增加一个内存重复释放检查跟踪结点模块
该模块在嵌入式软件程序释放堆内存的时候被调用,同时,“释放的堆内存地址”、“释放的堆内存的源文件名称”和“释放的堆内存程序代码所在源文件中的行号”作为该模块的输入,该模块将在重复释放堆内存检查管理链表中添加一个结点并将输入信息记录。程序详细流程如图5所示。
图3 删除一个内存泄露检查跟踪结点模块流程图
图4 内存泄露检查跟踪结点信息输出模块流程图
图5 增加一个内存重复释放检查跟踪结点模块流程图
2.2.2 内存重复释放检查跟踪结点信息输出模块
该模块在一个测试用例被执行完毕的时候被调用,也可以在使用者认为需要的时候调用,详细流程图如图6所示。
2.2.3 避免内存泄露和内存重复释放的建议
内存泄露和内存重复释放是在设计与编码期间引入的,当使用 C/C++ 进行开发时,采用一致的编程规范和养成良好的习惯是防止内存泄漏发生的第一步,也是最重要的措施。应注意以下事项:①malloc/calloc与free、new与delete、open与close、fopen与fclose 等一定要成对出现,特别是在函数带有判断分支语句退出时,一定记得在每个分支都要释放该释放的内存、关闭必要的文件句柄;②正确处理malloc、calloc、open/fopen 等函数的返回值;③在释放结构指针时,一定要遍历释放(处理)必要的结构中每个成员指针;④对于嵌入式系统而言,系统通常是一个确定的系统,对于系统中用到的可以确定大小内存,建议直接用数组或结构,不要动态申请。
图6 内存重复释放检查跟踪结点信息输出模块流程图
3 结束语
本文分析了VxWorks系统的动态内存管理机制,建立了动态内存管理模型,阐述了内存池管理策略和内存分配算法,在此基础上,对编程中容易出现的内存泄露和内存重复释放的问题给出了具体的故障检测算法,并对内存操作提出了建议。本文的内容有助于提升VxWorks系统软件的开发人员和测试人员的技能水平。
[1]顾胜元,杨丹,黄海伦.嵌入式实时动态内存管理机制研究与应用[J].重庆工学院学报(自然科学),2009(1):117-121.
[2]殷战宁,刘琳.VxWorks的内存配置和管理[J].舰船电子对抗,2012,35(3):104- 109.
[3]何煦岚,何晓岚.基于多链表结构的嵌入式系统内存管理[J].计算机应用与软件,2008,25(4):58- 60.
2095-6835(2018)21-0006-03
TP316.2
A
10.15913/j.cnki.kjycx.2018.21.006
田宇(1984—),男,工学硕士,工程师,主要从事装备质量监督与检验验收方面的工作与研究。马朝阳(1976—),男,大学本科,工程师,主要从事舰艇装备保障方面的工作与研究。赵昶宇(1982—),男,陕西汉中人,工学硕士,高级工程师,主要从事嵌入式系统软件测试方面的研究。
〔编辑:严丽琴〕