分享

WinCE内存管理(注意栈部分)

 cici5177 2011-05-25
   如果你在写Windows CE 程序中遇到的最重要的问题,那一定是内存问题。一个WinCE 系统可能只有4MB 的RAM,这相对于个人电脑来说是十分少的,因为个人电脑的标准配置已经到了128MB 甚至更多。事实上,运行WinCE 的机器的内存十分缺乏,以至于有时候有必要在写程序的时候为节约内存而牺牲程序的整体性能。

     幸运的是,尽管WinCE系统的内存很小,但可用来管理内存的函数却十分完善。WinCE实现了Microsoft Windows XP和Microsoft Windows Me中可用到的几乎全部的Win32内存管理API。WinCE支持虚拟内存(virtual memory)分配,本地(local)和分离(separate)的堆(heaps),甚至还有(memory-mapped files)内存映射文件。

     像Windows XP一样,WinCE支持一个带有应用程序间内存保护功能的32位平面地址空间,但是WinCE是被设计来应用于不同场合,所以它底层的内存结构不同于Windows XP。这些不同能够影响到你如何设计一个WinCE 应用程序。在这一章中,我将讲述最基础的WinCE内存结构。我也将讲述包括WinCE中可用的内存分配方式中的不同点以及如何使用这些不同的内存类型来最小化你的程序的内存占有率。

 

     对所有的电脑来说,系统地运行一个WinCE,需要ROM(只读存储器)和RAM(随机存储器)。但不论如何,在WinCE系统中,ROM和RAM的使用还是稍微有些不同于个人电脑环境。

关于RAM
      RAM在WinCE 系统中被分为两个区域:第一个是程序的存储区(program memory),也叫做系统堆(system heap)。第二个是对象存储区(object store)。这个对象存储区有点像一个永久的虚拟RAM磁盘。不同于PC上的旧式的虚拟RAM磁盘,对象存储区保留存储的文件甚至当系统被关闭以后。(脚注)这种安排的原因是WinCE 系统,例如Pocket PC代表性地具有一个主电池和一个备用电池。当用户更换主电池的时候,备用电池的工作是提供电源给RAM以便维持文件在对象存储区的存储。当用户按了重启键之后,WinCE核心就开始寻找在关闭系统前建立的对象存储区,如果找到的话就将继续使用它。

      RAM中的另一个区域则用作程序存储区。程序存储区有点像个人电脑中的RAM,它为正在运行的应用程序保存堆和栈的内容。在对象存储区和程序存储区之间的分界线是可以通过移动它来改变的,用户可以在控制面板中找到改变这条分界线的设置。在可用内存降低的(low-memory)条件下,系统将会弹出对话框询问用户是否要将对象存储区RAM划分一些给程序存储区RAM以满足要运行的应用程序的需求。

关于ROM
      在个人电脑中,ROM是用来存储BIOS(基本输入输出系统)并且只有64-128KB。在WinCE系统中,ROM大小可以从4MB到32MB并且存放整个操作系统以及和系统捆绑在一起的应用程序。在这种情况下,ROM在WinCE系统中就好像一个只读的硬盘。

      在一个WinCE系统中,存储在ROM之上的程序能够以现场执行(Execute in Place,XIP)的方式运行。换句话说,程序可以直接从ROM中执行而不必先加载到RAM中再执行。这种能力对小型系统来说,使之在两个方面具有巨大的优势。代码直接从ROM中执行意味着程序代码不会占据更有价值的RAM。同样,程序在执行前也不必先复制到RAM中,这样就只需要很少的时间来启动一个应用程序。不在ROM中,但是被包含在对象存储区(译者注:上文将对象存储区比作永久的RAM磁盘,故此处要说明,只有Intel力推的nor flash memroy类型才能以XIP方式执行,ROM其实也是一种nor flash memory类型)或闪存卡(Flash memory storage card)中的程序将不能以现场方式执行,它们将被复制到RAM中再执行。

