windows核心程式設計--記憶體堆疊

Mobidogs發表於2020-04-04

對記憶體進行操作的第三個機制是使用堆疊。堆疊可以用來分配許多較小的資料塊。例如,若要對連結表和連結樹進行管理,最好的方法是使用堆疊,堆疊的優點是,可以不考慮分配粒度和頁面邊界之類的問題,集中精力處理手頭的任務。堆疊的缺點是,分配和釋放記憶體塊的速度比其他機制要慢,並且無法直接控制物理儲存器的提交和回收。
從內部來講,堆疊是保留的地址空間的一個區域。開始時,保留區域中的大多數頁面沒有被提交物理儲存器。當從堆疊中進行越來越多的記憶體分配時,堆疊管理器將把更多的物理儲存器提交給堆疊。物理儲存器總是從系統的頁檔案中分配的,當釋放堆疊中的記憶體塊時,堆疊管理器將收回這些物理儲存器。

執行緒的堆疊:

每當建立一個執行緒時,系統就會為執行緒的堆疊(每個執行緒有它自己的堆疊)保留一個堆疊空間區域,並將一些物理儲存器提交給這個已保留的區域。按照預設設定,系統保留1 MB的地址空間並提交兩個頁面的記憶體。但是,這些預設值是可以修改的,方法是在你連結應用程式時設定M i c r o s o f t的連結程式的/ S TA C K選項:

 

/STACK:reserve[,commit]

當建立一個執行緒的堆疊時,系統將會保留一個連結程式的/ S TA C K開關指明的地址空間區域。
程式的預設堆疊

當程式初始化時,系統在程式的地址空間中建立一個堆疊。該堆疊稱為程式的預設堆疊。按照預設設定,該堆疊的地址空間區域的大小是1 MB。但是,系統可以擴大程式的預設堆疊,使它大於其預設值。當建立應用程式時,可以使用/ H E A P連結開關,改變堆疊的1 M B預設區域大小。由於D L L沒有與其相關的堆疊,所以當連結D L L時,不應該使用/ H E A P連結開關。/ H E A P連結開關的句法如下:

 

/HEAP:reserve[,commit]

許多Wi n d o w s函式要求程式使用其預設堆疊。特別是widows提供的API。對預設堆疊的訪問是順序進行的。換句話說,系統必須保證在規定的時間內,每次只有一個執行緒能夠分配和釋放預設堆疊中的記憶體塊。如果兩個執行緒試圖同時分配預設堆疊中的記憶體塊,那麼只有一個執行緒能夠分配記憶體塊,另一個執行緒必須等待第一個執行緒的記憶體塊分配之後,才能分配它的記憶體塊。一旦第一個執行緒的記憶體塊分配完,堆疊函式將允許第二個執行緒分配記憶體塊。這種順序訪問方法對速度有一定的影響。如果你的應用程式只有一個執行緒,並且你想要以最快的速度訪問堆疊,那麼應該建立你自己的獨立的堆疊,不要使用程式的預設堆疊。

單個程式可以同時擁有若干個堆疊。這些堆疊可以在程式的壽命期中建立和撤消。但是,預設堆疊是在程式開始執行之前建立的,並且在程式終止執行時自動被撤消。不能撤消程式的預設堆疊。每個堆疊均用它自己的堆疊控制程式碼來標識,用於分配和釋放堆疊中的記憶體塊的所有堆疊函式都需要這個堆疊控制程式碼作為其引數。

可以通過呼叫G e t P r o c e s s H e a p函式獲取你的程式預設堆疊的控制程式碼:

 

HANDLE GetProcessHeap();

為什麼要建立輔助堆疊

除了程式的預設堆疊外,可以在程式的地址空間中建立一些輔助堆疊。由於下列原因,你可能想要在自己的應用程式中建立一些輔助堆疊:

• 保護元件。

• 更加有效地進行記憶體管理。

• 進行本地訪問。

• 減少執行緒同步的開銷。

• 迅速釋放。

