這里僅是對(duì)windows內(nèi)存的簡(jiǎn)單介紹,適合編寫windows應(yīng)用程序的人閱讀,主要參考《windows核心編程》及《深入解析windows操作系統(tǒng)》第四版。對(duì)windows內(nèi)存管理的內(nèi)部機(jī)制,將在以后加以介紹。
首先,用戶用到的內(nèi)存都是虛擬內(nèi)存,windows內(nèi)存管理器負(fù)責(zé)將虛擬地址轉(zhuǎn)譯成物理內(nèi)存。對(duì)于32位機(jī)器,虛擬地址空間就是4G大小,用4個(gè)byte就可以覆蓋,因此,32位機(jī)的指針大小就是4個(gè)字節(jié)。
在這4G的地址空間中,windows把它一分為二,高2G的地址空間屬于操作系統(tǒng)(內(nèi)核)使用,低2G歸用戶模式(即用戶進(jìn)程可以訪問(wèn))。當(dāng)然,可以用/3GB開(kāi)關(guān),將操作系統(tǒng)壓縮到高1G,低3G歸用戶模式。通過(guò)這個(gè)劃分,操作系統(tǒng)將自己與用戶進(jìn)程隔離,用戶進(jìn)程不能直接訪問(wèn)操作系統(tǒng)地址空間(高2G),從而將操作系統(tǒng)保護(hù)起來(lái)。另外,每個(gè)用戶進(jìn)程擁有自己的地址空間,即每個(gè)用戶進(jìn)程所使用的低2G空間,僅為自己可見(jiàn)。舉個(gè)例子,進(jìn)程a的地址0x00E39BA4所存放的數(shù)據(jù),跟進(jìn)程b的0x00E39BA4數(shù)據(jù),不是同一個(gè)數(shù)據(jù)。這樣,進(jìn)程與進(jìn)程隔離開(kāi),保證了進(jìn)程的獨(dú)立性。因此,我們可以這樣理解,每個(gè)進(jìn)程都擁有自己低2G的虛擬地址,高2G的地址空間歸操作系統(tǒng)使用。假如一個(gè)進(jìn)程運(yùn)行失敗,它既不會(huì)使操作系統(tǒng)癱瘓,也不會(huì)導(dǎo)致其他進(jìn)程無(wú)法運(yùn)行,操作系統(tǒng)簡(jiǎn)單的把這個(gè)用戶進(jìn)程殺死即可。關(guān)于操作系統(tǒng)地址空間如何劃分和使用,以及用戶進(jìn)程間如何通信,將在以后的文章中給出。
后面,我將著重介紹用戶進(jìn)程的虛擬地址空間的分布以及使用,然后,再向大家介紹下編程常用的“堆棧”。
以win2000為例,如下圖
從低位向高位看,首先是NULL指針?lè)峙涞姆謪^(qū)。編程中,為了防止出現(xiàn)野指針,我們把該指針賦為NULL,就是讓指針指向這個(gè)區(qū)域。如果線程試圖根據(jù)指針來(lái)讀取或?qū)懭朐搮^(qū)域,就會(huì)引發(fā)一個(gè)訪問(wèn)違規(guī)。這個(gè)分區(qū)非常有用,它有助于我們發(fā)現(xiàn)程序中的錯(cuò)誤。
然后是DOS/16位Windows應(yīng)用程序兼容分區(qū),這個(gè)我們暫切不必考慮。
然后是用戶方式分區(qū),這是我們用的最多的一塊區(qū)域。用戶進(jìn)程的代碼、數(shù)據(jù)均放在該區(qū)域中。
再后面是禁止進(jìn)入?yún)^(qū),此區(qū)用于將用戶區(qū)與內(nèi)核區(qū)隔離。
最后就屬于內(nèi)核區(qū)了,本文暫不介紹。
看到這里,大家對(duì)用戶進(jìn)程的虛擬地址空間已經(jīng)有了一個(gè)大體的概念,下面我們來(lái)介紹怎樣操作虛擬內(nèi)存。
在最初,我們認(rèn)為整個(gè)2G空間都是空的。當(dāng)進(jìn)程加載并創(chuàng)建成功的時(shí)候,有一部分虛擬內(nèi)存空間已經(jīng)被使用了,還有一部分是空的。那么,我們可以通過(guò)WindowsApi來(lái)申請(qǐng)和使用這些空閑區(qū)域。在使用這塊內(nèi)存前,需要有兩個(gè)操作,一個(gè)稱作保留,一個(gè)稱作提交。先介紹下保留,保留的意思,就是說(shuō),這塊內(nèi)存區(qū)域已經(jīng)有人要了,但是,這塊內(nèi)存區(qū)域到底有沒(méi)有映射的存儲(chǔ)器呢?如果你初次保留,是沒(méi)有被映射的。提交,就是將物理存儲(chǔ)器映射到內(nèi)存地址空間。舉個(gè)例子,某市剛新建了一個(gè)1000米的路段,路段兩旁已經(jīng)建了一些建筑,但還有很大一部分是空的,現(xiàn)在需要規(guī)劃、建設(shè)一些新項(xiàng)目,這些項(xiàng)目的生殺大權(quán)歸建設(shè)局局長(zhǎng)管。包工頭a跟局長(zhǎng)說(shuō),“我要在30米處建一座大樓,長(zhǎng)10米”,局長(zhǎng)說(shuō),“好,我劃給你”,然后在筆記本上記錄了下來(lái)。這塊土地已經(jīng)劃給包工頭a,但是包工頭a還沒(méi)有拿到合同證書(shū),沒(méi)有進(jìn)入實(shí)質(zhì)性階段,所以還不能直接使用這塊土地。這個(gè)過(guò)程就是保留。后來(lái)包工頭拿到合同證書(shū),正式擁有了這塊土地,這就是提交。應(yīng)該注意的是,物理存儲(chǔ)器指的并不是物理內(nèi)存,而是物理內(nèi)存跟頁(yè)文件(用于虛擬內(nèi)存的硬盤空間)。保留內(nèi)存區(qū)域,用VirtualAlloc;顯式提交,也用VirtualAlloc,只是有所參數(shù)不同。在使用完后可以用VirtualFree來(lái)釋放該內(nèi)存區(qū)域。
到這里,我們已經(jīng)知道如何直接申請(qǐng)和釋放虛擬內(nèi)存區(qū)域了。其實(shí),在寫程序時(shí),我們用到最多的是堆棧了,下面將就這部分內(nèi)容展開(kāi)討論。
堆棧包括兩部分,堆和棧。每個(gè)進(jìn)程都至少會(huì)有一個(gè)堆,在創(chuàng)建進(jìn)程的時(shí)候已經(jīng)建立好了,堆是進(jìn)程所有的,也就是說(shuō),進(jìn)程的所有線程會(huì)共用一個(gè)堆,當(dāng)然,你也可以自己創(chuàng)建輔助堆。棧呢,是線程所有,每創(chuàng)建一個(gè)線程,系統(tǒng)就會(huì)為這個(gè)線程保留一個(gè)?!,F(xiàn)在我們分別來(lái)討論。
首先,介紹下棧。創(chuàng)建“線程”時(shí),系統(tǒng)已經(jīng)為棧“保留”一段內(nèi)存區(qū)域,win2000中棧的默認(rèn)大小是1M,通常,棧會(huì)放在較低的虛擬地址上,比如0x080XXXX。棧的使用,是從高位向低位分配的,比如棧的區(qū)域?yàn)?x08000000-0x080FF000,那么最先申請(qǐng)的局部變量放在0x080FF000的位置,然后依次往低處放,直到0x08000000處。0x08000000處是一個(gè)守護(hù)頁(yè)面,如果訪問(wèn)該頁(yè)面,將引發(fā)一個(gè)異常,即棧溢出所至。另外,棧中還有一個(gè)帶保護(hù)屬性的頁(yè)面,該頁(yè)面是棧中已分配(提交)內(nèi)存的最后一個(gè)頁(yè)面。棧中存放函數(shù)的局部變量,當(dāng)函數(shù)退出時(shí),棧會(huì)退,也就是那個(gè)保護(hù)屬性頁(yè)面會(huì)往回退,那么,存放原先函數(shù)局部變量的內(nèi)存頁(yè)面已經(jīng)無(wú)效,不能被訪問(wèn)了。因此,局部變量不用寫程序來(lái)顯式釋放。
下面再說(shuō)說(shuō)進(jìn)程的堆。當(dāng)創(chuàng)建“進(jìn)程”時(shí),系統(tǒng)會(huì)“保留”一段地址空間歸堆使用,win2000中堆默認(rèn)大小是1M。堆是由堆管理器來(lái)維護(hù)的,當(dāng)我們用new或malloc向堆管理器發(fā)出請(qǐng)求時(shí),堆管理器會(huì)從堆中分出一塊內(nèi)存區(qū)域并返回。剛才說(shuō)了,堆默認(rèn)初始大小是1M,那么當(dāng)我們從堆中申請(qǐng)的內(nèi)存超過(guò)了1M,堆管理器會(huì)怎樣處理呢。它會(huì)通過(guò)調(diào)用VirtualAlloc,來(lái)向內(nèi)存管理器申請(qǐng)?zhí)摂M內(nèi)存。另外,堆是所有線程共用的,當(dāng)寫一個(gè)單線程程序時(shí),不會(huì)有什么問(wèn)題,如果是多線程,那么就存在一個(gè)線程同步的問(wèn)題。在用vc進(jìn)行編譯的時(shí)候,如果選用多線程運(yùn)行期庫(kù),那么,我們所調(diào)用的new或malloc就是一個(gè)加鎖的方法,這樣就能安全正確的使用堆。new完之后我們就可以使用這塊內(nèi)存了,當(dāng)不再使用時(shí),我們必須通過(guò)delete或free來(lái)顯式釋放它,否則,直到進(jìn)程結(jié)束前,這塊內(nèi)存會(huì)一直存在。
至此,本文的主要內(nèi)容,虛擬地址空間,堆棧,就已經(jīng)介紹完了。
可能有人會(huì)有這樣的疑問(wèn),用VirtualAlloc和VirtualFree可以申請(qǐng)和釋放虛擬內(nèi)存,我們憑什么使用new和delete或malloc和free呢?為了解答這個(gè)問(wèn)題,先介紹下地址空間的內(nèi)存頁(yè)面分配粒度。迄今為止,windows環(huán)境下,其分配粒度大小均為64k。那么,我們可以把整個(gè)虛擬內(nèi)存空間看作是由一個(gè)個(gè)以64k為邊界的64k大小的內(nèi)存頁(yè)面組成。如果用VirtualAlloc來(lái)申請(qǐng)內(nèi)存,不管申請(qǐng)多大,內(nèi)存管理器都會(huì)把整張整張的頁(yè)面給你,即使你只申請(qǐng)一個(gè)字節(jié)的內(nèi)存,內(nèi)存管理器也會(huì)把一個(gè)64k大小的未用的頁(yè)面返回給你。這樣勢(shì)必會(huì)造成內(nèi)存資源的浪費(fèi)。而調(diào)用new來(lái)申請(qǐng)堆中的空間,就不會(huì)出現(xiàn)這種情況。堆就是一個(gè)內(nèi)存池,微軟已經(jīng)對(duì)堆管理器分配堆內(nèi)存的策略做了高度的優(yōu)化。當(dāng)我們調(diào)用new或malloc時(shí),堆管理器會(huì)從堆中找出一塊恰當(dāng)?shù)膬?nèi)存返回給我們。因此,還是建議大家使用new來(lái)申請(qǐng)內(nèi)存。