前言
在一個擁有32位的地址空間,4KB的頁面(212),並且每個PTE為4個位元組,那麼頁表大小為4MB(4 * 232 / 212),但若為64位地址空間,4KB的頁面(212)且每個PTE為4位元組,那麼頁表大小為16TB(4 * 264 / 212),由於頁表常駐記憶體,佔用記憶體會很大,所以必須對頁表儲存結構進行優化,這就是我們本文所要講解的內容,常見的頁表資料結構為多級頁表(兩級、三級等)、倒置頁表、雜湊頁表,我們來一一進行分析。
多級頁表
首先我們講講2級頁表,然後通過2級頁表延伸到多級頁表,現假設有16KB(214)的地址空間,並且每頁大小為64(26)位元組,每個PTE為4位元組,那麼說明頁表為1KB(4 * 214 / 26),若我們有64位元組的頁,那麼將1KB可劃分為16個64位元組的頁面,每個頁面可容納16個PTE,前面我們講解到虛擬地址劃分為虛擬頁號(VPN)和虛擬頁偏移量(VPO),但虛擬頁偏移量已經固定,那麼我們只能從VPN下手將其作為索引用於索引頁表目錄,那麼我們該如何利用VPN來構建各個部分的索引呢?我們首先需要構建頁表目錄,根據上述假設,總共有256個PTE分佈在16頁上,頁目錄在頁表的每頁上需要一個條目,因此它具有16個條目, 最終我們需要VPN中的4位索引到目錄中,這也就意味著我們需要使用VPN的前4位,如下所示:
我們從VPN提取出了頁表目錄索引(PDI),那麼我們就可以計算出每個PDE(Page Directory Entry)的地址值:
PDEAddress = PageDirectoryBase + (PDIndex * sizeof(PDE))。
有了頁目錄索引後我們還需進行進一步翻譯,如果頁目錄索引為空,很顯然第2級頁表根本就不會存在,如此一來則達到了減少記憶體的要求,因為只有第一級頁表才會存在於主存中,虛擬記憶體系統會根據需要調入或調出第2級頁表,這就減少了主存的壓力,只有經常使用的2級頁表才需要快取在主存中。如果第1級頁表即頁目錄索引有值,那麼還需根據頁目錄指向的頁表頁面去獲取PTE,要找到此PTE,我們還需要使用VPN的其餘索引對映到頁表的部分。
通過使用如上頁表索引來索引頁表本身,從而找到PTE地址,也就找到了PFN(物理頁幀號)
PTEAddress = (PDE.PFN << SHIFT) + (PTIndex * sizeof(PTE))
從頁面目錄獲得的頁面幀號(PFN)必須先左移到適當位置,然後再與頁表索引組合以形成PTE的地址。假設如下為二級頁表扁平化的片段
如上第1片段為頁表目錄,在其中存在索引到第2級頁表的索引,還包括有效位,第2和第3片段分別為第1級頁表目錄索引對應的頁表(其中包含保護位,可讀?可寫?等等),假設CPU產生虛擬地址(0xFE16 = 25410 = 111111102),由於我們假設虛擬地址空間為14位,所以將轉換後的2進位制不足用0填充即11111110000000,同時我們將地址空間進行虛擬頁號(VPN)和虛擬頁偏移量(VPO)劃分,然後對VPN劃分為頁表目錄和頁表索引,我們通過紅色、綠色、藍色由左至右分別代表頁表目錄索引、頁表索引、虛擬頁偏移量即1111 1110 000000,經過如此劃分後,此時前4位(11112 = 1510)為頁表目錄索引,對應上述頁表目錄最後一行,此時頁表目錄幀號為101對應第2個頁表片段,然後根據接下來的4位(11102 = 1410),最終得到索引為倒數第2行,即最終物理頁幀號為55。最後我們通過如下實體地址計算公式
PhysAddress = (PTE.PFN << SHIFT) + offset
即最終實體地址為:55 * 26 + 000000 = 352010 = 0XDC016。假設為32位地址空間,那麼頁目錄索引、頁表索引、虛擬頁偏移量分別對應為10、10、12位,那麼對應的2級頁表將是如下形式
簡而言之,對於32位地址空間,會將VPN中的前10位(位22..31)用於索引頁表目錄,緊接下來的10位(12 ..21)用於索引所選的頁表。換言之,對於2級頁表結構其本質是:VPN的前m位為頁表目錄索引,而接下來的n位為頁表索引,同時需要注意的是2級頁表其地址是從上往下增加。根據上述將32位地址空間中的頁表以2級結構劃分,此時第1級頁表大小為(1024 * 4) = 4KB,而第2級頁表為(1024 * 1024 * 4) = 4MB,所以頁表大小將為4KB + 4MB,這麼算來比直接使用單級頁表結構為4MB情況更糟糕了不是嗎,其實情況並不是這樣,如上算出的4KB + 4MB為最極限的情況,上述已經講解過只有經常需要用到的2級頁表才快取在主存中,所以實際情況下頁表大小會小於4MB。
早期作業系統採用的是2級頁表結構,但是現如今大多數作業系統採用多級頁表結構,就像樹一樣,不過是深度或層次更深了而已。假設我們有一個30位虛擬地址空間和一個較小的頁面(512位元組),因此,我們的虛擬地址具有21位的虛擬頁號和9位的偏移量,使頁表的每個部分都適合單個頁面是構建多級頁表的目標,但到目前為止,我們僅考慮了頁表本身,如果頁表目錄很大,那該怎麼辦?為了確定一個多級頁表中需要多少級才能使頁表的所有部分用一個頁面容納,我們首先確定一個頁面中可以容納多少個PTE。我們假設給定的頁面大小為512位元組,並假設PTE大小為4位元組,我們知道在單個頁面上可容納128個PTE。當我們索引到頁表的頁面時,可以得出結論,我們需要使用VPN的最低有效7位(log2128)作為索引
通過確定單頁面需要容納128個PTE,那麼將佔據地址空間7位,那麼還剩下14位地址空間,如果將剩下的214作為頁表目錄, 那麼將橫跨128頁而不再是1頁,那麼對於構建多級頁表的目標將無法實現,為了解決這個問題,我們需要將14位進行再次劃分,將頁表目錄進行設定為多頁,頁表目錄位於上方從而指向另一頁表目錄,因此我們可以進行如下劃分
現在,在索引上層頁表目錄時,我們使用虛擬地址的最高位(圖中PD Index:0),該索引可用於從頂級頁表目錄中獲取頁表目錄的條目,如果有效,則對來自自頂層PDE和VPN的下一部分(PD Index:1)的物理幀號組合來查詢頁表目錄的第二層,最後,如果有效,則為PTE地址通過將頁表索引與第二級PDE中的地址結合使用,可以形成一個地址。 當然這個過程需要做很多工作,所有這些都是為了在多級表中查詢物理頁幀號。最終多級頁表結構如下這般
上述我們講過若為64位地址空間,4KB的頁面(212)且每個PTE為4位元組,在單級頁表情況下,那麼頁表大小為16TB(4 * 264 / 212)= 16TB,若我們劃分為3級,如下:
那麼對於上述外部頁即頁目錄索引將需要佔記憶體4 * 232 = 16GB,所以我們仍需繼續劃分層級,但是每個層級都有一個額外的間接方式,因此會產生額外的開銷。比如64位地址空間在4KB頁面上將使用大地址空間,所以多級頁表成為具有小頁的大地址空間的記憶體消耗。
雜湊頁表
處理大於32位地址空間常用的方法是使用雜湊頁表(使用稀疏的地址空間),採用虛擬頁碼作為雜湊值,對於每一個PTE使用連結串列結構儲存從而解決衝突或碰撞,每個元素由三個欄位組成:虛擬頁碼、對映的頁幀、指向連結串列內下一個元素的指標。通過雜湊演算法將虛擬頁碼對映到雜湊頁表,然後將虛擬頁碼與連結串列第一個元素的第一個欄位進行比較,若匹配則將第二個欄位用來形成實體地址,否則遍歷連結串列查詢對應匹配項。雜湊頁表如下圖所示
雖然通過雜湊頁表查詢很快,同時採用如上劃重點標記的連結串列資料結構解決衝突問題,雖說消除了條目在記憶體中連續的需求,但是仍然以更高的記憶體開銷進行儲存即消耗更多記憶體,特別是如果頁表是完整的,並且具有有效/無效位以使未使用的條目無效,那麼雜湊頁表不再那麼適用,此時我們採用其他方案,如下倒置頁表。
倒置頁表
通過前面內容學習我們知道對於每個程式都有一個關聯的頁表,該程式中的每一個虛擬頁都在頁表中對應一項,不管是否有效,程式通過虛擬地址引用頁,作業系統通過計算虛擬地址在頁表中的位置即PTE,但這種方式有明顯的缺點,如上我們也敘述過,每個頁表可能包含數以百萬計的條目,如此一來,頁表將佔用大量的實體記憶體以跟蹤其他實體記憶體是如何使用的,為解決這個問題,我們可以使用倒置頁表(inverted page table),對於每個真正的記憶體頁,倒置頁表才有一個條目,每個條目包含儲存在真正記憶體位置上的頁的虛擬地址,以及擁有該頁程式的資訊,因此,整個系統中所有程式將只有一個頁表,並且每個實體記憶體的頁只有一個相應的條目,換言之,與知道每個程式的虛擬頁在哪裡相反,現在我們知道擁有哪個物理頁的程式與它對應的虛擬頁。IBM是最早採用倒置頁表的公司,從IBM System 38、RS/6000、到現代的IBM Power CPU。對於IBM RT,系統的虛擬地址包含三部分:程式Id、頁碼、頁偏移量,每個倒置頁表條目包含兩部分:程式Id、頁碼,這裡的程式Id作為地址空間的識別符號,當發生記憶體引用時,由程式Id和頁碼組成的虛擬地址被提交到記憶體子系統,然後搜尋倒置頁表來定址匹配,如果找到匹配條目,則生成實體地址,如果未找到匹配條目則為非法地址訪問。 倒置頁表結構如下:
雖然倒置頁表減少了儲存每個頁表所需的記憶體空間,但是它增加了由於引用頁而查詢頁表所需要的時間,由於倒置頁表是按照實體地址排序,而查詢則是根據虛擬地址,因此查詢匹配可能需要搜尋整個表,這種搜尋需要耗費很長時間,為解決這個問題,可以使用一個雜湊表結構,從而將搜尋限制在一個或最多數個頁表條目,當然,每訪問雜湊表就增加了一次記憶體引用,因此每次虛擬地址的引用至少需要兩個記憶體讀,一個用於雜湊表條目,另一個用於頁表條目即PTE,同時結合前面所學,在搜尋雜湊表之前,肯定先搜尋TLB,這樣可大大提高效能。對於倒置頁表還會帶來一個問題,那就是實現共享記憶體,共享記憶體需要將多個虛擬地址對映到同一實體地址,很顯然,這種標準的方式無法應用於倒置頁表,因為每一個物理頁只有一個虛擬頁條目,一個物理頁不可能有兩個或多個共享的虛擬地址,所以為解決這個問題,只能允許頁表包含一個虛擬地址到共享實體地址的對映,這也就意味著,對於未對映的虛擬地址的引用勢必會導致頁錯誤。
總結
本節我們非常詳細的討論了多級頁表結構、對於雜湊頁表和倒置頁表資料結構通過看圖理解起來非常簡單,從本節內容我們可總結出:對應頁表結構可以擁有良好的時間複雜度或空間複雜度,但不能同時兼得。到此關於虛擬記憶體重要內容基本上都已囊括,若有遺漏,後續我會繼續進行補充。接下來我們將進入記憶體管理分頁和分段的學習,講完之後,會陸續進入到程式的執行、程式、死鎖、併發等,相信大家會比較感興趣,感謝您的閱讀,我們下節再見。