关于虚拟内存
       WinCE 实现了系统的虚拟内存管理,在一个虚拟内存系统中,应用程序主要处理这个分离(译者注:物理上可能分离,但系统将它们联系起来),虚拟的地址空间,因此并不涉及到由硬件管理的物理内存。操作系统使用微处理器的内存管理单元来处理虚拟地址和物理地址间的实时转换。

       这种虚拟内存方法的优势能从MS-DOS系统复杂的地址空间看出来。一旦请求的RAM超过最初PC设计的640-KB限制,程序设计者将不得不作出像扩展内存一样的计划以便增加可用内存的数量。OS/2 1.x(译者注:IBM研制的操作系统)和Windows 3.0采用了一种基于段(segment-based)的虚拟内存系统来解决问题。应用程序使用虚拟内存不需要知道实际物理内存的位置,只要有内存可用就行。在这些系统中,虚拟内存以一种段的方式被实现了,即可移动的内存块(译者注:段其实就是内存分块)大小从16字节到64KB。64-KB的限制并不是由于段本身原因,而是由于Intel 80286的特性所致,这就是Windows3.x和OS/21.x的分段式虚拟内存系统结构。

分页存储
Intel 80386支持的段大小已经超过64KB,但是Microsoft和IBM开始设计OS/2 2.0,他们选择了一种不同的虚拟内存系统,随后也被386所支持,这就是分页式虚拟内存系统。在一个分页存储的系统中,最小的可被微处理器管理的单元是页(page)。对于Windows NT和OS/2 2.0系统来说,页大小都被设置为386处理器默认的4096字节。当一个应用程序存取一个页的时候,微处理器将转换该页的虚拟内存地址到实际的ROM或RAM中的物理页(译者注:这就是实现了地址映射和转换,将虚拟的和实际的存储单元一一对应),这一页同时被标记以便其他程序对该页的访问将被排斥。操作系统决定虚拟内存页是否有效,如果有效,将做一个物理内存页到虚拟页的映射。

WinCE实现了一个和其他Win32操作系统类似的分页式虚拟内存系统。在WinCE中,一页的大小可以从1024字节到4096字节,基于微处理器的不同而不同。这和Windows XP不同,Windows XP页面尺寸是Intel微处理器所支持的4096字节。对WinCE所支持的CPU类型来说,有486,Intel的Strong-ARM,和Hitachi SH4 都是是用了4096-byte 的页面。NEC 4100在Windows CE 3.0中使用了4-KB的页面尺寸但是在较早期的开放式系统版本中使用了1-KB的页面大小。

虚拟内存页可以处在三种状态:自由(free),保留(reserved),或被提交(committed),)。自由页就像它的名称一样,自由并且可被分配。保留页是虚拟地址已经被保留,并且不能被操作系统或进程中的其他线程重新分配。保留页不能用在别处,但是它同样不能被当前程序使用,因为它没有被映射到物理内存。要想执行映射,它必须被提交,一个提交页能被应用程序保留,并且直接映射到物理地址。

所有我刚才讲述的内容对有经验的Win32 程序员们来说是些陈旧的知识。对Windows CE 程序员来说最重要的东西是学习Windows CE 是如何改变这些因素的。当Windows CE 实现了大部分和它的老大哥Win32一样的内存API集的时候,Windows CE下面的基础结构将影响到上面的程序。在分开来看Window CE 应用程序的内存结构之前,让我们先来看看一些提供系统内存全局状态的函数。

 

Win XP和Wince系统内存映射
Windows XP 和 Windows CE 都是 32 位操作系统,都同样支持 4 GB 的虚拟地址空间。Windows XP 将地址空间划分为两个 2 GB 区域。上半部地址空间是为系统保留的。下面 2 GB 地址空间则由每个正在运行的应用程序重复使用。

图 1. Windows XP 虚拟内存空间

WINCE <wbr>内存管理


