談談windows線程棧。
當系統創建線程時會為線程預訂一塊地址空間區域,注意僅僅是預訂。默認情況下預定的這塊區域的大小是1MB,雖然預訂這么多,但是系統并不會給全部區域調撥物理存儲器。默認情況下,僅僅為兩個頁面挑撥。x86系統下每個頁面是4KB.其他頁面會在訪問的時候由系統調撥。這僅僅是在創建線程時,程序員指定CreateThread的第二個參數StackSize為0時才會發揮作用。如果程序員傳入的是非零值,那么調撥的物理存儲器的數量就是這個非零值。
這兩個默認的頁面是從哪里來的呢?原來是在鏈接的時候,系統會將當前編譯器中指定的大小寫入PE文件中。(PE文件即為exe可執行文件),如果StackSize被指定為0,系統就將PE文件中讀出的值作為預訂和調撥的頁面數。在編譯器將預定和挑撥的數量寫入PE文件之前,我們可以用兩種方法,改變編譯器寫入的大小。一種是使用/F選項,另一種是使用/STACK選項。
預定空間后,系統會為線程棧的最上方的兩個頁面調撥物理存儲器,esp指向第一個挑撥頁面的起始位置。第二個頁面也被調撥了頁面,它被稱為防護頁面,具有PAGE_GUARD屬性,當線程試圖訪問防護頁面時,系統會得到通知,會為防護頁面下方的頁面調撥物理存儲器,同時,將原來防護頁面的PAGE_GUARD的屬性去掉,為他下方剛剛調撥的頁面指定PAGE_GUARD屬性,他就變成了防護頁面,此操作,會使防護頁面不斷下移.不斷為頁面調撥物理存儲器。
由于棧空間默認是1M,(即使再大他也是有限的)當調用函數時,或是局部變量增多時,被調撥的頁面越來越多,
使防護頁面不斷下移,最終他會到達這樣一個狀態:(3000,2000,1000僅僅起標記作用并不代表實際情況)
此時,線程訪問地址為3000的頁面,由于它具有保護屬性,因此系統會為地址為2000的頁面調撥物理存儲器,系統會去除頁面地址為3000的PAGE_GUARD屬性,然后給地址為2000的頁面調撥物理存儲器。但此時會與原來不同。
原來為頁面調撥物理存儲器后,會將他上方的頁面的保護屬性去掉,而把他設為保護屬性,現在區別在于,新調撥的頁面并沒有被設為保護屬性。與此同時,系統會拋出EXCEPTION_STACK_OVERFLOW異常,此異常會被系統捕捉,進而通知我們的程序,至于如何響應這個通知,則有程序員自己定義。另外由于系統是在線程訪問具有保護屬性的頁面時,才會為他下方的頁面調撥物理存儲器,地址2000的頁面沒有保護屬性,所以地址為1000的頁面永遠也不會被調撥物理存儲器。
之所以不為地址為1000的頁面,(即棧底)調撥物理存儲器,是為了保護進程內的其他數據。因為當棧增長到超過預定的區域后,他會覆蓋進程地址空間的其他數據。如果線程在引發堆棧溢出異常后繼續使用棧,即訪問地址為1000的頁面,由于此頁面永遠不會被調撥物理存儲區,而訪問未被調撥的頁面會引發違規訪問異常,此時進程將會被終止。
系統故意不為棧底調撥物理存儲器,用來檢測程序溢出的情況。但這樣仍然不能完全阻止這種情況。如:
DWORD WINAPI threadpro(PVOID)
{
int array[10];
array[2000]=100; //假設此時array+2000的地址在棧外
return 0;
}
請問此時會發生訪問違規,導致進程被終止嗎?假設此時array+2000的地址在棧外。
答案是不確定。首先得明確什么情況下會導致訪問違規。
前面我們提到,當訪問沒有被調撥物理存儲器的頁面時會發生訪問違規。此處,array[2000]所處的頁面有沒有被調撥物理頁面我們是不清楚的。當它所處的位置已經被調撥物理存儲器,線程會用100覆蓋此處的值,導致的結果不確定。如果沒有被調撥,就會引發訪問違規,導致進程被結束。 通過這個例子可以知道,即使操作系統采取了不為棧底挑撥物理存儲器的方法,但仍不能解決此類問題。
再看一個例子:
DWORD WINAPI threadpro(PVOID)
{
int array[1000];
array[900]=100; //假設array+900位于防護頁面之下。
return 0;
}
此例中,線程需要1000*4個字節的棧空間,假設此時沒有發生棧溢出的情況,也就是說array+900仍然指向棧內,此時會發生什么情況呢??由于需要大量的棧空間,在開始運行時,系統會為1000*4個字節預訂空間,不會為他們全部調撥物理存儲器。只有當程序真正訪問時才會為他調撥物理存儲器。此時為存在一個問題。如果此時程序要訪問位于防護頁面之下的地址會發生什么情況?
由于位于防護頁面之下的棧空間都沒有被調撥物理存儲器,如果仍然去訪問的話,將會造成訪問違規。
如何解決這個問題呢?原來是編譯器編譯程序的時候,編譯器會獲得系統的頁面大小。當它算出線程執行中需要的棧空間大于系統的頁面大小時他會自動插入代碼來調用檢查函數,檢查函數的作用就是:為程序要訪問的棧空間之前的所有頁面調撥物理存儲器,確保程序去訪問時發生違規訪問的情況。如此例,當程序試圖訪問array+900的棧地址時,系統會為他和他之前的所有棧空間調撥物理存儲器。
檢查函數一般由編譯器廠商用匯編來實現。
文章參考自:windows核心編程(第五版)第三部分,以上僅僅是個人體會,如有紕漏,請不吝賜教。謝謝。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
