內存認識

 

        轉載自:http://blog.csdn.net/qingfeng_happy5/article/details/4322723

        http://blog.csdn.net/TBWood/article/details/5401463

        http://hi.baidu.com/cixiangdong/blog/item/53d9db1041d0a7cca7ef3f34.html

 

對進程地址空間的一點認識

在進入正題前先來談談操作系統內存管理機制的發展歷程,瞭解這些有利於我們更好的理解目前操作系統的內存管理機制。

早期的內存分配機制

在早期的計算機中,要運行一個程序,會把這些程序全都裝入內存,程序都是直接運行在內存上的,也就是說程序中訪問的內存地址都是實際的物理內存地址。當計算機同時運行多個程序時,必須保證這些程序用到的內存總量要小於計算機實際物理內存的大小。那當程序同時運行多個程序時,操作系統是如何爲這些程序分配內存的呢?下面通過實例來說明當時的內存分配方法:

某臺計算機總的內存大小是128M,現在同時運行兩個程序ABA需佔用內存10MB需佔用內存110。計算機在給程序分配內存時會採取這樣的方法:先將內存中的前10M分配給程序A,接着再從內存中剩餘的118M中劃分出110M分配給程序B。這種分配方法可以保證程序A和程序B都能運行,但是這種簡單的內存分配策略問題很多。

 

轉載:“對進程地址空間的一點認識”和“內存分頁機制 <wbr>”


圖一 早期的內存分配方法

問題1:進程地址空間不隔離。由於程序都是直接訪問物理內存,所以惡意程序可以隨意修改別的進程的內存數據,以達到破壞的目的。有些非惡意的,但是有bug的程序也可能不小心修改了其它程序的內存數據,就會導致其它程序的運行出現異常。這種情況對用戶來說是無法容忍的,因爲用戶希望使用計算機的時候,其中一個任務失敗了,至少不能影響其它的任務。

問題2:內存使用效率低。在AB都運行的情況下,如果用戶又運行了程序C,而程序C需要20M大小的內存才能運行,而此時系統只剩下8M的空間可供使用,所以此時系統必須在已運行的程序中選擇一個將該程序的數據暫時拷貝到硬盤上,釋放出部分空間來供程序C使用,然後再將程序C的數據全部裝入內存中運行。可以想象得到,在這個過程中,有大量的數據在裝入裝出,導致效率十分低下。

問題3:程序運行的地址不確定。當內存中的剩餘空間可以滿足程序C的要求後,操作系統會在剩餘空間中隨機分配一段連續的20M大小的空間給程序C使用,因爲是隨機分配的,所以程序運行的地址是不確定的。

分段

爲了解決上述問題,人們想到了一種變通的方法,就是增加一箇中間層,利用一種間接的地址訪問方法訪問物理內存。按照這種方法,程序中訪問的內存地址不再是實際的物理內存地址,而是一個虛擬地址,然後由操作系統將這個虛擬地址映射到適當的物理內存地址上。這樣,只要操作系統處理好虛擬地址到物理內存地址的映射,就可以保證不同的程序最終訪問的內存地址位於不同的區域,彼此沒有重疊,就可以達到內存地址空間隔離的效果。

