本博文很大程度上參考了,潘愛民先生的《Windows內核原理與實現》一書,在此對他表示感謝。
記得是在學C語言指針的時候,首次比較實際的使用內存尋址。也是在那個時候知道不能使用未初始化的指針,記得當時老師還說過,如果使用了未初始化的指針,輕則運行錯誤,重則操作系統崩潰。現在看起來那個重則系統崩潰還是比較可笑的,如果真的這么容易就讓系統崩潰,那么Windows早就被用戶拋棄了。而且我在調程序的時候,如果出現指針解引用錯誤,基本都是讓系統直接終止掉我的程序,Windows一向安然無恙。當然,也許老師指的是DOS環境,不過我在dos下只寫過匯編代碼。
我在學C語言之后的較長一段時間,都天真的認為我的程序在運行時候是直接使用物理內存的。那個時候我做過這樣的嘗試:訪問地址為0x0的內存值。結果可想而知。那個時候我只知道那塊內存系統禁止訪問,覺得也許是因為操作系統在時刻監視着所有進程代碼的原因吧。再后來寫C程序輸出地址的時候,發現每次程序變量的內存地址都差不多,開始有點懷疑Windows是怎么讓我的程序跑起來的。因為我覺得每次程序運行的變量地址應該有較大的偏差才是。而實際結果卻是,不論我計算機運行程序的數量多少,當前物理內存的使用量是多少,地址范圍始終都差不多,而且地址的位置比較低,好像二進制的高8位一直都是0。再后來看Windows的時候,才知道,我在程序中並非直接使用物理地址,而是一個虛擬地址。
所以,我找了本Windows內核的書好好看了看,並寫了這篇博文。
因為內存管理的優劣會直接影響到系統本身的性能,所以操作系統管理內存的方法一般要根據處理器的硬件情況來決定。Windows的主要硬件體系結構就是Intel的架構,所以在內存管理上就受很多的x86架構的影響。另外為了能讓多個進程之間不互相干擾破壞,並與操作系統隔離,Windows在處理器尋址的基礎上,又應用了大量的內存管理技術來滿足各種要求。在所有進程對於內存的要求已經超過了實際物理內存的時候,Windows還必須要平衡各種需求,借助如硬盤的外部存儲,使計算機能夠正常運行。由此可見,內存管理是所有操作系統非常重要的一部分。
首先,在x86體系中,內存地址有三類:
1 物理地址(直接對應物理內存的地址,一般為32位或36位的無符號整數)
2 虛擬地址(也稱作線性地址,處理器使用前需要轉換為物理地址,為32位無符號整數,因此地址空間范圍為4GB)
3 邏輯地址(學過8086匯編的人都知道,邏輯地址分為兩個部分:段地址和偏移地址,這里也是一樣的)
然后再介紹一下兩種比較主流的內存管理方式:頁式內存管理,段式內存管理。
1 頁式內存管理
如果直接讓進程使用物理地址來訪問內存將會使進程的動態內存分配難以有效實施,因為內存單元和進程緊密的聯系在了一起,從而使內存的回收和再分配受限於特定的進程和物理地址。一種簡單的思路就是讓進城使用虛擬地址,在虛擬地址和物理地址之間建立一個映射表來完成轉譯。
頁式內存管理中,虛擬地址空間是按照頁來管理的,對應的物理內存同樣是按照頁來管理,二者的頁大小是相同的。而因為這樣的一個轉譯關系存在,在虛擬地址中連續的頁面在物理地址中可以不連續。通過維護這樣的一個轉譯關系,物理頁面可以被動態的分配給虛擬頁面,這樣可以做到當虛擬頁面真正被使用的時候才為其分配物理頁面,這樣的好處就是能夠節省物理內存,使用效率更高。
另外需要注意的是,在一個系統中,物理地址空間只有一個,而虛擬內存空間可以有多個。而且每個虛擬內存空間都必須要維護一個映射關系,這樣每個進程的地址空間都是獨立的。進程A地址為0x00FF0000對應的物理地址和進程B的0x00FF0000地址對應的物理地址就不相同。另外,每個虛擬地址空間實際映射的物理頁面很少。而物理頁面一般只被映射到一個虛擬地址空間中。當然存在例外,比如DLL在被多個進程使用的時候,其函數所在的物理頁面必然被映射到了多個進程的虛擬地址空間中。
在頁面划分機制下,32位的虛擬地址被分為了兩個部分:頁索引+頁內偏移。Intel x86下的頁面大小標准為4KB,也就是2^12。因此,虛擬地址中的低12位可以用於頁內偏移。前20位可以用於頁索引部分,用於找到一個實際的物理頁面。這樣的頁面映射表大小就是1M。若用線性關系表示的話,那么就需要用4M的內存,因為一個地址的大小是4Byte。不過這種方式浪費比較嚴重,畢竟Windows默認線程棧的大小也不過才1M。
另外在這1M的地址空間中,還有很大一部分的表項並未使用,浪費很嚴重。
所以Intel x86的虛擬地址解析過程如下:
32位的虛擬地址被分為了3個部分:31 - 22 頁目錄索引 21 - 12 頁表索引 11- 0 頁內偏移
1 對於一個虛擬地址,首先解析頁目錄索引,在也目錄中查找。頁目錄的大小就是4KB。頁目錄索引的首地址位於CR3寄存器中。
2 在目錄中找到后,再根據頁表索引來找到頁對應的物理地址。
3 最后加上頁內偏移,就定位到了最終的物理地址。
可以知道,這樣下來需要使用的空間達到了4KB+4MB的大小,但是這4MB的頁表索引在對應頁表不使用的時候完全可以不構造出來,從而節省大量的內存。
同時,因為二級頁表的查找也需要付出一定性能上的代價,畢竟多了一次查找。對於這個問題,intel x86處理器緩存了地址轉譯信息,也就是虛擬地址到物理地址的映射關系。這樣當處理器重復訪問一個虛擬地址時,就不用再次進行轉譯了。此緩存被稱為TLB,中文意思就是:地址轉譯快查緩沖區。
TLB是硬件級的地址查找支持電路,專門用來做這個,效率非常高。也因此,這一塊是軟件所無法觸及的,知道優惠這么個東西就OK了。因為TLB的存在,二級查找引發的性能下降就不是很明顯了。
另外當CR3寄存器的值改變的時候,TLB中的數據就全部無效了,因為這意味着進程的切換,之前的映射關系自然就不再成立了。
另外Intel x86 Pentium Pro還提供了成為PAE物理地址擴展的內存映射模式,支持36位物理地址,但虛擬地址仍舊是32位。而且PAE采用的是3級頁表機制。但是基本原理一樣。
段式內存管理還看得不是很清楚,看明白了再把整理篇博文發出來。