Windows CE內存管理

如果你在寫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 應用程序的內存結構之前,讓我們先來看看一些提供系統內存全局狀態的函數。

查詢系統的內存

       如果一個應用程序知道系統當前的內存狀態,它將可以較好地管理可用到的資源。WinCE實現了Win32的GetSystemInfo和GlobalMemoryStatus函數,GetSystemInfo函數原型如下:

VOID GetSystemInfo (LPSYSTEM_INFO lpSystemInfo);

它傳遞了一個指針給SYSTEM_INFO結構,定義如下

 

 

wProcessorArchitecture參數表示系統微處理器的架構。它的值是定義在Winnt.h中,例如PROCESSOR_ARCHITECTURE_INTEL。Windows CE擴展了這些常數,包括PROCESSOR_ARCHITECTURE_ARM,PROCESSOR_ARCHITECTURE_SHx,等等。增加的常數包括像Win32操作系統支持的網絡CPU(net CPU)。跳過一些參數,我們看dwProcessorType參數,它來自於特定的微處理器類型。常數有Hitachi SHx架構中的PROCESSOR_HITACHI_SH3和PROCESSOR_HITACHI_SH4。最後兩個參數,wProcessorLevel和wProcessorRevision,指明瞭CPU類型的特徵。wProcessorLevel參數類似於dwProcessorType參數,它一個指定的微處理器系列中被定義了,dwProcessorRevision告訴你模式(model)和芯片的步進級別(stepping level)。

typedef struct {

    WORD wProcessorArchitecture;

    WORD wReserved;

    DWORD  dwPageSize;

    LPVOID lpMinimumApplicationAddress;

    LPVOID lpMaximumApplicationAddress;

    DWORD  dwActiveProcessorMask;

    DWORD  dwNumberOfProcessors;

    DWORD  dwProcessorType;

    DWORD  dwAllocationGranularity;

    WORD  wProcessorLevel; 

    WORD  wProcessorRevision;

} SYSTEM_INFO;

 

 

 

       dwPageSize參數說明了微處理器頁面的大小,以字節爲單位。知道這個值,將會在你直接處理虛擬內存API的時候帶來方便,在此我只作簡短說明。lpMinimumApplication­Address和lpMaximumApplicationAddress參數說明了應用程序可用到的最小和最大的虛擬內存地址。dwActiveProcessorMask和dwNumberOfProcessors參數顯示被Window XP系統支持的多個處理器數量。因爲Windows CE 只支持一個處理器,所以你可以忽略這個參數。dwAllocationGranularity參數說明了一個完整的虛擬內存區域分配的界限。像Windows XP,Windows CE 規定虛擬區爲64-KB的界限(譯者注:作者此處64-KB的意思是即使你只分配一個字節的內存,系統也將會保留64-KB的虛擬地址空間給它,這個值一般是由硬件代碼實現的,但是不同硬件可能不同值)。

       第二個方便的檢測系統狀態的函數如下:

void GlobalMemoryStatus(LPMEMORYSTATUS lpmst);

它返回一個MEMORYSTATUS結構,定義爲

typedef struct { 

    DWORD dwLength; 

    DWORD dwMemoryLoad; 

    DWORD dwTotalPhys; 

    DWORD dwAvailPhys; 

    DWORD dwTotalPageFile; 

    DWORD dwAvailPageFile; 

    DWORD dwTotalVirtual; 

    DWORD dwAvailVirtual; 

} MEMORYSTATUS;

 

 

    dwLength參數在調用這個函數之前必須初始化。dwMemoryLoad參數是一個不確定的值;這是一個可用的一般性的參數指示了當前系統的內存使用情況(譯者注:該參數是一個近似的百分比值,指明瞭物理內存的使用情況)。dwTotalPhys和dwAvailPhys參數指明瞭RAM有多少頁被分配給了程序存儲區RAM,和還有多少可用(譯者注:實際上是以字節爲單位)。這些值不包括分配對象存儲區的RAM。

 

       dwTotalPageFile和dwAvailPageFile參數在Windows XP下和Windows Me下指明瞭當前頁面文件(paging file)的狀態。因爲Windows CE不支持頁面文件,所以這些參數總是0。dwTotalVirtual和dwAvailVirtual參數指明瞭總共的和可用的可被應用程序存取的虛擬內存頁的數量(譯者注:參數都是以字節爲單位,而不是指頁數,dwAvailVirtual是指未保留和未提交的內存)。

       通過GlobalMemoryStatus返回的信息可以驗證Windows CE內存結構,通過在有32MBRAM的HP iPaq Pocket PC上調用函數,返回值如下:

dwMemoryLoad       0x18          (24)

dwTotalPhys        0x011ac000    (18,530,304)

dwAvailPhys        0x00B66000    (11,952,128)

dwTotalPageFile    0

dwAvailPageFile    0

dwTotalVirtual     0x02000000    (33,554,432)

dwAvailVirtual     0x01e10000    (31,522,816)

 

 

 

 

    dwTotalPhys參數表明了系統的32MB RAM,分配了18.5MB給程序存儲區RAM,其中12MB仍然可用。注意這對應用程序來說,並不是通過這次調用,就知道了另外14MB的RAM是分配給對象存儲區的。要檢測分配給對象存儲區的RAM的大小,要使用GetStoreInformation。

       dwTotalPageFile和dwAvailPageFile參數是0,表明頁面文件不被Windows CE所支持。dwTotalVirtual參數十分有趣,因爲它顯示了Windows CE 強制給程序的32-MB的虛擬內存限制。其間,dwAvailVirtual參數顯示了只使用了32MB虛擬內存的一小部分(譯者注:即33,554,432-31,522,816=2,031,616)。

應用程序的地址空間

儘管和Windows XP的應用程序設計類似,但Windows CE應用程序地址空間有一個巨大的不同影響到應用程序。在Windows CE之下,一個應用程序被限制在虛擬內存空間中它自己的32MB slot(槽)和 32MB 的slot 1中,slot 1用來加載基於XIP的DLL(譯者注:Windows CE將虛擬地址空間分爲33個slot,每個slot 32MB,序號從0-32 ,附插圖c-1,c-2)。當系統只有4MB RAM的時候,分配給應用程序32MB的虛擬地址空間看起來是比較合理的,Win32的程序員在使用這個2-GB的虛擬地址空間的時候,必須記住對Windows CE應用程序的虛擬地址空間限制。

圖7-1展示了一個應用程序的64-MB虛擬地址空間,其中包括32MB用於XIP的DLL空間。

 

圖7-1     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中執行前必須先解壓縮。

       被保留給XIP DLLs的32MB 應用程序地址空間的較高位置。Windows CE 映射這些XIP DLLs的代碼進入這個空間(譯者注:即較高空間),而可讀寫段被映射進較低位置。從Windows CE 4.2開始,在ROM中的純資源的DLL將被載入到應用程序64MB空間之外的虛擬內存空間。

 

腳註

在PocketPC這樣的移動式系統中,當用戶按下關閉按鈕時系統將不會被真正的關閉,系統進入一種低功耗的掛起狀態。

 

內存分配的不同類型

       一個Windows CE 應用程序有許多不同的內存分配方式。在內存食物鏈的底端是Virtualxxx 函數,它們直接保留,提交和釋放(free)虛擬內存頁。接下來的是堆(heap) API。堆是系統爲應用程序保留的內存區域。堆有兩種風味:當應用程序啓動時自動默認分配的本地堆(local heap),以及能夠由程序手動創建的分離堆(separate heap)。在堆API之後是靜態數據,數據塊是被編譯器定義好的或者由程序手動創建的。最後,我們來看棧,這是程序爲函數存儲變量的區域。

       一個Windows CE不支持的Win32 內存API是全局堆(global heap)。全局堆API包括Global­Alloc,GlobalFree和GlobalRealloc,將不會出現在Windows CE中(譯者注:很奇怪,我在Windows CE 中仍然可以使用這幾個API,並且工作正常,好像Microsoft並沒有把它們完全去掉)。全局堆只是從Windows 3.x的Win16 時期繼承而來。在Win32中,全部和本地的堆很類似,全局內存一個獨特用法是,爲剪貼板的數據分配內存,在Windows CE中已經被本地堆替代並加上了句柄。

       在Windows CE中最小化內存使用的關鍵是選擇與內存塊使用模型相匹配的恰當的內存分配策略。我將回顧一下這些內存類型然後講述Windows CE應用程序中的最小化內存使用策略。