保護元件
通過建立多個獨立的堆疊,是資料隔離,且相互獨立的操作。
更有效的記憶體管理

通過在堆疊中分配同樣大小的物件,就可以更加有效地管理堆疊。就是把大小相同的物件放在一個堆疊中進行分配。
進行本地訪問

每當系統必須在R A M與系統的頁檔案之間進行R A M頁面的交換時,系統的執行效能就會受到很大的影響。如果經常訪問侷限於一個小範圍地址的記憶體,那麼系統就不太可能需要在R A M與磁碟之間進行頁面的交換。

所以,在設計應用程式的時候,如果有些資料將被同時訪問,那麼最好把它們分配在互相靠近的位置上。
減少執行緒同步的開銷

正如下面就要介紹的那樣,按照預設設定,堆疊是順序執行的,這樣,如果多個執行緒試圖同時訪問堆疊,就不會使資料受到破壞。但是,堆疊函式必須執行額外的程式碼,以保證堆疊對執行緒的安全性。如果要進行大量的堆疊分配操作,那麼執行這些額外的程式碼會增加很大的負擔,從而降低你的應用程式的執行效能。當你建立一個新堆疊時,可以告訴系統,只有一個執行緒將訪問該堆疊,因此額外的程式碼將不執行。(就是用多個堆疊來減少同步的效能消耗)
迅速釋放堆疊

最後要說明的是,將專用堆疊用於某些資料結構後,就可以釋放整個堆疊,而不必顯式釋放堆疊中的每個記憶體塊。例如,當Windows Explorer遍歷硬碟驅動器的目錄層次結構時,它必須在記憶體中建立一個樹狀結構。如果你告訴Windows Explorer重新整理它的顯示器,它只需要撤消包含這個樹狀結構的堆疊並且重新執行即可(當然,假定它將專用堆疊用於存放目錄樹資訊)。對於許多應用程式來說,這是非常方便的,並且它們也能更快地執行。

如何建立輔助堆疊

你可以在程式中建立輔助堆疊,方法是讓執行緒呼叫H e a p C r e a t e函式:

 

HANDLE HeapCreate(
   DWORD fdwOptions,
   SIZE_T dwInitialSize,
   SIZE_T dwMaximumSize);

當試圖從堆疊分配一個記憶體塊時, H e a p A l l o c函式(下面將要介紹)必須執行下列操作:

1) 遍歷分配的和釋放的記憶體塊的連結表。

2) 尋找一個空閒記憶體塊的地址。

3) 通過將空閒記憶體塊標記為“已分配”分配新記憶體塊。

4) 將新記憶體塊新增給記憶體塊連結表。

從堆疊中分配記憶體塊

若要從堆疊中分配記憶體塊,只需要呼叫H e a p A l l o c函式:

 

PVOID HeapAlloc(
   HANDLE hHeap,
   DWORD fdwFlags,
   SIZE_T dwBytes);

改變記憶體塊的大小

常常需要改變記憶體塊的大小。有些應用程式開始時分配的記憶體塊比較大,然後,當所有資料放入記憶體塊後,再縮小記憶體塊的大小。有些應用程式開始時分配的記憶體塊比較小,後來需要將更多的資料拷貝到記憶體塊中去時,再設法擴大它的大小。如果要改變記憶體塊的大小,可以呼叫H e a p R e A l l o c函式:

 

PVOID HeapReAlloc(
   HANDLE hHeap,
   DWORD fdwFlags,
   PVOID pvMem,
   SIZE_T dwBytes);

瞭解記憶體塊的大小

當記憶體塊分配後,可以呼叫H e a p S i z e函式來檢索記憶體塊的實際大小:

 

SIZE_T HeapSize(
   HANDLE hHeap,
   DWORD fdwFlags,
   LPCVOID pvMem);

釋放記憶體塊

當不再需要記憶體塊時,可以呼叫H e a p F r e e函式將它釋放:

 

BOOL HeapFree(
   HANDLE hHeap,
   DWORD fdwFlags,
   PVOID pvMem);

撤消堆疊

