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

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