虛擬內存

       虛擬內存是內存類型中最基礎的。系統調用虛擬內存API來爲其他類型內存分配內存。包括堆和棧。虛擬內存API,包括VirtualAlloc,VirtualFree和VirtualReSize函數,這些可以直接操作應用程序虛擬內存空間的虛擬內存頁面。頁面可以保留,提交給物理內存,或使用這些函數釋放。

分配虛擬內存

       分配和保留虛擬內存是同過這個函數完成的:

LPVOID VirtualAlloc (LPVOID lpAddress, DWORD dwSize,

                     DWORD flAllocationType,

                     DWORD flProtect);

 

 

VirtualAlloc 的第一個參數是要分配內存區域的地址。當你使用VirtualAlloc來提交一塊以前保留的內存塊的時候,lpAddress參數可以用來識別以前保留的內存塊。如果這個參數是NULL,系統將會決定分配內存區域的位置,並且圍繞64-KB的範圍(譯者注:就是前面說提及的最小內存分配尺寸)。第二個參數是dwSize,要分配或者保留的區域的大小。這個參數以字節爲單位,而不是頁,系統會根據這個大小一直分配到下頁的邊界。

 

flAllocationType參數指定了分配的類型,你可以指定或者合併以下標誌:MEM_COMMIT,MEM_AUTO_COMMIT,MEM_RESERVE和MEM_TOP_DOWN。MEM_COMMIT標誌分配程序使用的內存,MEM_RESERVE保留虛擬地址空間以便以後提交。保留的頁不能存取直到調用VirtualAlloc的時候再次指定了MEM_COMMIT標誌。第三個標誌,MEM_TOP_DOWN,告訴系統從最高可允許的虛擬地址開始映射應用程序。

The MEM_AUTO_COMMIT標誌是唯一一個Windows CE最方便的標誌,當這個參數被指定了之後,內存塊立即被保留,當其中的頁被第一次存取的時候,系統將自動提交該頁。這允許你分配大塊的虛擬內存而不需要顧及系統和實際RAM分配直到當前頁被第一次使用。自動提交內存的缺點是,物理RAM需要退回當頁面被第一次訪問時可能不可用的頁面。在這種情形下,系統將產生一個異常(exception)(譯者注:可能會出現因爲無法訪問而出錯)。

       VirtualAlloc可以通過並行多次調用提交一個區域的部分或全部來保留一個大的內存區域。多重調用提交同一塊區域不會引起失敗。這使得一個應用程序保留內存後可以隨意提交將被寫的頁。當這種方式不在有效的時候,它會釋放應用程序通過檢測被保留頁的狀態看它是否在提交調用之前已經被提交。

       flProtect參數指定了被分配區域的訪問保護方式。這些不同的標誌被總結在下面的列表中:

PAGE_READONLY

該區域爲只讀。如果應用程序試圖訪問區域中的頁的時候,將會被拒絕訪問。

PAGE_READWRITE

區域可被應用程序讀寫。

PAGE_EXECUTE

區域包含可被系統執行的代碼。試圖讀寫該區域的操作將被拒絕。

PAGE_EXECUTE_READ

區域包含可執行代碼,應用程序可以讀該區域。

PAGE_EXECUTE_READWRITE

區域包含可執行代碼,應用程序可以讀寫該區域。

PAGE_GUARD

區域第一次被訪問時進入一個STATUS_GUARD_PAGE異常,這個標誌要和其他保護標誌合併使用,表明區域被第一次訪問的權限。

PAGE_NOACCESS

任何訪問該區域的操作將被拒絕。

PAGE_NOCACHE

RAM中的頁映射到該區域時將不會被微處理器緩存(cached)。

PAGE_GUARD和PAGE_NOCHACHE標誌可以和其他標誌合併使用以進一步指定頁的特徵。PAGE_GUARD標誌指定了一個防護頁(guard page),即當一個頁被提交時會因第一次被訪問而產生一個one-shot異常,接着取得指定的訪問權限。PAGE_NOCACHE防止當它映射到虛擬頁的時候被微處理器緩存。這個標誌方便設備驅動使用直接內存訪問方式(DMA)來共享內存塊。

區域和頁

       在我繼續談論虛擬內存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[i] = 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[i] = 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頁)。圖7-2顯示了這個假設的情況。

 

圖7-2被保留的區域有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補齊。

釋放分離堆中的內存

       你可以通過以下調用釋放內存塊:

BOOL HeapFree (HANDLE hHeap, DWORD dwFlags, LPVOID lpMem);

dwFlags參數唯一的標誌是HEAP_NO_SERIALIZE,當hHeap包含堆句柄時,lpMem參數指向要釋放的內存塊。

改變和查詢分離堆中內存的大小:

       你可以通過以下調用改變堆大小。

    LPVOID HeapReAlloc (HANDLE hHeap, DWORD dwFlags, LPVOID lpMem,

                    DWORD dwBytes);

    dwFlags參數包含三種標誌的組合:HEAP_NO_SERIALIZE,HEAP_REALLOC_IN_PLACE_ONLY和HEAP_ZERO_ MEMORY。其中較新的標誌是HEAP_REALLOC_IN_PLACE_ONLY,這個參數告訴堆的管理者,找不到要分配的塊的空間,重分配操作失敗。這個標誌方便的地方在於當你有了一些指向內存數據塊的指針,並且你不想改變內存塊。lpMem參數是一個指向要改變大小的內存塊的指針,dwBytes參數是被請求的新內存塊的大小。注意,HeapReAlloc中HEAP_REALLOC_IN_PLACE_ONLY標誌提供和LocalReAlloc中LMEM_MOVEABLE相反的作用。HEAP_REALLOC_IN_PLACE_ONLY防止在分離堆中對內存塊默認的移動操作。而LMEM_MOVEABLE允許本地堆中對內存塊的默認移動操作。如果HeapReAlloc成功,就返回一個指向內存塊的指針,否則就返回NULL。除非你指定內存塊不可重新定位,那麼當內存塊因爲堆中空間不足時將不得不重定位,因此造成返回指針的值將與原來不同。

       要決定實際的內存塊大小,你可以作以下調用:

DWORD HeapSize (HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);

參數就像你想象的:有堆的句柄,單選標誌HEAP_NO_SERIALIZE,和指向內存塊的指針。

銷燬一個分離堆

       你可以通過以下調用完全釋放一個堆:

BOOL HeapDestroy (HANDLE hHeap);

在堆中單個的內存塊並不需要在銷燬堆前釋放。

最後一個是寫DLL時比較有價值的函數:

HANDLE GetProcessHeap (VOID);

返回的是調用DLL時進程的本地堆的句柄。這個函數允許一個DLL在調用者進程的本地堆中分配內存。GetProcessHeap返回的句柄可以供其他堆調用使用,HeapDestroy除外。

       棧是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++應用程序有一個預先定義好的內存塊,這是由應用程序被裝載時自動分配的。這些塊被用來存儲靜態分配的字符串,緩衝區和全局變量,同時也包括通過靜態連接到應用程序的靜態庫函數中的緩衝區。這些對C程序員來說都不陌生,但是在Windows CE下,這是最後一塊可以在RAM之外壓縮的空間(譯者注:作者的意圖是儘可能壓縮內存佔有率)。

       Windows CE分配給應用程序兩塊RAM中的內存塊存放靜態數據,一個是可讀寫數據(read/write data)和只讀數據(read only data)。因爲這些區域是基於頁分配的,所以你可以在一頁的靜態數據開始到下一頁開始之間找到一些剩餘空間。細微調整Windows CE應用程序就是要寫滿這些剩餘的空間。如果你在靜態數據區有空間,最好把一個或兩個緩衝區放到靜態數據區,避免動態分配緩衝區。

       另一個值得考慮的事情是你是否在寫一個基於ROM的應用程序。你要把儘可能多的數據移到只讀靜態數據區。Windows CE不會分配只讀的RAM給基於ROM的應用程序。並且,ROM頁會直接映射到虛擬地址空間。這實際上就給你了一個無限制的只讀空間,而且不會影響到應用程序對RAM的需求。

       確定靜態數據區大小的方法是查看連接器產生的映象(map)文件。映象文件主要用於調試(debug)目的來確定函數和數據的位置。但是如果你知道查看什麼地方的話,它也可以用來顯示靜態數據的大小。列表7-1顯示了一個由Visual C++產生的示例映象文件的一部分。

列表7-1。映象文件的頂部顯示了應用程序數據段的大小

memtest
 
Timestamp is 34ce4088 (Tue Jan 27 12:16:08 1998)
 
Preferred load address is 00010000
 
