淺議windows內存管理[來自網絡]

來源:網絡

這裏僅是對windows內存的簡單介紹,適合編寫windows應用程序的人閱讀,主要參考《windows核心編程》及《深入解析windows操作系統》第四版。對windows內存管理的內部機制,將在以後加以介紹。
首先,用戶用到的內存都是虛擬內存,windows內存管理器負責將虛擬地址轉譯成物理內存。對於32位機器,虛擬地址空間就是4G大小,用4個byte就可以覆蓋,因此,32位機的指針大小就是4個字節。
在這4G的地址空間中,windows把它一分爲二,高2G的地址空間屬於操作系統(內核)使用,低2G歸用戶模式(即用戶進程可以訪問)。當然,可以用/3GB開關,將操作系統壓縮到高1G,低3G歸用戶模式。通過這個劃分,操作系統將自己與用戶進程隔離,用戶進程不能直接訪問操作系統地址空間(高2G),從而將操作系統保護起來。另外,每個用戶進程擁有自己的地址空間,即每個用戶進程所使用的低2G空間,僅爲自己可見。舉個例子,進程a的地址0x00E39BA4所存放的數據,跟進程b的0x00E39BA4數據,不是同一個數據。這樣,進程與進程隔離開,保證了進程的獨立性。因此,我們可以這樣理解,每個進程都擁有自己低2G的虛擬地址,高2G的地址空間歸操作系統使用。假如一個進程運行失敗,它既不會使操作系統癱瘓,也不會導致其他進程無法運行,操作系統簡單的把這個用戶進程殺死即可。關於操作系統地址空間如何劃分和使用,以及用戶進程間如何通信,將在以後的文章中給出。
後面,我將着重介紹用戶進程的虛擬地址空間的分佈以及使用,然後,再向大家介紹下編程常用的“堆棧”。
以win2000爲例,如下圖

 用戶進程的虛擬地址空間的分佈

從低位向高位看,首先是NULL指針分配的分區。編程中,爲了防止出現野指針,我們把該指針賦爲NULL,就是讓指針指向這個區域。如果線程試圖根據指針來讀取或寫入該區域,就會引發一個訪問違規。這個分區非常有用,它有助於我們發現程序中的錯誤。
然後是DOS/16位Windows應用程序兼容分區,這個我們暫切不必考慮。
然後是用戶方式分區,這是我們用的最多的一塊區域。用戶進程的代碼、數據均放在該區域中。
再後面是禁止進入區,此區用於將用戶區與內核區隔離。
最後就屬於內核區了,本文暫不介紹。

