1. 程式人生 > >在應用程式中使用虛擬記憶體——Windows核心程式設計學習手札之十五

在應用程式中使用虛擬記憶體——Windows核心程式設計學習手札之十五

在應用程式中使用虛擬記憶體

——Windows核心程式設計學習手札之十五

Windows提供了3種進行記憶體管理的方法:

1)虛擬記憶體,最適合用來管理大量物件或結構陣列;

2)記憶體對映檔案,最適合用來管理大型資料流(通常來自檔案)以及在單個計算機上執行的多個程序之間共享資料;

3)記憶體堆疊,最適合用來管理大量的小物件;

用於管理虛擬記憶體的函式可以用來直接保留一個地址空間區域,將物理儲存器(來自頁檔案)提交給該區域,並且可以設定你自己的保護屬性。

通過呼叫VirtualAlloc函式,可以在程序的地址空間中保留一個區域:

PVOID VirtualAlloc(

PVOID pvAddress,

SIZE_T dwSize,

DWORD fdwAllocationType,

DWORD fdwProtect);

第一個引數pvAddress包含一個記憶體地址,用於設定想讓系統將地址空間保留在什麼地址,多數情況下,該引數傳遞NULL,用於告訴VirtualAlloc,儲存一個空間地址區域的記錄的系統應該將區域保留在它認為合適的任何地方。系統可以從程序的地址空間的任何位置來保留一個區域,因為不能保證系統可以從地址空間的底部向上或從上面向底部來分配各個區域,使用MEM_TOP_DOWN標誌來說明該分配方式。

分配記憶體時,作業系統尋找一個大小滿足需要的記憶體塊,並返回記憶體塊的地址,由於每個程序有自己的地址空間,可以設定一個基本記憶體地址,在這個地址上讓作業系統保留地址空間區域。例如,將一個從

50MB開始的區域保留在程序的地址空間中,傳遞pvAddress52428800(50*1024*1024),如果該記憶體地址有一個足夠大的空閒區域滿足你的要求,那系統就保留這個區域並返回地址,如果在特定的地址上不存在空閒區域,或者空閒區域不夠大,那系統就不滿足需求,VirtualAlloc函式返回NULL。注意,為pvAddress引數傳遞的任何地址必須始終位於程序的使用者方式分割槽中,否則對VirtualAlloc函式的呼叫就會失敗,導致其返回NULL。地址空間區域總是按照分配粒度的邊界來保留(迄今為止所有的Windows環境下都是64KB),因此,如果試圖在程序地址空間中保留一個從19668992(300*65356+8192)
這個地址開始的區域,系統就會將這個地址保留為64KB的倍數,即19660800(300*65356)開始的區域。如果VirtualAlloc函式滿足了需求,就返回保留區域的基地址,如果傳遞一個指定的地址作為VirtualAllocpvAddress引數,那麼該返回值與傳遞給VirtualAlloc的值相同,並取為64KB的整數倍。

VirtualAlloc函式的第二個引數是dwSize,用於設定想保留的區域大小(以位元組為單位),系統保留的區域必須是CPU頁面大小的倍數,如試圖保留一個跨越62KB的區域,結果就會在使用4KB/8KB16KB頁面的計算機上產生一個跨越64KB的區域。

VirtualAlloc函式的第三個引數是fdwAllocationType,告訴系統想保留一個區域還是提交物理儲存器(VirtualAlloc函式也可以用來提交物理儲存器),若要保留一個地址空間區域,傳遞MEM_RESERVE識別符號作為fdwAllocationType引數的值。如果保留區域預計在很長時間內不會被釋放,那可以在儘可能高的記憶體地址上保留該區域,這樣,該區域就不會從程序地址空間的中間位置上進行保留,因此這個位置可能導致區域分成碎片,如果想讓系統在最高記憶體地址上保留一個區域,需為pvAddress引數和fdwAllocationType引數傳遞NULL,還必須逐位使用ORMEM_TOP_DOWN標誌和MEM_RESERVE標誌連線起來。

VirtualAlloca最後一個引數是fdwProtect,用於指明應該賦予該地址空間區域的保護屬性。與該區域相關聯的保護屬性對對映到該區域的已提交記憶體沒有影響,無論賦予區域的保護屬性是社呢,如果沒有提交任何物理儲存器,那訪問該範圍中的記憶體地址的任何企圖都將導致該執行緒引發一個訪問違規。

當保留一個區域後,必須將物理儲存器提交給該區域,然後才能訪問該區域中包含的記憶體地址,系統從它的頁檔案中將已提交的物理儲存器分配給一個區域,物理儲存器總是按頁面邊界和頁面大小的塊來提交的。若要提交物理儲存器,須再次呼叫VirtualAlloc函式,設定引數fdwAllocationTypeMEM_COMMIT,傳遞的頁面保護屬性一般與呼叫VirtualAlloc來保留區域時使用的保護屬性相同(大多數情況下是PAGE_READWRITE)。在已保留的區域中,須告訴VirtualAlloc函式,要將物理儲存器提交到那裡以及提交多大物理儲存空間,實現這一點,需要在pvAddress引數中設定需要的記憶體地址,並在dwSize引數上設定物理儲存器的大小。

提交物理儲存器的例子:應用程式在X86CPU上執行,保留了一個從地址5242880開始的512KB區域,現在將物理儲存器提交給已保留區域的6KB部分,從2KB的地方開始,直到已保留區域的地址空間。可呼叫帶有MEM_COMMIT標誌的VirtualAlloc函式:

VirtualAlloc((PVOID)(5242880+(2*1024)),6*1024,MEM_COMMIT,PAGE_READWRITE);

例子中,系統必須提交8KB的物理儲存器,地址範圍從52428805251071(5242880+8KB-1),這兩個提交的頁面都擁有PAGE_READWRITE保護屬性,保護屬性在整個頁面單位內生效,同一個記憶體頁面的不同部分不能使用不同的保護屬性,但在不同區域中的一個頁面可以使用兩種以上保護屬性。

若要回收對映到一個區域的物理儲存器,或者釋放這個地址空間區域,可呼叫VirtualFree函式:

BOOL VirtualFree(

LPVOID pvAddress,

SIZE_T dwSize,

DWORD fdwFreeType);

當程序不再訪問區域中的物理儲存器,可以釋放整個保留的區域和所有提交給該區域的物理儲存器,方法是一次呼叫VirtualFree函式。pvAddress是釋放區域的基地址,與該區域被保留時VirtualAlloc函式返回的地址相同,系統知道在特定記憶體地址上的區域大小,因此dwSize引數可以為零,實際,該引數必須傳遞零,否則呼叫VirtualFree失敗,對最後一個引數fdwFreeType,必須傳遞MEM_RELEASE,以告訴系統將所有對映的物理儲存器提給該區域並釋放該區域,當釋放一個區域時,必須釋放該區域保留的所有地址空間。如不想保留128KB的區域,不能只釋放64KB。當想要從一個區域回收某些物理儲存器,但是卻不釋放該區域,設定引數fdwFreeTypeMEM_DECOMMIT標誌。回收時也按照頁面的分配粒度來進行,設定一個頁面中間的一個記憶體地址就可以回收整個頁面,如果pvAddress+dwSize的值位於一個頁面的中間,那包含該地址的整個頁面將被回收,因此,位於pvAddresspvAddress+dwSize範圍內的所有頁面均被回收。