Start         Length     Name                   Class
0001:00000000 00006100H .text                   CODE
0002:00000000 00000310H .rdata                  DATA
0002:00000310 00000014H .xdata                  DATA
0002:00000324 00000028H .idata$2                DATA
0002:0000034c 00000014H .idata$3                DATA
0002:00000360 000000f4H .idata$4                DATA
0002:00000454 000003eeH .idata$6                DATA
0002:00000842 00000000H .edata                  DATA
0003:00000000 000000f4H .idata$5                DATA
0003:000000f4 00000004H .CRT$XCA                DATA
0003:000000f8 00000004H .CRT$XCZ                DATA
0003:000000fc 00000004H .CRT$XIA                DATA
0003:00000100 00000004H .CRT$XIZ                DATA
0003:00000104 00000004H .CRT$XPA                DATA
0003:00000108 00000004H .CRT$XPZ                DATA
0003:0000010c 00000004H .CRT$XTA                DATA
0003:00000110 00000004H .CRT$XTZ                DATA
0003:00000114 000011e8H .data                   DATA
0003:000012fc 0000108cH .bss                    DATA
0004:00000000 000003e8H .pdata                  DATA
0005:00000000 000000f0H .rsrc$01                DATA
0005:000000f0 00000334H .rsrc$02                DATA
 Address         Publics by Value              Rva+Base     Lib:Object
 
0001:00000000       _WinMain                   00011000 f   memtest.obj
0001:0000007c       _InitApp                   0001107c f   memtest.obj
0001:000000d4       _InitInstance              000110d4 f   memtest.obj
0001:00000164       _TermInstance              00011164 f   memtest.obj
0001:00000248       _MainWndProc               00011248 f   memtest.obj
0001:000002b0       _GetFixedEquiv             000112b0 f   memtest.obj
0001:00000350       _DoCreateMain              00011350 f   memtest.obj.
 
 在列表7-1中的映象文件指出了EXE文件有五個區。區0001是文本段,包含程序中可執行的代碼。區0002包含只讀(read-only)靜態數據。區0003包含可讀寫(read/write)靜態數據。區0004包含調用其他DLL的固定表。最後,區0005是資源區,包含應用程序的資源,例如菜單和對話框模板。

讓我們來看看.data,.bss和.rdata行。.data區包含已初始化的可讀寫數據。如果你這樣初始化了一個全局變量:

static HINST g_hLoadlib = NULL;

g_loadlib變量將結束在.data段末尾。.bss段包含未初始化的可讀寫數據。一個緩衝被定義如下:

static BYTE g_ucItems[256];

以.bss段爲結尾。最後一個段.rdata,包含只讀數據。你使用const關鍵字定義的靜態數據結束在.rdata段。有一個結構的例子,使我用來作消息查詢表的:

// Message dispatch table for MainWindowProc

const struct decodeUINT MainMessages[] = {

    WM_CREATE, DoCreateMain,

    WM_SIZE, DoSizeMain,

    WM_COMMAND, DoCommandMain,

    WM_DESTROY, DoDestroyMain,

};

 

    .data和.bss塊被摺疊進0003區,如果你將第三區的所有塊大小加起來,總共爲0x2274,或8820字節。爲和下頁對齊,讀寫數據區將佔9頁,那麼就有396字節未使用(譯者注:1024*9-8820=396)。因此在這個例子中,把一個或者兩個緩衝區放入靜態數據區比較合適。只讀數據段0002區,包括.rdata,佔0x0842或2114字節,佔3頁,剩餘958字節,幾乎是一整頁。在這種情況下,移動75字節的常量數據從只讀段到可讀寫段將在應用程序加載時節約一頁的RAM。

字符串資源

有一個經常忘記的只讀區域時應用程序的資源段,像我前面在第四章提到的Windows CE的新特性有一個LoadString函數,值得再次重複。如果你調用LoadString時指向緩衝區的指針寫0,函數將返回一個指向資源段中字符串的指針。例子如下:

LPCTSTR pString;

 

pString  = (LPCTSTR)LoadString (hInst, ID_STRING, NULL, 0)

返回的字符串是隻讀的,但是它允許你應用字符串而不需要分配一個緩衝給字符串。這裏警告一下,字符串不能以0結尾,除非你在資源編譯器命令行中加了-n開關。不管如何,單詞必須是先於字符串資源長度(譯者注:作者此處意思可能是說長度包含字符串資源的長度)。

