1. 程式人生 > >Windows記憶體體系(1) -- 虛擬地址空間

Windows記憶體體系(1) -- 虛擬地址空間

一、真實模式下記憶體分配機制

在8086或者80186以前,要執行一個程式,作業系統會把這些程式全都裝入記憶體,程式都是直接執行在實體記憶體上的,也就是說程式中訪問的記憶體地址都是實際的實體記憶體地址。當計算機同時執行多個程式時,必須保證這些程式用到的記憶體總量要小於計算機實際實體記憶體的大小。

例如某臺計算機總的記憶體大小是128M ,現在同時執行兩個程式 A和B ,A需佔用記憶體10M , B需佔用記憶體110M 。計算機在給程式分配記憶體時會採取這樣的方法:先將記憶體中的前10M分配給程式 A ,接著再從記憶體中剩餘的118M中劃分出 110M分配給程式B 。這種分配方法雖然可以保證程式A和程式B都能執行,但是這種簡單的記憶體分配策略會導致很多問題:

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

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

  • 問題 3 :程式執行的地址不確定。當記憶體中的剩餘空間可以滿足程式 C 的要求後,作業系統會在剩餘空間中隨機分配一段連續的 20M 大小的空間給程式 C 使用,因為是隨機分配的,所以程式執行的地址是不確定的。

二、虛擬地址空間介紹

作業系統讓每個程序都有自己的虛擬地址空間(Virtual Address Space,簡稱VAS)。以32位程序為例,每個程序都有0x00000000 ~ 0xFFFFFFFF(4GB)的虛擬地址空間,所以每個程序都可能分配到0x123456地址的記憶體,但這個地址不能在程序間相互訪問。

因為這些都是“虛擬”的地址空間,這些“地址”都不能直接使用,CPU在定址的時候雖然是按照虛擬地址來定址的,但是還要通過MMU

(記憶體管理單元)來將虛擬地址轉換為物理儲存器(如記憶體等)上的實體地址:

這裡寫圖片描述

從圖上可以看出,程序A和B雖然都有地址0x123456,但它們分別對應的實體地址不一樣。

三、虛擬地址空間分割槽

程序的虛擬地址空間雖然很大,但是它被劃分成了很多分割槽,供Ring3層應用程式使用的使用者模式分割槽並不大(一半不到),如圖:
這裡寫圖片描述

3.1 空指標賦值分割槽

這一分割槽的程序地址空間的範圍為:[0x00000000, 0x0000FFFF],總大小為64K,保留該分割槽的目的是為了幫助應用程式設計師捕獲對空指標的賦值。如malloc分配記憶體失敗,就會返回NULL

如果程序中的執行緒試圖訪問該分割槽內的記憶體地址,就會引發訪問違規。

3.2 使用者模式分割槽

在Windows中,所有的exe和動態連結庫都載入到這一區域。系統同時會把該程序可以訪問的所有記憶體對映檔案(後面會介紹)對映到這一分割槽。

程序無法通過指標來讀取、寫入、訪問其他程序的這一分割槽,因此一個應用程式破壞另一個應用程式的可能性就非常小了,從而使整個系統更加堅固。

3.3 核心模式分割槽

核心模式分割槽是作業系統程式碼的駐地。與執行緒排程、記憶體管理、檔案系統支援、網路支援以及裝置驅動程式相關的程式碼都會載入到這一分割槽。該分割槽內的程式碼和資料被完全的保護起來了,如果一個應用程式試圖讀取或寫入位於這一分割槽的記憶體地址,會引發訪問違規。

駐留在這一分割槽內的程式碼為所有程序共有。

四、虛擬地址空間的使用

虛擬地址空間的使用涉及到3個概念:頁面大小分配粒度預定和調撥

4.1 頁面大小

虛擬地址空間被分成以“頁面”為單位,因為硬體記憶體管理單元是以頁面為粒度將虛擬地址轉譯成實體地址的。頁面的大小根據不同的CPU不而有所不同。x86和x64系統使用的頁面大小都是4KB,而IA-64系統使用的頁面大小是8KB

IA-64作業系統只能在INTEL安騰系列處理器及AMD部分伺服器處理器執行,所以主流市場並不常見

應用程式在虛擬地址空間分配空間時,系統需要確保分配區域的大小正好是系統頁面大小的整數倍。

4.2 分配粒度

應用程式在從虛擬地址空間分配空間時,系統會確保所有分配區域的起始地址都是分配粒度的整數倍。分配粒度的會根據不同的CPU平臺而有所不同,但目前所有的CPU平臺的分配粒度都是使用64KB。也就是說,分配的起始地址 = 64 * N

通過Windows的GetSystemInfo函式也可以獲得此分配粒度值。

上面所說的分配粒度頁面大小的限制,只是針對於“應用程式”,系統核心自己不存在這樣的限制。

4.3 預定和調撥

虛擬地址空間的使用分為2個步驟:

  1. 預定(reserve):告訴系統我們要從虛擬地址空間預定哪一塊區域,系統為我們保留這一塊區域。預定的局域的起始地址和大小遵循上面介紹的分配粒度頁面大小的要求。因為預定的只是虛擬地址空間,不佔用任何其他物理儲存器,所以沒有形成實質的開銷。
  2. 調撥(commit):預定的區域還不能使用,我們還需要為預定的區域從頁交換檔案調撥儲存器,調撥之後我們才能使用該區域。
    至於為什麼要從頁交換檔案中調撥儲存器? 頁交換檔案如何與實體記憶體之間互動?下一篇文章《Windows記憶體體系(2) – 虛擬記憶體》會介紹。