內存分頁機制的實現(虛擬地址和物理地址的映射)

現代操作系統都使用分頁機制來管理內存,這使得每個程序都擁有自己的地址空間。每當程序使用虛擬地址進行讀寫時,都必須轉換爲實際的物理地址,才能真正在內存條上定位數據。如下圖所示:


內存地址的轉換是通過一種叫做頁表(Page Table)的機制來完成的,這是本節要講解的重點,即:

  • 頁表是什麼?爲什麼要採用頁表機制,而不採用其他機制?
  • 虛擬地址如何通過頁錶轉換爲物理地址?

直接使用數組轉換

最容易想到的映射方案是使用數組:每個數組元素保存一個物理地址,而把虛擬地址作爲數組下標,這樣就能夠很容易地完成映射,並且效率不低。如下圖所示:


但是這樣的數組有 2^32 個元素,每個元素大小爲4個字節,總共佔用16GB的內存,顯現是不現實的!

使用一級頁表

既然內存是分頁的,只要我們能夠定位到數據所在的頁,以及它在頁內的偏移(也就是距離頁開頭的字節數),就能夠轉換爲物理地址。例如,一個 int 類型的值保存在第 12 頁,頁內偏移爲 240,那麼對應的物理地址就是 2^12 * 12 + 240 = 49392。

2^12 爲一個頁的大小,也就是4K。

虛擬地址空間大小爲 4GB,總共包含 2^32 / 2^12 = 2^20 = 1K * 1K  = 1M = 1048576 個頁面,我們可以定義一個這樣的數組:它包含 2^20 = 1M 個元素,每個元素的值爲頁面編號(也就是位於第幾個頁面),長度爲4字節,整個數組共佔用4MB的內存空間。這樣的數組就稱爲頁表(Page Table),它記錄了地址空間中所有頁的編號。

虛擬地址長度爲32位,我們不妨進行一下切割,將高20位作爲頁表數組的下標,低12位作爲頁內偏移。如下圖所示:


爲什麼要這樣切割呢?因爲頁表數組共有 2^20 = 1M 個元素,使用虛擬地址的高20位作爲下標,正好能夠訪問數組中的所有元素;並且,一個頁面的大小爲 2^12 = 4KB,使用虛擬地址的低12位恰好能夠表示所有偏移。

注意,表示頁面編號只需要 20 位,而頁表數組的每個元素的長度卻爲 4 字節,即 32 位,多出 32 - 20 = 12 位。這 12 位也有很大的用處,可以用來表示當前頁的相關屬性,例如是否有讀寫權限、是否已經分配物理內存、是否被換出到硬盤等。

例如一個虛擬地址 0XA010BA01,它的高20位是 0XA010B,所以需要訪問頁表數組的第 0XA010B 個元素,才能找到數據所在的物理頁面。假設頁表數組第 0XA010B 個元素的值爲 0X0F70AAA0,它的高20位爲 0X0F70A,那麼就可以確定數據位於第 0X0F70A 個物理頁面。再來看虛擬地址,它的低12位是 0XA01,所以頁內偏移也是 0XA01。有了頁面索引和頁內偏移,就可以算出物理地址了。經過計算,最終的物理地址爲 0X0F70A * 2^12 + 0XA01 = 0X0F70A000 + 0XA01 = 0X0F70AA01。

這種思路所形成的映射關係如下圖所示:


可以發現,有的頁被映射到物理內存,有的被映射到硬盤,不同的映射方式可以由頁表數組元素的低12位來控制。

使用這種方案,不管程序佔用多大的內存,都要爲頁表數組分配4M的內存空間(頁表數組也必須放在物理內存中),因爲虛擬地址空間中的高1G或2G是被系統佔用的,必須保證較大的數組下標有效。

現在硬件很便宜了,內存容量大了,很多電腦都配備4G或8G的內存,頁表數組佔用4M內存或許不覺得多,但在32位系統剛剛發佈的時候,內存還是很緊缺的資源,很多電腦才配備100M甚至幾十兆的內存,4M內存就顯得有點大了,所以還得對上面的方案進行改進,壓縮頁表數組所佔用的內存。