選擇適當的內存類型

       現在我們已經看過了不同類型的內存,是時候來考慮最好的使用辦法了。對大的內存塊來說,直接分配虛擬內存是最好的辦法,一個應用程序可以保留很多的地址空間(直到應用程序32MB的限制)但是只能在一個時間提交必須的頁。直接分配虛擬內存是最靈活的內存分配方式,它把頁間隔(granularity)的負擔以及對保留頁和提交頁都交由我們負擔。

       本地堆是很方便的,它不需要創建並且會自動隨着需求擴大。但碎片是這裏的問題。但是要考慮到Pocket PC的應用程序可能會運行幾星期或幾個月的時間。在Pocket PC上沒有關閉電源的按鈕,只有掛起命令。因此,你考慮內存碎片的時候不要假設用戶會打開應用程序,改變一個項目,然後關閉它。用戶可能打開程序然後讓它一直運行以至於程序就像一個快捷方式(quick click away)。

       分離堆的優點是當你不用時可以銷燬,把碎片消滅在萌芽狀態。有一點不好的就是分離堆需要手動創建和銷燬。

       靜態數據區是放置一兩個緩衝區的好地方,因爲頁面是已經被分配的。管理靜態數據的關鍵是使靜態數據段大小儘可能地接近,但是要超過你目標處理器的頁面的大小。當常量數據在只讀段中,往往較好的辦法是把它移到可讀寫段中。但當應用程序被燒到ROM中時,你不要這麼做。常量數據越多會比較好,因爲它不佔RAM。只讀段方便應用程序從對象存儲區啓動,因爲只讀頁能通過操作系統丟棄和重載。

       棧用起來比較簡單而且到處存在。唯一要考慮的是棧的最大尺寸和在的內存環境下擴大棧的問題。確定你的應用程序在關閉的時候不需要大量棧空間。當程序被關閉時,如果系統掛起你程序中的一個線程,用戶可能會丟失數據。這會使顧客不滿意。

低內存環境

       當系統運行在一個低RAM環境中,應用程序將調整並最小化它們的內存使用。Windows CE運行在一個幾乎永久的低內存環境中。Pocket PC被特意設計爲運行低內存環境。在Pocket PC中的應用程序沒有關閉按鈕,當系統需要更多內存時,外殼(shell)自動關閉這些程序。正因爲如此,Windows CE有許多方法來管理運行在低內存系統中的程序。

WM_HIBERNATE 消息

       Windows CE第一個最明顯的變化時是增加了WM_HIBERNATE消息。Windows CE的shell發送消息給最頂層的有WS_OVERLAPPED式樣(那就是說,既沒有WS_POPUP也沒有WS_CHILD式樣)和WS_VISIBLE式樣的窗口。這些限制將允許大多數程序至少有一個窗口可以接受WM_HIBERNATE消息。有一個例外就是,當應用程序不能真正結束程序而只是簡單隱藏所有窗口。這種方式允許應用程序可以快速啓動,因爲它下次只是顯示窗口。但是這就意味着,當用戶想關閉它們的時候仍然佔據着RAM。這對程序設計來說是正確的,但是不應用在Windows CE中,這種方式會造成程序被隱藏時總處在冬眠(hibernate)模式,因爲它們永遠接收不到WM_HIBERNATE消息。

       Shell發送WM_HIBERNATE消息給最頂層的窗口在Z軸相反的位置(reverse Z-order)直到內存被釋放,使可用內存超過系統預先的限制。當應用程序接收到一個WM_HIBERNATE消息,它會儘可能減少內存佔有程度。這包括釋放被緩衝(cached)的數據;釋放GDI對象,例如字體,位圖和畫刷;並銷燬任何窗口控件。從本質上來說,應用程序將會減少內存到維持它內部狀態的最小值。

       如果發送WM_HIBERNATE消息給後臺的應用程序不能釋放足夠的內存以便使系統離開內存被限制的狀態。WM_HIBERNATE消息將會發送給前臺程序。如果你正在冬眠的程序開始銷燬窗口的控件,你必須確保它不是前臺的程序,控件消失不會給用戶帶來興奮的感覺而是困惑。

