很多实时嵌入式设备是长时间不间断运行的,即使是少许的内存泄漏,也会积少成多,对嵌入式系统带来灾难性的影响。这几天,我在嵌入式软件项目中就饱尝到这个痛苦,让我明白到嵌入式实时系统的应用软件也会有许多内存问题,从而导致嵌入式系统的崩溃。例如非法的内存访问、各种死锁以及诸如堆栈溢出、数组越界和内存泄漏等。
Windows CE作为最流行的一种嵌入式操作系统,现正广泛被应用。我所负责的嵌入式应用程序也是在Windows CE平台上开发的。在进入测试阶段中,我发现有一个程序模块系统内存和CPU资源消耗急剧增加,持续增长到出现OutOfMemoryError为止,然后自动重启。这个问题折腾到我生不如死,痛苦不堪。花了我好几个通宵达旦的加班后,经过分析终于确认Windows CE内存泄漏是造成这次Windows CE系统崩溃的主要原因。这里与大家分享我在开发过程中遇到的内存泄漏的检测和处理解决过程。
一.Windows CE如何进行内存分配?
为了判断是否有内存泄露,我们首先需要了解Windows CE是如何管理内存的。许多嵌入式程序员都有一个共识,就是如果评选在Windows CE 程序中遇到最多的问题,那其中一个问题一定有内存问题。
(1)什么是Windows CE内存管理
一般来说,运行Windows CE的嵌入式设备出于紧凑型的考虑内存都不大,以至于有时候有些程序员会为了节省内存开支而牺牲程序的某些性能。但尽管WinCE系统的内存很小,用来管理内存的函数却十分完善。Windows CE实现了Windows XP中几乎全部的Win32内存管理API。例如,Windows CE支持虚拟内存分配,本地和分离的堆管理,甚至还有内存映射文件。像Windows XP一样,Windows CE支持带有应用程序间内存保护功能的32位地址空间,这一点对于多程序和多线程运行时是非常重要的功能。但是Windows CE毕竟是被设计来应用于实时场合的,所以它底层的内存结构又不同于Windows XP。
Windows CE内核可以在Flash上直接运行,也可以加载到内存中运行。Flash的运行方式,是把内核的可执行映像烧写到Flash上,系统启动时从Flash的某个地址开始执行。在这种情况下,Windows CE系统就像直接读硬盘,存储在Flash上的程序能够以现场执行的方式运行。这种能力对小型系统来说使之在具有巨大的优势,这样这能快速启动一个应用程序,因此这种方法被很多嵌入式系统所采用。另一种是内核加载方式,是把内核的压缩文件存放在Flash上,系统启动时读取压缩文件在内存里解压,然后开始执行。
(2)虚拟内存和函数应用
和大多数现代操作系统一样,Windows CE实现按需调页的虚拟内存机制。由于Windows CE系统使用了虚拟内存,这就给应用程序造成了一个假象,以为计算机安装的内存远远超过自己所需要的数量。Windows CE是32位的操作系统,因此支持4GB的虚拟地址空间。Windows把这些地址空间分给进程和系统使用,每个部分可以获得2GB的虚拟内存。
虚拟内存是内存类型中最基础的。Windows CE 实现了系统的虚拟内存管理,在一个虚拟内存系统中,应用程序主要处理这个虚拟的地址空间,并不涉及到由硬件管理的物理内存。系统调用虚拟内存API来为其它类型内存分配内存,包括堆和栈。Windows CE虚拟内存页可以处在三种状态:自由(free),保留(reserved),或被提交(committed)。
简单说,就是当一个应用程序要查询系统的内存时,可使用虚拟内存API,包括VirtualAlloc,VirtualFree和VirtualReSize函数,这些函数可以直接操作虚拟内存空间的虚拟内存页面。例如,页面可以保留,提交给物理内存,或使用这些函数释放。Windows CE实现了Win32的GetSystemInfo和GlobalMemoryStatus函数。另一个检测系统状态的函数是:void GlobalMemoryStatus(LPMEMORYSTATUS lpmst),通过GlobalMemoryStatus返回的信息可以验证Windows CE内存结构。
(3)释放虚拟内存
不同于Windows XP,Windows CE只支持在堆中分配固定(fixed)的块。这简化了内存块在堆中的处理,但是这使得堆在分配和释放一段时间后会产生碎片。当堆里已经清空的时候,仍然会占用大量的虚拟内存页,因为系统不能在堆中内存页没有完全释放的时候回收这些页。这时,一般情况下是可以通过调用VirtualFree来取消提交,或释放虚拟内存。从物理RAM页中取消提交或者取消映射,但是保持页被保留的状态,当在区域中的所有的页通过VirtualFree被释放时,也应该处在同样的情况下。更确切地说,区域中的全部页要被释放,那这些页要么都是被提交的页,要么都是被保留的页。如果有些页被提交,有些页被保留,那么VirtualFree函数调用就会失败。
实际上,Windows CE会监视系统自由的内存,并对越来越少的内存作出响应。当很少内存可用时,Windows CE首先发送WM_HIBERNATE消息,接下来会限制可能的内存分配。当应用程序被发送了一个WM_HIBERNATE消息后,系统将检测内存级别,确认是否可用内存在限度之上,如果可用内存不足,WM_HIBERNATE消息将被发送给下一个程序,这会持续到所有程序被发送了WM_HIBERNATE消息。