找回密码
 注册
搜索
查看: 1304|回复: 3

[讨论] Windows Mobile 内存管理(三)

[复制链接]
发表于 2009-3-19 10:54:40 | 显示全部楼层 |阅读模式
区域和页
    在我继续谈论虚拟内存API之前,我需要说明一个比较细微的差异。虚拟内存在区域内被保留是以64KB为基础的。在区域内的页面能够一页一页地被提交(译者注:前面说到在Windows CE中每页是4096字节或1024字节)。你可以直接提交一页或者几页而不是保留区域的全部页。但是对页或几页来说,直接提交的仍是以64-KB为单位(译者注:可以直到被提交的页数量足够填满64KB才真正提交),因为这个原因,最好保留一块64-KB的虚拟内存,然后提交那些需要的页到区域里。
    因为对每个进程32MB虚拟内存地址空间的限制,这就有了一个最大值 32MB/64KB-1=511,这是虚拟内存在内存溢出前能被保留的最大值。接下来,有个例子,代码段如下:
#define PAGESIZE 1024   // Assume we're on a 1-KB page machine
for (i = 0; i < 512; i++)
pMem = VirtualAlloc (NULL, PAGESIZE, MEM_RESERVE │ MEM_COMMIT,PAGE_READWRITE);
代码分配512个单页的虚拟内存。甚至你系统还有一半的可用RAM,VirtualAlloc也会在完成分配前失败。因为它的运行已经超出了应用程序的虚拟地址空间。发生这种情况是因为每1-KB的块要占用64-KB的空间,接下来应用程序的代码,栈,和本地堆也要映射到同样的32-MB虚拟地址空间,可用的虚拟分配区域通常不超过475个。
    一个比较好的分配512块特殊内存的方法是这样做:
#define PAGESIZE 1024   // Assume we're on a 1-KB page machine.
// Reserve a region first.
pMemBase = VirtualAlloc (NULL, PAGESIZE * 512, MEM_RESERVE,
                         PAGE_NOACCESS);

for (i = 0; i < 512; i++)
    pMem = VirtualAlloc (pMemBase + (i*PAGESIZE), PAGESIZE,
                            MEM_COMMIT, PAGE_READWRITE);
    代码首先保留了一块区域,页面将在以后被提交。因为区域已经被先保留了,提交页就不受64-KB限制(译者注:只有保留页最小值受64KB限制),等等,如果你系统中有512KB的可用内存,分配将会成功。
    尽管我刚才给你看的是一个人为的例子(还有比直接分配虚拟内存更好的方法来分配1-KB的内存块),这中内存分配方法验证了一个重要的不同(对于其他Windows系统)。在桌面版本的Windows中,工作中的应用程序有一个完全的2-GB的虚拟地址空间。在Windows CE中,一个程序员必须明白每个应用程序只被保留了较小的32-MB虚拟地址空间。

释放虚拟内存
    你可以通过调用VirtualFree来取消提交,或释放虚拟内存。从物理RAM页中取消提交或者取消映射,但是保持页被保留的状态。函数原型如下:
    BOOL VirtualFree (LPVOID lpAddress, DWORD dwSize,DWORD dwFreeType);
lpAddress参数是一个指针,指向要被释放或取消提交的虚拟内存的区域。dwSize参数指明要取消提交区域的大小,以字节为单位。如果区域要被释放,这个值必须是0,dwFreeType参数包含了操作类型标志,MEM_DECOMMIT标志指定了区域将被取消提交但是仍被保留,MEM_RELEASE标志说明区域要取消提交并且释放。
    在区域中的所有的页通过VirtualFree被释放必须处在同样的情况下。更确切地说,区域中的全部页要被释放,那这些页要么都是被提交的页,要么都是被保留的页。如果有些页被提交,有些页被保留,那么VirtualFree函数调用就会失败。

改变和查询权限
    你可以通过调用VirtualProtect来修改最初通过VirtualAlloc指定的虚拟内存区域的访问权限。这个函数只能改变被提交的页的访问权限。函数的原型如下:BOOL VirtualProtect (LPVOID lpAddress, DWORD dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);开始的两个参数lpAddress和dwSize,指定了函数作用的块的大小。flNewProtect参数包含区域的新的保护标志。这些标志和我前面提到的VirtualAlloc函数使用的一样。lpflOldProtect参数指向一个DWORD,将返回旧的保护标志(译者注:如果此处为NULL或指向一个无效的变量,函数将会失败)。
    当前区域的保护权限可用通过下面的调用查询:
DWORD VirtualQuery (LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer,DWORD dwLength);lpAddress参数包含区域开始查询的地址。lpBuffer指针指向我很快就要提到的一个PMEMORY_BASIC_INFORMATION结构。第三个参数dwLength,必须包含PMEMORY_BASIC_INFORMATION结构的大小。
    PMEMORY_BASIC_INFORMATION结构被定义如下:
  typedef struct _MEMORY_BASIC_INFORMATION {
    PVOID BaseAddress;
    PVOID AllocationBase;
    DWORD AllocationProtect;
    DWORD RegionSize;
    DWORD State;
    DWORD Protect;
    DWORD Type;
} MEMORY_BASIC_INFORMATION;MEMORY_BASIC_INFORMATION结构的第一个字段是BaseAddress,是传递给VirtualQuery函数的一个地址。AllocationBase字段包含使用VirtualAlloc函数分配的区域的基地址,AllocationProtect字段包含区域原来被分配时的保护属性。RegionSize字段包含从传递给VirtualQuery的指针开始到一系列具有相同属性的页为结尾的区域大小(译者注:这里是从基地址开始)。State字段包含区域中页的状态-自由,保留,提交。Protect字段可以包含MEM_PRIVATE标志,指明该区域包含应用程序私有的数据;MEM_MAPPED指明该区域被映射为一个内存映射文件;MEM_IMAGE指明该区域被映射为一个EXE或DLL模块。
    理解VirtualQuery最好的方式是看例子,比方说一个应用程序保留了16,384字节(在以页面大小为1-KB的机器中占16页)。系统从地址0xA0000开始保留这16-KB的块。后来应用程序从最初的区域中提交了从第2048字节(2页)开始的9216字节(9页)。

    如果一个对VirtualQuery的调用中,lpAddress指向第四页的区域(地址0xA1000),返回值如下:
BaseAddress          0xA1000
AllocationBase       0xA0000
AllocationProtect    PAGE_NOACCESS
RegionSize           0x1C00    (7,168 bytes or 7 pages)
State                MEM_COMMIT
Protect              PAGE_READWRITE
Type                 MEM_PRIVATE
    BaseAddress字段包含传递给VirtualQuery的地址,值为0xA1000,在最初的区域中是第4096字节。AllocationBase字段包含最初区域的地址。当AllocationProtect设为PAGE_NOACCESS时,指明区域是最初被保留的,而不是直接提交。RegionSize字段包含传递给VirtualQuery的指针0xA1000开始,到被提交的页结束地址0xA2C00的字节数。State和Protect字段包含的标志表明当前的页状态。Type字节表明区域被应用程序分配给自己使用。


    很明显,以页为单位分配内存对应用程序来说效率是很低的。为了优化内存的使用,应用程序需要以字节为单位分配和释放内存,或者至少以每8字节为单位。系统通过堆来实现这种分配方式。使用堆可以免去处理由Windows CE支持的不同微处理器的不同页面大小。一个应用程序可以简单地在堆中分配一块内存,由系统来处理分配需要的页数。
    就像我前面提到的,堆是系统为应用程序保留的虚拟内存区域。系统提供大量的函数来在堆中分配和释放内存块,并且间隔比页要小(译者注:例如每页大小为4KB,而堆分配可以字节为单位)。当内存由应用程序的堆分配时,系统自动分配调整堆大小来满足需要,当堆中的内存块被释放时,系统会查看是否整页被释放,如果是的话,那么该页将被回收。
    不同于Windows XP,Windows CE只支持在堆中分配固定(fixed)的块。这简化了内存块在堆中的处理,但是这使得堆在分配和释放一段时间后会产生碎片。当堆里已经清空的时候,仍然会占用大量的虚拟内存页,因为系统不能在堆中内存页没有完全释放的时候回收这些页(译者注:因为堆以字节为单位,一页中可能有的块需要被释放,其他的块不需要,所以整页都不会被释放)。
    当应用程序启动的时候,每个程序都会有一个由系统创建的默认或本地堆。本地堆中的内存块,可以通过LocalAlloc,LocalFree和LocalRealloc来分配,释放和改变大小。一个应用程序也可以建立分离堆。这些堆和本地堆有着相同的属性,但是是通过一组Heapxxxx函数来管理的。

本地堆
    在默认情况下,Windows CE最初会保留192,512字节给本地堆,但是只提交被分配的页。如果应用程序在本地堆中分配了超过188KB,系统将会分配更多的空间给本地堆。增加堆大小将需要一个分离的,不连续的保留地址空间作为堆的附加空间。应用程序不应该假设本地堆被包含在一块虚拟地址空间里。因为Windows CE 的堆只支持固定的块,Windows CE执行的只是Win32本地堆函数的子集,提供必要的分配,改变大小,释放固定的本地堆内存块。

在本地堆中分配内存
    你可以通过一下调用在本地堆中分配一块内存:
    HLOCAL LocalAlloc (UINT uFlags, UINT uBytes);调用返回一个HLOCAL,这是本地内存块的句柄,但是由于内存块是固定分配的,所以返回值可以被简单地看作是一个指向块的指针。
    uFlags参数描述了内存块的特征。标志由于Windows CE被限制固定分配操作,只支持以下内存:
LMEM_FIXED
在本地堆中分配一个固定内存块,因为本地堆分配已经固定,所以是多余的。
LMEM_ZEROINIT
初始化内存内容为0。
LPTR
合并LMEM_FIXED和LMEM_ZEROINIT标志。
uBytes参数指定了要分配的内存块的大小,以字节为单位。块大小要补齐,但是只针对后面8字节范围。

释放本地堆的内存
    你可以通过以下调用释放内存块:
HLOCAL LocalFree (HLOCAL hMem);
函数需要本地堆内存句柄,成功会返回NULL。如果调用失败,会返回内存块的句柄。

改变和查询本地堆内存的大小
    你可以通过调用改变本地堆的分配:
    HLOCAL LocalReAlloc (HLOCAL hMem, UINT uBytes, UINT uFlag);hMem参数是一个由LocalAlloc返回的指针(句柄)。uBytes参数是内存块的新大小。uFlag参数包含给新内存块的标志。在Windows CE中,有两个新标志与之相关,LMEM_ZEROINIT和LMEM_MOVEABLE。LMEM_ZEROINIT表示调用函数后内存块中新增加的区域被初始化为0。LMEM_MOVEABLE标志告诉Windows,当内存块增加后,没有合适的空间容纳内存块时,函数可以立即移动内存块。如果没有这个标志,当你没有合适的空间来满足需要的时候,LocalRealloc将会出现out-of-memory的错误而失败,如果你指定了LMEM_MOVEABLE标志,调用将会返回句柄(实际是指向内存块的指针)。
    内存块的大小可以通过以下调用查询:
    UINT LocalSize (HLOCAL hMem);返回内存块最少需要的内存大小。像我前面提到的,Windows CE本地堆自动以8个字节来补齐(译者注:就是分配1字节要占8字节)。

分离堆
    为了避免本地堆的碎片,并且如果你要分配连续的内存块,较好的办法是建立分离堆,但将花费一定的时间。一个例子就是,文本编辑器为要编辑的文件建立多个分离堆。当文件被打开或者关闭的时候,堆随之建立和销毁。
    在Windows CE下的堆和Windows XP下有着同样的API。唯一值得注意的不同是缺少HEAP_GENERATE- _EXCEPTIONS标志。在Windows XP下,该标志表示系统在分配请求不合适的时候产生一个异常。

建立一个分离堆
    你可以通过以下调用建立一个分离堆。HANDLE HeapCreate (DWORD flOptions, DWORD dwInitialSize,DWORD dwMaximumSize);在Windows CE中,第一个参数flOptions必须为空或包含HEAP_NO_SERIALIZE标志。默认情况下,Windows堆管理程序防止一个进程中的两个线程在同意时间访问堆。这个串行参数防止系统用来跟踪堆中内存块分配的堆指针被破坏。在其他版本的Windows中,当你不需要这种保护时可以使用HEAP_NO_SERIALIZE标志。在Windows CE中,该标志是为了兼容性而提供的,所有的堆访问都是串行的(译者注:串行即非并行,只能依次访问)。
    其他两个参数,dwInitialSize和dwMaximumSize,指定了最初的大小和预期的堆最大值。dwMaximumSize的值确定虚拟内存空间保留给堆多少页。如果你想让Windows来决定有多少页可以保留,你可以把这个参数设为0。默认一个堆的大小是188KB,dwInitialSize参数决定了有多少这些保留的页将被提交。如果该参数为0,表示堆将一页一页提交。

在分离堆中分配内存
    你可以通过以下调用分配内存LPVOID HeapAlloc (HANDLE hHeap, DWORD dwFlags, DWORD dwBytes);
    注意,返回值是一个指针,而不是和LocalAlloc函数一样的句柄。分离堆总是分配固定的内存块,甚至在Windows XP和Windows Me中也是一样。第一个参数是通过HeapCreate调用返回的句柄。dwFlags参数可以是两个自说明的(self-explanatory)标志之一HEAP_NO_SERIALIZE和 HEAP_ZERO_MEMORY。最后一个参数dwBytes指定了要分配的内存块字节数。大小要和DWORD补齐。
发表于 2009-6-24 16:46:46 | 显示全部楼层
顶[em01]
点评回复

使用道具 举报

发表于 2009-6-26 16:14:17 | 显示全部楼层
佳作
顶一个     [em27]
点评回复

使用道具 举报

发表于 2009-7-3 13:28:59 | 显示全部楼层
牛人[em02][em02]
点评回复

使用道具 举报

高级模式
B Color Image Link Quote Code Smilies

本版积分规则

Archiver|手机版|小黑屋|52RD我爱研发网 ( 沪ICP备2022007804号-2 )

GMT+8, 2024-11-25 20:21 , Processed in 0.046749 second(s), 16 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表