首頁 > 軟體

我對虛擬記憶體的理解

2020-06-16 18:09:10

什麼是虛擬記憶體?

先直接摘抄一段 wikipedia 上的介紹。

虛擬記憶體是計算機系統記憶體管理的一種技術。它使得應用程式認為它擁有連續的可用的記憶體(一個連續完整的地址空間),而實際上,它通常是被分隔成多個實體記憶體碎片,還有部分暫時儲存在外部磁碟記憶體上,在需要時進行資料交換。

對於 C 語言裡面的變數,我們可以使用 & 運算子來獲得其地址, 既然是虛擬地址,就是指這個地址是虛擬的。

虛擬地址機制不是必須的,在簡單的微控制器中,編寫的程式碼編譯時都需要指定物理 RAM 空間分布,不會有虛擬地址的概念,地址就是指在 RAM 中的實體地址。

為什麼需要虛擬記憶體?

下面都是我的理解,可能有不恰當的地方。

假設在裸機上執行一個程式,這時就沒辦法再同時執行其他程式了,需要引入作業系統來進行管理。有了作業系統之後,我們或許能執行多個不同程式,但很可能無法同時執行同一個程式的多個範例,因為同一個程式使用的實體地址是一樣的(假設是舊的編譯器),一起執行會有衝突。想執行同一個程式的多個範例,看起來有 2 種方案。

  1. 重新編譯一個使用其他地址的程式,和前一個不衝突。
  2. 執行該程式時使用新增地址偏移等方式保證使用地址不同。

第一個是讓編譯器來完成,理論上可以,但是會過於麻煩,如果我想同時執行3個,4個呢, 就必須多編譯幾次。
第二個呢,是讓作業系統來完成,應該算是虛擬記憶體的雛形了。使用該模式後,編譯器給出的程式內相關地址就不是實際實體地址了,算是虛擬地址。

X86 的虛擬記憶體技術

GDT/LDT

GDT 和 LDT 都是在 80286 的時候引入 x86 體系的,LDT 和 GDT 有著類似的結構。 LDT 的出現就是為了多進程使用獨立地址空間來服務的,通常每個進程一個 LDT, 而共用記憶體和核心記憶體則使用 GDT。 每個程式根據段描述符來確定基址,而且每個 Entry 裡面還有 limit 欄位,正好可以對程式存取空間作限制。但在 80386 引入了更優秀的分頁技術後,LDT 基本上就不再使用了。

分頁

分頁作為當前虛擬記憶體技術的實現,肯定有比 LDT 更好的地方,但它們的實現思路都是類似的。作業系統為每個進程維護一個 handle,這個handle關聯的是該進程從虛擬地址到實體地址轉換的相關資料塊。在 LDT 中 handle就是 LDT 指標與長度, 資料塊就是 LDT 自身。分頁模式下資料塊叫做 paging structure, handler 是指向其的指標。

paging structure 有 4096 bytes, 包含有獨立的 entries,不同模式下每個 entry 的大小不同。 每個 entry 包含一個實體地址,可以指向一個 page frame,也可以指向另一個 paging structur,也就是級聯的方式。 指向第一個 page structure 的指標在 CR3 暫存器裡, 之後從線性地址到實體地址的過程就是一個疊代的過程。線性地址的一部分用來指示對應的 entry, 該 entry 如果指向的是另一個 page structure 則繼續,直到指向了一個 page frame則表示地址轉換完成,使用最後這個 entry 作為基址,線性地址剩餘部分表示偏移。

現在專門討論 32bit 模式下的分頁。32bit下每個 entry 4bytes,每個 paging structure包含 1024 個 entries, 需要 10bit 來區分每個 entry。實際上 32bit 模式下使用了 2 級 pageing structure。 第一級稱為 Page Directory, 使用 32bit 線性地址的 bits 31-22 來區分, 第二級稱為 Page Table, 使用 32bit 線性地址的 bits 21-12 來區分,剩下的 bits 11-0正好是用來計算在 4K page 裡的偏移。

在所有的線性地址到實體地址翻譯中, CR3,PDPTE,PDT, PTE等儲存的都是下一步的基址, 線性地址中存的則是偏移。


IT145.com E-mail:sddin#qq.com