第一眼看,Windows CE 的虚拟地址空间是以类似系统保留区域的方式组织的,并且是重复的应用程序空间。图 2 显示了 Windows CE 地址空间。在这里,上部 2GB 地址空间也是为系统保留的。下半部地址空间则划分为很多区域。该区域的大多数(几乎一半空间)被定义为大型内存区域 (Large Memory Area)。该区域用来分配通常用于内存映射文件的大型内存空间块。
在大型内存区域的下面是另一个大型区域,本文称为保留区域。在保留区域的下面,在内存空间的最底端,是一个 64 MB 的区域。该 64 MB 区域,更准确地说是最下面的 32 MB 区域,是每个正在运行的应用程序重复使用的区域。

图 2. Windows CE 虚拟内存空间

 

WINCE <wbr>内存管理


Windows CE 应用程序内存映射
最下面的 64 MB 虚拟地址空间是驻留 Windows CE 应用程序的地方。图 3 显示了该应用程序虚拟地址空间。像在 Windows XP 应用程序中一样,应用程序代码从虚拟地址 0x10000 开始加载。应用程序启动时,将在地址空间中为所有代码保留足够的空间。然后,在需要实际代码时,这些代码将被按需分页调度进该地址空间。
在为代码保留的区域上面,页是为只读和读/写静态数据区域保留的。此外,还为本地堆和应用程序中运行的每个线程的堆栈保留了区域。当线程启动时,为每个堆栈保留的区域的大小是固定的。只在堆栈增长时才会提交实际的 RAM。另一方面,堆保留了需要在堆中分配 RAM 块时增长的区域。
当加载“现场执行”(XIP) DLL 时,将从 64 MB 空间的顶部向下加载这些 DLL。当创建 ROM 时,每个 XIP DLL 都会被定址(确定在地址空间中的位置)。加载非 XIP DLL 时,将把它放在 32 MB 边界的下面。非 XIP 的 DLL(也叫作基于 RAM 的 DLL)是指那些从对象存储区加载的 DLL、从 ROM 解压缩的 DLL或从外部文件系统(例如 Compact Flash 卡)加载的 DLL。应用程序虚拟内存空间中靠上位置的 32 MB 仅用于 XIP DLL。

 

WINCE <wbr>内存管理


图 3. Windows CE .NET 应用程序虚拟内存空间

由应用程序通过创建单独的堆或直接调用 VirtualAlloc API 所分配的任何其他内存将从底部向上进行分配,分配时,系统将查找第一个足够大、可满足分配的可用区域。
尽管和Windows XP的应用程序设计类似,但Windows CE应用程序地址空间有一个巨大的不同影响到应用程序。在Windows CE之下,一个应用程序被限制在虚拟内存空间中它自己的32MB slot(槽)和 32MB 的slot 1中,slot 1用来加载基于XIP的DLL(注:Windows CE将虚拟地址空间分为33个slot,每个slot 32MB,序号从0-32 )。当系统只有4MB RAM的时候,分配给应用程序32MB的虚拟地址空间看起来是比较合理的,Win32的程序员在使用这个2-GB的虚拟地址空间的时候,必须记住对 Windows CE应用程序的虚拟地址空间限制。

    要注意的是应用程序是以一个64-KB的内存区域开始从0x10000映射,记得最低的64KB地址空间是由Windows为所有应用程序保留的。文件映象包括代码,静态数据段和资源段。在实际过程中,当应用程序启动时代码页(code pages)不会载入进来,只有在需要该页面被载入的时候,代码才被载入进来

    只读静态数据段(read-only static data segment)和可读写静态数据区(read/write static data areas)只占很少的页面。这些段都是排列在一起的。如同代码一样,只有当这些数据段被应用程序读或者写的时候才会提交给RAM。应用程序的资源将被载入到一些分离的页面中,这些资源是只读的,并且只有当它们被应用程序获取的时候才会分页进入RAM

    应用程序的栈(stack)被映射到资源段之上。栈的段位置很容易被找到,因为它提交的页就在被保留的区域的尾部。栈的表现是从高地址到低地址增长(注:即从高到低填满地址)。如果该应用程序有超过一个线程,那么应用程序的地址空间就会保留超过一个的栈的段

    紧接着栈的就是本地堆(local heap)。引导程序保留了大量的页,大约有几百K交给heap来使用,但是只提交满足malloc,new,LocalAlloc函数调用分配的内存(注:这里是说,被分配多少内存才可以提交多少内存,没被分配的不能用作提交)。从本地堆的保留页尾部到non-XIP DLL开始的部分剩余保留页面将被映射为自由的保留空间,如果RAM允许,应用程序可以提交这些保留页。Non-XIP DLLs 就是不能被在ROM中现场执行的DLL将被从上至下载入到32MB的地址空间。Non-XIP DLLs 包括那些被压缩存储在ROM中的DLL。被压缩的ROM 中的文件在被载入到RAM中执行前必须先解压缩。