當創建一個進程時,操作系統會爲該進程分配一個4GB大小的虛擬進程地址空間。之所以是4GB,是因爲在32位的操作系統中,一個指針長度是4字節,而4字節指針的尋址能力是從0x00000000~0xFFFFFFFF,最大值0xFFFFFFFF表示的即爲4GB大小的容量。與虛擬地址空間相對的,還有一個物理地址空間,這個地址空間對應的是真實的物理內存。如果你的計算機上安裝了512M大小的內存,那麼這個物理地址空間表示的範圍是0x00000000~0x1FFFFFFF。當操作系統做虛擬地址到物理地址映射時,只能映射到這一範圍,操作系統也只會映射到這一範圍。當進程創建時,每個進程都會有一個自己的4GB虛擬地址空間。要注意的是這個4GB的地址空間是“虛擬”的,並不是真實存在的,而且每個進程只能訪問自己虛擬地址空間中的數據,無法訪問別的進程中的數據,通過這種方法實現了進程間的地址隔離。那是不是這4GB的虛擬地址空間應用程序可以隨意使用呢?很遺憾,在Windows系統下,這個虛擬地址空間被分成了4部分:NULL指針區、用戶區、64KB禁入區、內核區。應用程序能使用的只是用戶區而已,大約2GB左右(最大可以調整到3GB)。內核區爲2GB,內核區保存的是系統線程調度、內存管理、設備驅動等數據,這部分數據供所有的進程共享,但應用程序是不能直接訪問的。

      人們之所以要創建一個虛擬地址空間,目的是爲了解決進程地址空間隔離的問題。但程序要想執行,必須運行在真實的內存上,所以,必須在虛擬地址與物理地址間建立一種映射關係。這樣,通過映射機制,當程序訪問虛擬地址空間上的某個地址值時,就相當於訪問了物理地址空間中的另一個值。人們想到了一種分段(Sagmentation)的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。比如說虛擬地址空間中某個10M大小的空間映射到物理地址空間中某個10M大小的空間。這種思想理解起來並不難,操作系統保證不同進程的地址空間被映射到物理地址空間中不同的區域上,這樣每個進程最終訪問到的

物理地址空間都是彼此分開的。通過這種方式,就實現了進程間的地址隔離。還是以實例說明,假設有兩個進程AB,進程A所需內存大小爲10M,其虛擬地址空間分佈在0x000000000x00A00000,進程B所需內存爲100M,其虛擬地址空間分佈爲0x000000000x06400000。那麼按照分段的映射方法,進程A在物理內存上映射區域爲0x001000000x00B00000,,進程B在物理內存上映射區域爲0x00C000000x07000000。於是進程A和進程B分別被映射到了不同的內存區間,彼此互不重疊,實現了地址隔離。從應用程序的角度看來,進程A的地址空間就是分佈在0x000000000x00A00000,在做開發時,開發人員只需訪問這段區間上的地址即可。應用程序並不關心進程A究竟被映射到物理內存的那塊區域上了,所以程序的運行地址也就是相當於說是確定的了。 圖二顯示的是分段方式

的內存映射方法。 

 

 轉載:“對進程地址空間的一點認識”和“內存分頁機制 <wbr>”

圖二 分段方式的內存映射方法

      這種分段的映射方法雖然解決了上述中的問題一和問題三,但並沒能解決問題二,即內存的使用效率問題。在分段的映射方法中,每次換入換出內存的都是整個程序,這樣會造成大量的磁盤訪問操作,導致效率低下。所以這種映射方法還是稍顯粗糙,粒度比較大。實際上,程序的運行有局部性特點,在某個時間段內,程序只是訪問程序的一小部分數據,也就是說,程序的大部分數據在一個時間段內都不會被用到。基於這種情況,人們想到了粒度更小的內存分割和映射方法,這種方法就是分頁(Paging) 

分頁

分頁的基本方法是,將地址空間分成許多的頁。每頁的大小由CPU決定,然後由操作系統選擇頁的大小。目前Inter系列的CPU支持4KB4MB的頁大小,而PC上目前都選擇使用4KB。按這種選擇,4GB虛擬地址空間共可以分成1048576個頁,512M的物理內存可以分爲131072個頁。顯然虛擬空間的頁數要比物理空間的頁數多得多。

在分段的方法中,每次程序運行時總是把程序全部裝入內存,而分頁的方法則有所不同。分頁的思想是程序運行時用到哪頁就爲哪頁分配內存,沒用到的頁暫時保留在硬盤上。當用到這些頁時再在物理地址空間中爲這些頁分配內存,然後建立虛擬地址空間中的頁和剛分配的物理內存頁間的映射。