看到這裏,大家對用戶進程的虛擬地址空間已經有了一個大體的概念,下面我們來介紹怎樣操作虛擬內存。
在最初,我們認爲整個2G空間都是空的。當進程加載並創建成功的時候,有一部分虛擬內存空間已經被使用了,還有一部分是空的。那麼,我們可以通過WindowsApi來申請和使用這些空閒區域。在使用這塊內存前,需要有兩個操作,一個稱作保留,一個稱作提交。先介紹下保留,保留的意思,就是說,這塊內存區域已經有人要了,但是,這塊內存區域到底有沒有映射的存儲器呢?如果你初次保留,是沒有被映射的。提交,就是將物理存儲器映射到內存地址空間。舉個例子,某市剛新建了一個1000米的路段,路段兩旁已經建了一些建築,但還有很大一部分是空的,現在需要規劃、建設一些新項目,這些項目的生殺大權歸建設局局長管。包工頭a跟局長說,“我要在30米處建一座大樓,長10米”,局長說,“好,我劃給你”,然後在筆記本上記錄了下來。這塊土地已經劃給包工頭a,但是包工頭a還沒有拿到合同證書,沒有進入實質性階段,所以還不能直接使用這塊土地。這個過程就是保留。後來包工頭拿到合同證書,正式擁有了這塊土地,這就是提交。應該注意的是,物理存儲器指的並不是物理內存,而是物理內存跟頁文件(用於虛擬內存的硬盤空間)。保留內存區域,用VirtualAlloc;顯式提交,也用VirtualAlloc,只是有所參數不同。在使用完後可以用VirtualFree來釋放該內存區域。
到這裏,我們已經知道如何直接申請和釋放虛擬內存區域了。其實,在寫程序時,我們用到最多的是堆棧了,下面將就這部分內容展開討論。
堆棧包括兩部分,堆和棧。每個進程都至少會有一個堆,在創建進程的時候已經建立好了,堆是進程所有的,也就是說,進程的所有線程會共用一個堆,當然,你也可以自己創建輔助堆。棧呢,是線程所有,每創建一個線程,系統就會爲這個線程保留一個棧。現在我們分別來討論。
首先,介紹下棧。創建“線程”時,系統已經爲棧“保留”一段內存區域,win2000中棧的默認大小是1M,通常,棧會放在較低的虛擬地址上,比如0x080XXXX。棧的使用,是從高位向低位分配的,比如棧的區域爲0x08000000-0x080FF000,那麼最先申請的局部變量放在0x080FF000的位置,然後依次往低處放,直到0x08000000處。0x08000000處是一個守護頁面,如果訪問該頁面,將引發一個異常,即棧溢出所至。另外,棧中還有一個帶保護屬性的頁面,該頁面是棧中已分配(提交)內存的最後一個頁面。棧中存放函數的局部變量,當函數退出時,棧會退,也就是那個保護屬性頁面會往回退,那麼,存放原先函數局部變量的內存頁面已經無效,不能被訪問了。因此,局部變量不用寫程序來顯式釋放。
下面再說說進程的堆。當創建“進程”時,系統會“保留”一段地址空間歸堆使用,win2000中堆默認大小是1M。堆是由堆管理器來維護的,當我們用new或malloc向堆管理器發出請求時,堆管理器會從堆中分出一塊內存區域並返回。剛纔說了,堆默認初始大小是1M,那麼當我們從堆中申請的內存超過了1M,堆管理器會怎樣處理呢。它會通過調用VirtualAlloc,來向內存管理器申請虛擬內存。另外,堆是所有線程共用的,當寫一個單線程程序時,不會有什麼問題,如果是多線程,那麼就存在一個線程同步的問題。在用vc進行編譯的時候,如果選用多線程運行期庫,那麼,我們所調用的new或malloc就是一個加鎖的方法,這樣就能安全正確的使用堆。new完之後我們就可以使用這塊內存了,當不再使用時,我們必須通過delete或free來顯式釋放它,否則,直到進程結束前,這塊內存會一直存在。
至此,本文的主要內容,虛擬地址空間,堆棧,就已經介紹完了。
可能有人會有這樣的疑問,用VirtualAlloc和VirtualFree可以申請和釋放虛擬內存,我們憑什麼使用new和delete或malloc和free呢?爲了解答這個問題,先介紹下地址空間的內存頁面分配粒度。迄今爲止,windows環境下,其分配粒度大小均爲64k。那麼,我們可以把整個虛擬內存空間看作是由一個個以64k爲邊界的64k大小的內存頁面組成。如果用VirtualAlloc來申請內存,不管申請多大,內存管理器都會把整張整張的頁面給你,即使你只申請一個字節的內存,內存管理器也會把一個64k大小的未用的頁面返回給你。這樣勢必會造成內存資源的浪費。而調用new來申請堆中的空間,就不會出現這種情況。堆就是一個內存池,微軟已經對堆管理器分配堆內存的策略做了高度的優化。當我們調用new或malloc時,堆管理器會從堆中找出一塊恰當的內存返回給我們。因此,還是建議大家使用new來申請內存。
 
 
 
---> ---> ---> ---> --->

《c程序的存儲空間佈局》
 
首先要說明的是字節順序問題。由於歷史原因,計算機的數據在內存中的存放方式有兩種:
little endian,big endian。intel 的X86系列使用little endian體系,其他基於Risc的mips計算機
使用big endian體系。
little endian 指的是數據的高字節存在內存的高地址上,低位字節存放在內存的低地址上,big endian
和它相反。當編寫跨平臺和網絡程序時應該注意字節順序。
下面給出一般的c程序存儲佈局:
用戶空間的程序使用低2G的虛擬內存,內核空間使用高2G
高地址    ——0x7FFFFFFF———
                命令行參數和環境變量
                 ——————————
                 棧空間,向下增長
             ___________________
                堆空間,向上增長
            ———————————
                 未初始化的數據
           ———————————
                已初始化的數據
         ———————————
               正文段
低地址—0x00000000————
正文段:cpu執行的機器指令部分
初始化數據段:已初始化的全局變量
未初始化數據段:也成爲bss段,存放沒有初始化的全局變量
棧:自動變量,函數調用的返回地址,調用者環境變量,臨時變量。
堆:動態分配的變量。
參考文獻:
Advanced programming in the UNIX Environment , First Edition  :by W.Richard Steves
Microsoft Windows Internals ,Fourth Edition :by Mark E. Russinovich, David A. Solomon
 
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/yifeng123/archive/2007/07/02/1675727.aspx
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章