比较关心的问题 
 栈是Windows CE内存类型中最容易使用的(自行管理)。在Windows CE中的栈像其它操作系统一样,是被引用函数的临时变量存储区。操作系统也用栈来存储函数的返回地址和在异常处理中微处理器寄存器的状态。
 在系统中,Windows CE给每个线程一个分离的栈。默认情况下,系统中每个栈大小最大被限制为58KB。在一个进程中,每个分离的线程可以增加栈的大小直到58-KB的限制。
这个限制使得要我们要知道Windows CE如何对栈管理。当线程被建立的时候,Windows CE保留一个64-KB的区域给每个线程的栈。栈增加时,提交虚拟内存页是从上至下的。当栈减小时,系统将处于的低内存环境(low-memory),会回收在栈下面未使用但是仍然被提交的页。58KB的限制来源于64-KB的区域减去用来防止栈的上溢和下溢的页面数量。
 当一个应用程序建立一个新的线程时,栈的最大尺寸可以通过建立线程时CreateThread调用来指定。应用程序的主线程的栈大小可以通过应用程序被连接时的连接器开关(linker switch)来指定。同样会有一些页用作防护,但是栈的大小可以指定至1MB。注意,这个指定大小同样会被用作所有分离线程栈的默认栈大小。那就是说,如果你指定主栈为128KB,程序中所有其他的线程栈大小也限制为128KB,除非在用CreateThread建立线程时指定一个不同的大小。
 当计划如何在应用程序中使用栈的时候,另一个要值得考虑事情的是。当应用程序调用一个需要栈空间的函数时,Windows CE会试图立即提交满足要求的当前栈之下的页面,如果没有物理RAM可用,需要栈空间的线程将会暂时停止。如果请求在短时间内得不到允许,可能产生一个异常。但是如果系统不发生异常的化,Windows CE将会最大限度释放请求的页。

静态数据
 C和C++应用程序有一个预先定义好的内存块,这是由应用程序被装载时自动分配的。这些块被用来存储静态分配的字符串,缓冲区和全局变量,同时也包括通过静态连接到应用程序的静态库函数中的缓冲区。
 Windows CE分配给应用程序两块RAM中的内存块存放静态数据,一个是可读写数据(read/write data)和只读数据(read only data)。因为这些区域是基于页分配的,所以你可以在一页的静态数据开始到下一页开始之间找到一些剩余空间。细微调整Windows CE应用程序就是要写满这些剩余的空间。如果你在静态数据区有空间,最好把一个或两个缓冲区放到静态数据区,避免动态分配缓冲区。
 另一个值得考虑的事情是你是否在写一个基于ROM的应用程序。你要把尽可能多的数据移到只读静态数据区。Windows CE不会分配只读的RAM给基于ROM的应用程序。并且,ROM页会直接映射到虚拟地址空间。这实际上就给你了一个无限制的只读空间,而且不会影响到应用程序对RAM的需求。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多