下面通過介紹一個可執行文件的裝載過程來說明分頁機制的實現方法。一個可執行文件(PE文件)其實就是一些編譯鏈接好的數據和指令的集合,它也會被分成很多頁,在PE文件執行的過程中,它往內存中裝載的單位就是頁。當一個PE文件被執行時,操作系統會先爲該程序創建一個4GB的進程虛擬地址空間。前面介紹過,虛擬地址空間只是一箇中間層而已,它的功能是利用一種映射機制將虛擬地址空間映射到物理地址空間,所以,創建4GB虛擬地址空間其實並不是要真的創建空間,只是要創建那種映射機制所需要的數據結構而已,這種數據結構就是頁目和頁表。

當創建完虛擬地址空間所需要的數據結構後,進程開始讀取PE文件的第一頁。在PE文件的第一頁包含了PE文件頭和段表等信息,進程根據文件頭和段表等信息,將PE文件中所有的段一一映射到虛擬地址空間中相應的頁(PE文件中的段的長度都是頁長的整數倍)。這時PE文件的真正指令和數據還沒有被裝入內存中,操作系統只是根據PE文件的頭部等信息建立了PE文件和進程虛擬地址空間中頁的映射關係而已。當CPU要訪問程序中用到的某個虛擬地址時,當CPU發現該地址並沒有相相關聯的物理地址時,CPU認爲該虛擬地址所在的頁面是個空頁面,CPU會認爲這是個頁錯誤(Page Fault)CPU也就知道了操作系統還未給該PE頁面分配內存,CPU會將控制權交還給操作系統。操作系統於是爲該PE頁面在物理空間中分配一個頁面,然後再將這個物理頁面與虛擬空間中的虛擬頁面映射起來,然後將控制權再還給進程,進程從剛纔發生頁錯誤的位置重新開始執行。由於此時已爲PE文件的那個頁面分配了內存,所以就不會發生頁錯誤了。隨着程序的執行,頁錯誤會不斷地產生,操作系統也會爲進程分配相應的物理頁面來滿足進程執行的需求。

分頁方法的核心思想就是當可執行文件執行到第x頁時,就爲第x頁分配一個內存頁y,然後再將這個內存頁添加到進程虛擬地址空間的映射表中,這個映射表就相當於一個y=f(x)函數。應用程序通過這個映射表就可以訪問到x頁關聯的y頁了。

 

 

【操作系統知識】內存分頁機制

Windows 2000 使用基於分頁機制的虛擬內存。每個進程有4GB的虛擬地址空間。基於分頁機制,這4GB地址空間的一些部分被映射了物理內存,一些部分映射硬盤上的交換文件,一些部分什麼也沒有映射。程序中使用的都是4GB地址空間中的虛擬地址。而訪問物理內存,需要使用物理地址。

下面我們看看什麼是物理地址,什麼是虛擬地址。

物理地址 (physical address): 放在尋址總線上的地址。放在尋址總線上,如果是讀,電路根據這個地址每位的值就將相應地址的物理內存中的數據放到數據總線中傳輸。如果是寫,電路根據這個地址每位的值就將相應地址的物理內存中放入數據總線上的內容。物理內存是以字節(8位)爲單位編址的。

虛擬地址 (virtual address): 4G虛擬地址空間中的地址,程序中使用的都是虛擬地址。

如果CPU寄存器中的分頁標誌位被設置,那麼執行內存操作的機器指令時,CPU會自動根據頁目錄和頁表中的信息,把虛擬地址轉換成物理地址,完成該指令。比如 mov eax,004227b8h ,這是把地址004227b8h處的值賦給寄存器的彙編代碼,004227b8這個地址就是虛擬址。CPU在執行這行代碼時,發現寄存器中的分頁標誌位已經被設定,就自動完成虛擬地址到物理地址的轉換,使用物理地址取出值,完成指令。對於Intel CPU 來說,分頁標誌位是寄存器CR0的第31位,爲1表示使用分頁,爲0表示不使用分頁。對於初始化之後的 Win2k 我們觀察 CR0 ,發現第31位爲1。表明Win2k是使用分頁的。

使用了分頁機制之後,4G的地址空間被分成了固定大小的頁,每一頁或者被映射到物理內存,或者被映射到硬盤上的交換文件中,或者沒有映射任何東西。對於一般程序來說,4G的地址空間,只有一小部分映射了物理內存,大片大片的部分是沒有映射任何東西。物理內存也被分頁,來映射地址空間。對於32bit的 Win2k,頁的大小是4K字節。CPU用來把虛擬地址轉換成物理地址的信息存放在叫做頁目錄和頁表的結構裏。