使用兩級頁表

上面的頁表共有 2^20 = 2^10 * 2^10 個元素,爲了壓縮頁表的存儲空間,可以將上面的頁表分拆成 2^10 = 1K = 1024 個小的頁表,這樣每個頁表只包含 2^10 = 1K = 1024 個元素,佔用 2^10 * 4 = 4KB 的內存,也即一個頁面的大小。這 1024 個小的頁表,可以存儲在不同的物理頁,它們之間可以是不連續的。

那麼問題來了,既然這些小的頁表分散存儲,位於不同的物理頁,該如何定位它們呢?也就是如何記錄它們的編號(也即在物理內存中位於第幾個頁面)。

1024 個頁表有 1024 個索引,所以不能用一個指針指向它們,必須將這些索引再保存到一個額外的數組中。這個額外的數組有1024個元素,每個元素記錄一個頁表所在物理頁的編號,長度爲4個字節,總共佔用4KB的內存。我們將這個額外的數組稱爲頁目錄(Page Directory),因爲它的每一個元素對應一個頁表。

如此,只要使用一個指針來記住頁目錄的地址即可,等到進行地址轉換時,可以根據這個指針找到頁目錄,再根據頁目錄找到頁表,最後找到物理地址,前後共經過3次間接轉換。

那麼,如何根據虛擬地址找到頁目錄和頁表中相應的元素呢?我們不妨將虛擬地址分割爲三分部,高10位作爲頁目錄中元素的下標,中間10位作爲頁表中元素的下標,最後12位作爲頁內偏移,如下圖所示:


前面我們說過,知道了物理頁的索引和頁內偏移就可以轉換爲物理地址了,在這種方案中,頁內偏移可以從虛擬地址的低12位得到,但是物理頁索引卻保存在 1024 個分散的小頁表中,所以就必須先根據頁目錄找到對應的頁表,再根據頁表找到物理頁索引。

例如一個虛擬地址 0011000101  1010001100  111100001010,它的高10位爲 0011000101,對應頁目錄中的第 0011000101 個元素,假設該元素的高20位爲 0XF012A,也即對應的頁表在物理內存中的編號爲 0XF012A,這樣就找到了頁表。虛擬地址中間10位爲 1010001100,它對應頁表中的第 1010001100 個元素,假設該元素的高20位爲 0X00D20,也即物理頁的索引爲 0X00D20。通過計算,最終的物理地址爲 0X00D20 * 2^12 + 111100001010 = 0X00D20F0A。

這種思路所形成的映射關係如下圖所示:

圖中的點狀虛線說明了最終的映射關係。圖中沒有考慮映射到硬盤的情況。


採用這樣的兩級頁表的一個明顯優點是,如果程序佔用的內存較少,分散的小頁表的個數就會遠遠少於1024個,只會佔用很少的一部分存儲空間(遠遠小於4M)。

在極少數的情況下,程序佔用的內存非常大,佈滿了4G的虛擬地址空間,這樣小頁表的數量可能接近甚至等於1024,再加上頁目錄佔用的存儲空間,總共是 4MB+4KB,比上面使用一級頁表的方案僅僅多出4KB的內存。這是可以容忍的,因爲很少出現如此極端的情況。

也就是說,使用兩級頁表後,頁表佔用的內存空間不固定,它和程序本身佔用的內存空間成正比,從整體上來看,會比使用一級頁表佔用的內存少得多。

使用多級頁表

對於64位環境,虛擬地址空間達到 256TB,使用二級頁表佔用的存儲空間依然不小,所以會更加細化,從而使用三級頁表甚至多級頁表,這樣就會有多個頁目錄,虛擬地址也會被分割成多個部分,思路和上面是一樣的,不再贅述。

轉載自:https://www.cnblogs.com/zhangjinfu/articles/11274389.html
發佈了13 篇原創文章 · 獲贊 5 · 訪問量 752
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章