內存限度

       Windows CE監視系統自由的RAM,並對越來越少的RAM作出響應。當很少內存可用時,Windows CE首先發送WM_HIBERNATE消息,接下來會限制可能的內存分配。下面的兩個表顯示了Explorer shell和Pocket PC引發的低內存事件的自由內存級別。Windows CE定義了是個內存狀態:normal,limited,low和critical。系統的內存狀態依賴於整個系統有多少內存可用。這些限制都比4-KB頁要高,因爲系統具有內存最小分配限制,就像7-1和7-2的表。

表7-1 Explorer Shell的內存限度

 

事件

自由內存

1024-Page Size

自由內存

4096-Page Size

註解

Limited-memory state

128 KB

160 KB

發送 WM_HIBERNATE 消息給in reverse Z-order的應用程序。釋放棧空間並回收利用。

Low-memory state

64 KB

96 KB

限制虛擬內存分配爲16 KB。 顯示Low-memory對話框。

Critical-memory state

16 KB

48 KB

限制虛擬內存分配爲8KB。

表7-2 Pocket PC的內存限度

 

事件

自由內存

1024-Page Size

自由內存

4096-Page Size

註解

Hibernate threshold

200 KB

224 KB

發送 WM_HIBERNATE 消息給in reverse Z-order的應用程序。

Limited-memory state

128 KB

160 KB

開始關閉在 reverse Z-order上的應用程序。釋放棧空間並回收利用。

Low-memory state

64 KB

96 KB

限制虛擬內存分配爲16 KB。

Critical-memory state

16 KB

48 KB

限制虛擬內存分配爲8 KB。

 

       這些內存狀態的影響是共享剩餘的財富。首先,WM_HIBERNATE消息被髮送給應用程序,並請求減少它們的內存佔有率,當應用程序被髮送了一個WM_HIBERNATE消息後,系統將檢測內存級別,確認是否可用內存在限度之上,如果可用內存不足,WM_HIBERNATE消息將被髮送給下一個程序。這會持續到所有程序被髮送了WM_HIBERNATE消息。

       Exlporer shell和Pocket PC的低內存策略在這點上有區別。如果Explorer shell運行時,系統會顯示OOM(out of memory)對話框,並請用戶確認是否關閉一個應用程序或把對象存儲區的RAM重新劃分給程序內存。如果用戶選擇了其中之一,仍然沒有足夠的內存,out of memory對話框將會再次出現,這個過程會重複,直到H/PC有足夠的在限度之上的內存。

       對Pocket PC來說,操作稍微有些不同。Pocket PC shell自動開始關閉最近最少使用的應用程序,而不詢問用戶。如果關閉除了前臺程序和shell之外的所有程序,仍然沒有足夠內存,系統將會使用其他的技術來從棧開始清理自由的頁,並限制虛擬內存分配。

       如果在任何一個系統上,應用程序被請求關閉卻沒有關閉,系統在8秒鐘後將會清理該應用程序。這就是一個應用程序不要分配大量的棧空間的原因。如果應用程序被關閉而導致低內存環境,很可能是棧空間不能分配,應用程序將被掛起。如果發生在系統請求應用程序關閉以後,可能是清除內存以後沒有適當的恢復狀態。

       在low和critical-memory狀態,應用程序被限制了內存分配的大小。在這些情況下,甚至還有可以滿足要求的內存剩餘情況下,請求分配大過允許限度的虛擬內存將會被拒絕。記住,並不止是虛擬內存分配被限制,堆分配和棧分配也被禁止,要滿足分配請求,那麼分配時需要虛擬內存在可允許的限制之上。

       我這裏要指出,發送WM_HIBERNATE消息和自動關閉應用程序是由系統的shell執行的。在一個OEM自己可以編寫shell的嵌入式系統中,實現WM_HIBERNATE消息和其他內存管理技術是OEM廠商的責任。幸運的是,Microsoft Windows CE PlatForm Builder提供了Exlporer shell實現WM_HIBERNATE消息的源碼。

       這裏不言而喻,應用程序要檢查任何內存分配調用的返回代碼,但是因爲這裏還沒說,所以我還是要說。檢查內存分配調用的返回代碼。在Windows CE中比在桌面版本的Windows中可能有更多的機會導致內存分配失敗。應用程序必須很好地實現拒絕內存分配。

       Windows CE不支持完全的Win32內存管理API,但是很清楚這裏有對WindowsCE設備受限制內存的足夠支持。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章