物理內存分頁,一個物理頁的大小爲4K字節,第0個物理頁從物理地址 0x00000000 處開始。由於頁的大小爲4KB,就是0x1000字節,所以第1頁從物理地址 0x00001000 處開始。第2頁從物理地址 0x00002000 處開始。可以看到由於頁的大小是4KB,所以只需要32bit的地址中高20bit來尋址物理頁。

頁表,一個頁表的大小爲4K字節,放在一個物理頁中。由1024個4字節的頁表項組成。頁表項的大小爲4個字節(32bit),所以一個頁表中有1024 個頁表項。頁表中的每一項的內容(每項4個字節,32bit)高20bit用來放一個物理頁的物理地址,低12bit放着一些標誌。

頁目錄,一個頁目錄大小爲4K字節,放在一個物理頁中。由1024個4字節的頁目錄項組成。頁目錄項的大小爲4個字節(32bit),所以一個頁目錄中有 1024個頁目錄項。頁目錄中的每一項的內容(每項4個字節)高20bit用來放一個頁表(頁表放在一個物理頁中)的物理地址,低12bit放着一些標誌。

對於x86系統,頁目錄的物理地址放在CPU的CR3寄存器中。

CPU把虛擬地址轉換成物理地址:
一個虛擬地址,大小4個字節(32bit),包含着找到物理地址的信息,分爲3個部分:第22位到第31位這10位(最高10位)是頁目錄中的索引,第 12位到第21位這10位是頁表中的索引,第0位到第11位這12位(低12位)是頁內偏移。對於一個要轉換成物理地址的虛擬地址,CPU首先根據CR3 中的值,找到頁目錄所在的物理頁。然後根據虛擬地址的第22位到第31位這10位(最高的10bit)的值作爲索引,找到相應的頁目錄項 (PDE,page directory entry),頁目錄項中有這個虛擬地址所對應頁表的物理地址。有了頁表的物理地址,根據虛擬地址的第12位到第21位這10位的值作爲索引,找到該頁表中相應的頁表項(PTE,page table entry),頁表項中就有這個虛擬地址所對應物理頁的物理地址。最後用虛擬地址的最低12位,也就是頁內偏移,加上這個物理頁的物理地址,就得到了該虛擬地址所對應的物理地址。

一個頁目錄有1024項,虛擬地址最高的10bit剛好可以索引1024項(2的10次方等於1024)。一個頁表也有1024項,虛擬地址中間部分的 10bit,剛好索引1024項。虛擬地址最低的12bit(2的12次方等於4096),作爲頁內偏移,剛好可以索引4KB,也就是一個物理頁中的每個字節。

一個虛擬地址轉換成物理地址的計算過程就是,處理器通過CR3找到當前頁目錄所在物理頁,取虛擬地址的高10bit,然後把這10bit右移2bit(因爲每個頁目錄項4個字節長,右移2bit相當於乘4)得到在該頁中的地址,取出該地址處PDE(4個字節),就找到了該虛擬地址對應頁表所在物理頁,取虛擬地址第12位到第21位這10位,然後把這10bit右移2bit(因爲每個頁表項4個字節長,右移2bit相當於乘4)得到在該頁中的地址,取出該地址處的PTE(4個字節),就找到了該虛擬地址對應物理頁的地址,最後加上12bit的頁內偏移得到了物理地址。

32bit的一個指針,可以尋址範圍0x00000000-0xFFFFFFFF,4GB大小。也就是說一個32bit的指針可以尋址整個4GB地址空間的每一個字節。一個頁表項負責4K的地址空間和物理內存的映射,一個頁表1024項,也就是負責1024*4k=4M的地址空間的映射。一個頁目錄項,對應一個頁表。一個頁目錄有1024項,也就對應着1024個頁表,每個頁表負責4M地址空間的映射。1024個頁表負責1024*4M=4G的地址空間映射。一個進程有一個頁目錄。所以以頁爲單位,頁目錄和頁表可以保證4G的地址空間中的每頁和物理內存的映射。

