Windows核心编程 第十八章 堆栈
第1?8章?堆?棧
????對內(nèi)存進行操作的第三個機制是使用堆棧。堆棧可以用來分配許多較小的數(shù)據(jù)塊。例如,若要對鏈接表和鏈接樹進行管理,最好的方法是使用堆棧,而不是第?1?5章介紹的虛擬內(nèi)存操作方法或第1?7章介紹的內(nèi)存映射文件操作方法。堆棧的優(yōu)點是,可以不考慮分配粒度和頁面邊界之類的問題,集中精力處理手頭的任務。堆棧的缺點是,分配和釋放內(nèi)存塊的速度比其他機制要慢,并且無法直接控制物理存儲器的提交和回收。
從內(nèi)部來講,堆棧是保留的地址空間的一個區(qū)域。開始時,保留區(qū)域中的大多數(shù)頁面沒有被提交物理存儲器。當從堆棧中進行越來越多的內(nèi)存分配時,堆棧管理器將把更多的物理存儲器提交給堆棧。物理存儲器總是從系統(tǒng)的頁文件中分配的,當釋放堆棧中的內(nèi)存塊時,堆棧管理器將收回這些物理存儲器。
18.1?進程的默認堆棧
????當進程初始化時,系統(tǒng)在進程的地址空間中創(chuàng)建一個堆棧。該堆棧稱為進程的默認堆棧。
按照默認設置,該堆棧的地址空間區(qū)域的大小是?1?MB。但是,系統(tǒng)可以擴大進程的默認堆棧,使它大于其默認值。當創(chuàng)建應用程序時,可以使用?/?H?E?A?P鏈接開關,改變堆棧的1?M?B默認區(qū)域大小。由于?D?L?L沒有與其相關的堆棧,所以當鏈接?D?L?L時,不應該使用?/?H?E?A?P鏈接開關。
/?H?E?A?P鏈接開關的句法如下:
/?H?E?A?P:reserve[,commit]
許多Wi?n?d?o?w?s函數(shù)要求進程使用其默認堆棧。例如,?Windows?2000的核心函數(shù)均使用U?n?i?c?o?d?e字符和字符串執(zhí)行它們的全部操作。如果調(diào)用Wi?n?d?o?w?s函數(shù)的A?N?S?I版本,那么該A?N?S?I版本必須將A?N?S?I字符串轉(zhuǎn)換成U?n?i?c?o?d?e字符串,然后調(diào)用同一個函數(shù)的?U?n?i?c?o?d?e版本。為了進行字符串的轉(zhuǎn)換,A?N?S?I函數(shù)必須分配一個內(nèi)存塊,以便放置U?n?i?c?o?d?e版本的字符串。該內(nèi)存塊是從你的進程的默認堆棧中分配的。?Wi?n?d?o?w?s的其他許多函數(shù)需要使用一些臨時內(nèi)存塊,這些內(nèi)存塊是從進程的默認堆棧中分配的。
由于進程的默認堆棧可供許多Wi?n?d?o?w?s函數(shù)使用,你的應用程序有許多線程同時調(diào)用各種Wi?n?d?o?w?s函數(shù),因此對默認堆棧的訪問是順序進行的。
單個進程可以同時擁有若干個堆棧。這些堆棧可以在進程的壽命期中創(chuàng)建和撤消。但是,
默認堆棧是在進程開始執(zhí)行之前創(chuàng)建的,并且在進程終止運行時自動被撤消。不能撤消進程的默認堆棧。每個堆棧均用它自己的堆棧句柄來標識,用于分配和釋放堆棧中的內(nèi)存塊的所有堆棧函數(shù)都需要這個堆棧句柄作為其參數(shù)。
可以通過調(diào)用G?e?t?P?r?o?c?e?s?s?H?e?a?p函數(shù)獲取你的進程默認堆棧的句柄:
HANDLE?GetProcessHeap();
18.2?為什么要創(chuàng)建輔助堆棧
除了進程的默認堆棧外,可以在進程的地址空間中創(chuàng)建一些輔助堆棧。由于下列原因,你可能想要在自己的應用程序中創(chuàng)建一些輔助堆棧:
??保護組件。
??更加有效地進行內(nèi)存管理。
??進行本地訪問。
??減少線程同步的開銷。
??迅速釋放。
下面讓我們來詳細說明每個原因。
18.2.1?保護組件
????假如你的應用程序需要保護兩個組件,一個是節(jié)點結(jié)構(gòu)的鏈接表,一個是?B?R?A?N?C?H結(jié)構(gòu)的二進制樹。你有兩個源代碼文件,一個是?L?n?k?L?s?t?.?c?p?p,它包含負責處理
N?O?D?E鏈接表的各個函數(shù),另一個文件是B?i?n?Tr?e?e?.?c?p?p,它包含負責處理
分支的二進制樹的各個函數(shù)。
????如果節(jié)點和分支一道存儲在單個堆棧中,那么這個組合堆棧將類似圖1?8?-?1所示的樣子。
????現(xiàn)在假設鏈接表代碼中有一個錯誤,它使節(jié)點?1后面的8個字節(jié)不小心被改寫了,從而導致分支?3中的數(shù)據(jù)被破壞。當B?i?n?Tr?e?e?.?c?p?p文件中的代碼后來試圖遍歷二進制樹時,它將無法進行這項操作,因為它的內(nèi)存已經(jīng)被破壞。當然,這使你認為二進制樹代碼中存在一個錯誤,而實際上錯誤是在鏈接表代碼中。由于不同類型的對象混合放在單個堆棧中,因此跟蹤和確定錯誤將變得非常困難。
????通過創(chuàng)建兩個獨立的堆棧,一個堆棧用于存放節(jié)點,另一個堆棧用于存放分支,就能夠確定你的問題。你的鏈接表代碼中的一個小錯誤不會破壞你的二進制樹的完整性。反過來,二進制樹中的小錯誤也不會影響鏈接表代碼中的數(shù)據(jù)完整性。但是,你的代碼中的錯誤仍然
可能導致對堆棧進行雜亂的內(nèi)存寫操作,不過出現(xiàn)這種情況的可能性很小。
18.2.2?更有效的內(nèi)存管理
????通過在堆棧中分配同樣大小的對象,就可以更加有效地管理堆棧。例如,假設每個節(jié)點結(jié)構(gòu)需要2?4字節(jié),每個分支結(jié)構(gòu)需要3?2字節(jié)。所有這些對象均從單個堆棧中分配。圖?1?8?-?2顯示了單個堆棧中已經(jīng)分配的若干個節(jié)點和分支對象占滿了這個堆棧。如果節(jié)點?2和節(jié)點4被釋放,堆棧中的內(nèi)存將變成許多碎片。這時,如果試圖分配分支結(jié)構(gòu),那么盡管分支只需要3?2個字節(jié),而實際上可以使用的有4?8個字節(jié),但是分配仍將失敗。
如果每個堆棧只包含大小相同的對象,那么釋放一個對象后,另一個對象就可以恰好放入被釋放的對象空間中。
18.2.3?進行本地訪問
????每當系統(tǒng)必須在R?A?M與系統(tǒng)的頁文件之間進行?R?A?M頁面的交換時,系統(tǒng)的運行性能就會受到很大的影響。如果經(jīng)常訪問局限于一個小范圍地址的內(nèi)存,那么系統(tǒng)就不太可能需要在?R?A?M與磁盤之間進行頁面的交換。
????所以,在設計應用程序的時候,如果有些數(shù)據(jù)將被同時訪問,那么最好把它們分配在互相靠近的位置上。讓我們回到鏈接表和二進制樹的例子上來,遍歷鏈接表與遍歷二進制樹之間并無什么關系。如果將所有的節(jié)點放在一起(放在一個堆棧中),就可以使這些節(jié)點位于相鄰的頁面上。實際上,若干個節(jié)點很可能恰好放入單個物理內(nèi)存頁面上。遍歷鏈接表將不需要?C?P?U為了訪問每個節(jié)點而引用若干不同的內(nèi)存頁面。
如果將節(jié)點和分支分配在單個頁面上,那么節(jié)點就不一定會互相靠在一起。在最壞的情況下,每個內(nèi)存頁面上可能只有一個節(jié)點,而其余的每個頁面則由分支占用。在這種情況下,遍歷鏈接表將可能導致每個節(jié)點的頁面出錯,從而使進程運行得極慢。
18.2.4?減少線程同步的開銷
????正如下面就要介紹的那樣,按照默認設置,堆棧是順序運行的,這樣,如果多個線程試圖同時訪問堆棧,就不會使數(shù)據(jù)受到破壞。但是,堆棧函數(shù)必須執(zhí)行額外的代碼,以保證堆棧對線程的安全性。如果要進行大量的堆棧分配操作,那么執(zhí)行這些額外的代碼會增加很大的負擔,從而降低你的應用程序的運行性能。當你創(chuàng)建一個新堆棧時,可以告訴系統(tǒng),只有一個線程將訪問該堆棧,因此額外的代碼將不執(zhí)行。但是要注意,現(xiàn)在你要負責保證堆棧對線程的安全性。系統(tǒng)將不對此負責。
18.2.5?迅速釋放堆棧
????最后要說明的是,將專用堆棧用于某些數(shù)據(jù)結(jié)構(gòu)后,就可以釋放整個堆棧,而不必顯式釋放堆棧中的每個內(nèi)存塊。例如,當Windows?Explorer遍歷硬盤驅(qū)動器的目錄層次結(jié)構(gòu)時,它必須在內(nèi)存中建立一個樹狀結(jié)構(gòu)。如果你告訴?Windows?Explorer刷新它的顯示器,它只需要撤消包含這個樹狀結(jié)構(gòu)的堆棧并且重新運行即可(當然,假定它將專用堆棧用于存放目錄樹信息)。對于許多應用程序來說,這是非常方便的,并且它們也能更快地運行。
?
之后的內(nèi)容就是介紹一些函數(shù),這里我就把函數(shù)寫出來,具體使用細節(jié)可以查看文檔。
1.創(chuàng)建輔助堆棧 HANDLE HeapCreate( DWORD fdwOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize); 2.從堆棧中分配內(nèi)存塊 PVOID HeapAlloc( HANDLE hHeap, DWORD fdwFlags, SIZE_T dwButes); 3.改變內(nèi)存塊大小 PVOID HeapReAlloc( HANDLE hHeap, DWORD fdwFlags, PVOID pvMem, SIZE_T dwBytes); 4.了解內(nèi)存塊大小 SIZE_T HeapSize( HANDLE hHeap, DWORD fdwFlags, LPCVOID pvMem); 5.釋放內(nèi)存塊 BOOL HeapFree( HANDLE hHeap, DWORD fdwFlags, PVOID pvMem); 6.撤銷堆棧 BOOL HeapDestroy(HANDLE hHeap); 7.獲取現(xiàn)有堆棧信息 DWPRD GetProcessHeaps( DWORD dwNumHeaps, PNANDLE pHeaps); 8.驗證堆棧完整性 BOOL HeapVa;odate( HANDLE hHeap, DWORD fdwFlags, LPCVOID pvMem); 9.合并地址中的空閑內(nèi)存塊回收不包含已經(jīng)分配的地址內(nèi)存塊的存儲器頁面 UINT HeapCompact( HANDLE hHeap, DWORD fdwFlags);10.堆棧上鎖 BOOL HeapLock(HANDLE hHeap); BOOL HeapUnLock(HANDLE hHeap); 11.遍歷堆棧內(nèi)容 BOOL HeapWalk( HANDLE hHeap, PPROCESS_HEAP_ENTRY pHeapEntry);
總結(jié)
以上是生活随笔為你收集整理的Windows核心编程 第十八章 堆栈的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PowerShell-6.文件操作
- 下一篇: Windows核心编程 第十九章 DLL