1. 程式人生 > >作業系統頁表管理

作業系統頁表管理

學過作業系統的都知道,在作業系統中存在一個虛擬記憶體的概念,它用於記憶體的管理,使得應用程式認為它有一段連續的記憶體,大大地簡化了程式設計師碼程式碼的難度。程式設計師只用關注在這個連續的虛擬記憶體段中怎麼使用記憶體,不用關心在實體記憶體中到底用那一段記憶體,程序執行的時候作業系統會自動進行對映。作業系統是怎麼做到的呢?實際上作業系統為每一個程序維護了一個從虛擬地址到實體地址的對映關係的資料結構,叫頁表,頁表的內容就是該程序的虛擬地址到實體地址的一個對映。

那麼什麼樣的資料結構能夠表示這個對映關係呢?最容易想到的方法就是用陣列儲存,虛擬空間中的每一個頁都分配一個數組項。該陣列指向與之關聯的頁幀, 但這會引發一個問題, 例如, IA-32體系結構使用4KB大小的頁,在虛擬地址空間為4GB的前提下,則需要包含1048576
(4G/4K=1024*1024=1048576)項的頁表,要定址4G的空間,需要32位,也就是4Bytes, 1048576項需要4M(4 * 1024 * 1024 Bytes=4M)的記憶體.這個問題在64位體系結構下, 情況會更加糟糕. 而每個程序都需要自身的頁表,這會導致系統中大量的記憶體都用來儲存頁表.

為了減少頁表佔用空間,我們可以考慮一個實際情況,對於一個程序的虛擬空間,其實大部分的記憶體都是沒有使用的,因此在頁表中其實是不需要這部分的對映的,因此前輩們設計出了多級頁表的結構。

那麼到底多少級才是最合適的呢?

頁表級數的選擇和發展

多級頁表的設計思想其實就是現實生活中分組的概念,舉個例子,我們研究中心有1000個人,現在祕書準備組織一次春遊活動,需要統計一下有多少人去春遊,如果祕書每個人都問一下記錄一下每個人的資訊,那麼就需要1000行去記錄這個資訊,但是這樣效率就很低而且浪費空間,所以祕書就讓每個實驗室單獨統計,祕書只用要記錄每個實驗室去春遊的人數以及負責人的聯絡方式即可,假設有五個實驗室,則只需要五行即可,然後每個實驗室的負責人統計一下自己實驗室需要去的人員資訊,那麼這樣效率就比較高了,而且因為只是記錄了那些去春遊的的人員資訊,因此這樣既提高了效率,也大大的也省了儲存空間,當然你可能回發現一個小問題,如果10000個人都去那麼實際上需要10005行統計資訊,但是考慮到一些可能也是經常發生的情況,比如我們軟體系統實驗室春遊那天需要加班趕專案,那麼實際上我們實驗室負責人就不用進行記錄了,祕書那裡直接記錄軟體實驗室0個人去,甚至不記錄也可以,所以一般情況下這樣是可以提高效率,並且能夠節省空間的。分級的本質其實也是將頁表項進行分組管理,考慮到程序記憶體大部分空閒以及區域性性,因此實際記錄的時候大部分的記錄是沒有的,所以相比上面提到的直接使用一個數組(也就是一級頁表)大大地節省了儲存需要的空間。

我們再來算一筆帳,從Intel 80386開始,預設的頁大小是4k, 提供32位的定址空間。一頁4k需要12位進行定址,因此對於32的定址空間,還有32-12=20位可以用來頁表的索引。因為頁表項儲存的是實體記憶體一頁的實體地址,因此一個頁表項只能指向一頁,而硬體的實現是使用CR3暫存器儲存了頁表的起始地址,因此對於頁表我們應該使其只佔用一頁的記憶體。4k能夠儲存的頁表項個數為4k/4B=1024, 定址1024個索引只需要10位即可,因此在這個前提下,用於頁表索引的20位自然而然的就分為了兩段,也就是頁表被分成了兩級,十位用來表示一級頁表內部偏移,十位用來表示二級頁表的內部偏移,一級頁表的頁表項儲存二級頁表的實體地址。其實這也是Linux最初採用的兩級頁表分頁機制。

Linux的兩級分頁機制

兩級分頁機制將32位的虛擬空間分成三段,對就是三段,低十二位表示頁內偏移,高20分成兩段分別表示兩級頁表的偏移。
二級頁表.png
* PGD(Page Global Directory): 最高10位,全域性頁目錄表索引
* PTE(Page Table Entry):中間10位,頁表入口索引

在進行地址轉換時,結合CR3暫存器中存放的全域性頁表實體地址,然後加上虛擬地址的高10位也就是PGD段,作為頁表偏移找到相應的頁表項,從改頁表項得到二級頁表的實體地址,然後結合中間十位即PTE段,作為偏移找到PTE項,即獲得了要訪問的記憶體地址所在的物理頁,然後結合Page Offset即定位到了需要訪問的實體記憶體,這樣就完成了從虛擬地址到實體地址的轉換。如果沒有快取的情況下,整個過程需要三次記憶體訪問。

Linux的三級頁表

Intel通過在處理器上把管腳數從32增加到36,以提高處理器的定址能力,使其達到236=64GB,但是linux依然使用32位的虛擬定址空間,為此,需引入一種新的分頁機制。

如果頁的大小依然是4k, 64G(234)的記憶體可以分為224個頁框,然後頁表項的實體地址欄位從20位擴充套件到24位,每個頁表項必須包含12個標誌位(固定)和24個實體地址位(36-12),共36位,因此,每個頁表項須從32位擴充套件到64位(36位>32位,考慮到對齊,因此應將頁表項擴大一倍到64位)。所以原來32位的頁表項現在變成了64位後,一頁可以儲存的頁表項從1024變成了512,需要9位進行定址,高一級的頁表也需要9位,加上頁內偏移12位,現在32位的地址還剩餘2位,因此又引入一級頁表,該頁表只含有4個頁表項。這樣就解決了PAE情況下的分頁。
下圖是PAE情況下的三級頁表情況,圖片來自維基百科

注意(個人觀點):雖然頁表項擴充套件到了64位,選址達到了36位,即64G的空間,但是實際上一個程序可以使用的虛擬空間大小仍然是4G,因為虛擬地址依然只有32位,即使每個頁表項都填滿也只能有4G的空間。唯一不同的是實體記憶體進行分配時候就可以使用超過4G的空間了。

Linux的四級頁表

隨著硬體的發展,64位的CPU出現了,這個時候基於32位處理器的三級頁表又不能使用了,不過現在的硬體已經可以支援四級頁表了,可以使用48位的虛擬空間了(虛擬空間的位數和硬體的對應關係理的還不是很清楚),經過前輩們的爭論,最後確定了下面的四級頁表形式:
四級頁表.png
從圖中可以看出,64位的虛擬地址只使用了低48位,每一級的頁表項都有64位,其中低12位作為標誌位,然後緊接著的40位作為物理定址使用,加上頁內偏移一共52位用於實體地址,但是實際上CPU的引腳並沒有52位,一般是40位,46位等。比如我們實驗室使用的Intel Xeon E5645 就是40位的實體地址,所以52位只使用了40位(240=1T)。
下面是IA32-e模式下的線性地址對映

參考資料