每個進程都有自己的4G地址空間,從 0x00000000-0xFFFFFFFF 。通過每個進程自己的一套頁目錄和頁表來實現。由於每個進程有自己的頁目錄和頁表,所以每個進程的地址空間映射的物理內存是不一樣的。兩個進程的同一個虛擬地址處(如果都有物理內存映射)的值一般是不同的,因爲他們往往對應不同的物理頁。

4G地址空間中低2G,0x00000000-0x7FFFFFFF 是用戶地址空間,4G地址空間中高2G,
0x80000000-0xFFFFFFFF 是系統地址空間。訪問系統地址空間需要程序有ring0的權限

未分頁池(我寧願把它理解成:沒有被使用的頁組成的空間池)

未分頁池(nonpaged pool)是常駐內存的虛擬內存頁設置,能被隨時訪問並且不造成分頁錯誤。設備驅動和操作系統內核使用它存儲那些必須常駐物理內存並且不得分頁存儲到硬盤上的數據結構。

未分頁池有兩種類型:一種作爲通常的使用,令一種小的部分(4頁)留給緊急狀態當未分頁池滿並且調用者無法忍受分配的失敗。單處理器系統有3個未分頁池,多處理器系統有5個未分頁池。

未分頁池有容量極限:Windows NT-128MB,Windows 2K-256MB。內核函數和設備驅動如果需要實時內存緩衝並不允許它分頁到當前系統之外,就得從未分頁池中分配內存。如果程序在這個分配過程存在着內存泄漏,它終將耗盡所有的未分頁池空間,並導致之後對未分頁池的請求失敗,最後未分頁池越界造成當前操作系統藍屏。

未分頁池和分頁池可以從註冊表"HKLM/SYSTEM/ CurrentControlSet/Control/Session Manager/Memory Management"中預設,從0到極限值。

 

 

 

分頁與非分頁內存

 

雖然可以尋址4GB的內存,而在PC裏往往沒有如此多的真實物理內存。操作系統和硬件(這裏指的是CPU中的內存管理單元MMU)爲使用者提供了虛擬內存的概念。Windows的所有程序(包括Ring0層和Ring3層的程序)可以操作的都是虛擬內存。之所以稱爲虛擬內存,是因爲對它的所有操作,最終會變成一系列對真實物理內存的操作。在CPU中有一個重要的寄存器CR0,它是32位的寄存器,其中的一個位(PG位)是負責告訴系統是否分頁的。Windows在啓動前會將它的PG位置1,即允許分頁。宏PAGE_SIZE記錄分頁大小4KB,4GB的虛擬內存會被分割成1M個分頁單元。

有一部分單元會和物理內存對應起來,這種對應不是一一對應,而是多對一的映射,多個虛擬內存頁可以映射同一個物理內存頁。還有一部分單元會被映射成磁盤上的文件,並標記爲髒的(Dirty)。0~0X7FFFFFFF範圍內的虛擬內存,即低於2GB的虛擬地址爲用戶內核模式地址,而0X80000000~0XFFFFFFFF範圍內的虛擬內存,即高2GB的虛擬內存,爲內核模式地址。Windows的核心代碼和Windows的驅動程序加載的位置都是在高2GB的內核地址裏,所以一般的應用程序是不能訪問到這些核心代碼和重要數據的。同時Windows操作系統在進程切換時,保持內核模式地址是完全相同的。也就是說,所有進程的內核地址映射完全一致,進程切換的時候,只改變用戶模式地址的映射。

Windows規定有些虛擬內存頁面是可以交換到文件中的,這類內存被稱爲分頁內存。而有些虛擬內存永遠不會交換到文件中,這些內存被稱爲非分頁內存。當程序的中斷請求級在DISPATCH_LEVEL之上(包括DISPATCH_LEVEL層),程序只能使用非分頁內存,否則將導致藍屏死機。

發佈了36 篇原創文章 · 獲贊 3 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章