如果應用程式不再需要它建立的堆疊,可以通過呼叫H e a p D e s t r o y函式將它撤消:

 

BOOL HeapDestroy(HANDLE hHeap);

呼叫H e a p D e s t r o y函式可以釋放堆疊中包含的所有記憶體塊,也可以將堆疊佔用的物理儲存器和保留的地址空間區域重新返回給系統。如果該函式執行成功, H e a p D e s t r o y返回T R U E。如果在程式終止執行之前沒有顯式撤消堆疊,那麼系統將為你將它撤消。但是,只有當程式終止執行時,堆疊才能被撤消。如果執行緒建立了一個堆疊,當執行緒終止執行時,該堆疊將不會被撤消。

在程式完全終止執行之前,系統不允許程式的預設堆疊被撤消。如果將程式的預設堆疊的控制程式碼傳遞給H e a p D e s t r o y函式,系統將忽略對該函式的呼叫。

由於程式的地址空間中可以存在多個堆疊,因此可以使用G e t P r o c e s s H e a p s函式來獲取現有堆疊的控制程式碼:

 

DWORD GetProcessHeaps(
   DWORD dwNumHeaps,
   PHANDLE pHeaps);

若要呼叫G e t P r o c e s s H e a p s函式,必須首先分配一個H A N D L E陣列,然後呼叫下面的函式:

 

HANDLE hHeaps[25];
DWORD dwHeaps = GetProcessHeaps(25, hHeaps);
if(dwHeaps > 5) 
{
   //More heaps are in this process than we expected.
} 
else
{
   //hHeaps[0] through hHeap[dwHeaps - 1]
   //identify the existing heaps.
}

注意,當該函式返回時,你的程式的預設堆疊的控制程式碼也包含在堆疊控制程式碼的陣列中。

H e a p Va l i d a t e函式用於驗證堆疊的完整性:

 

BOOL HeapValidate(
   HANDLE hHeap,
   DWORD fdwFlags,
   LPCVOID pvMem);

呼叫該函式時,通常要傳遞一個堆疊控制程式碼,一個值為0的標誌(唯一的另一個合法標誌是H E A P _ N O _ S E R I A L I Z E),並且為p v M e m傳遞N U L L。然後,該函式將遍歷堆疊中的記憶體塊以確保所有記憶體塊都完好無損。為了使該函式執行得更快,可以為引數p v M e m傳遞一個特定的記憶體塊的地址。這樣做可使該函式只檢查單個記憶體塊的有效性。

若要合併地址中的空閒記憶體塊並收回不包含已經分配的地址記憶體塊的儲存器頁面,可以呼叫下面的函式:

 

UINT HeapCompact(
   HANDLE hHeap,
   DWORD fdwFlags);

通常情況下,可以為引數f d w F l a g s傳遞0,但是也可以傳遞H E A P _ N O _ S E R I A L I Z E。

下面兩個函式H e a p L o c k和H e a p U n l o c k是結合在一起使用的:

 

BOOL HeapLock(HANDLE hHeap);
BOOL HeapUnlock(HANDLE hHeap);

這些函式是用於執行緒同步的。當呼叫H e a p L o c k函式時,呼叫執行緒將成為特定堆疊的所有者。如果其他任何執行緒呼叫堆疊函式(設定相同的堆疊控制程式碼),系統將暫停呼叫執行緒的執行,並且在堆疊被H e a p U n l o c k函式解鎖之前不允許它醒來。

H e a p A l l o c、H e a p S i z e和H e a p F r e e等函式在內部呼叫H e a p L o c k和H e a p U n l o c k函式來確保對堆疊的訪問能夠順序進行。自己呼叫H e a p L o c k或H e a p U n l o c k這種情況是不常見的。

最後一個堆疊函式是H e a p Wa l k:

 

BOOL HeapWalk(
   HANDLE hHeap,
   PPROCESS_HEAP_ENTRY pHeapEntry);

該函式只用於除錯目的。它使你能夠遍歷堆疊的內容。可以多次呼叫該函式。

zz
 

相關文章