前言
上一節我們整體概括通過MMU將虛擬地址翻譯為實體地址的轉換,這個過程都是按序就班的進行,一切都是基於已提前建立、分配虛擬頁、物理頁以及命中的前提,只是給和我一樣沒怎麼系統學習作業系統的童鞋首先在腦海裡有個大概的印象,本節我們從源頭開始分析為程式建立程式到對映到主存上整個詳細過程,本文將通過大量圖解來分析原理,以便讓各位能夠完全理解地址翻譯原理。若有敘述不當之處,還請批評指正。
虛擬記憶體原理分析
如果沒有系統學習現代作業系統,理論上我們會認為使用者程式會將記憶體視為單個連續的記憶體空間,實際上可以將使用者程式在記憶體中分佈可以分散在頁面的整個實體記憶體中。分頁是一種記憶體管理方案,它允許程式的實體地址空間不連續。
實體記憶體劃分:將實體記憶體劃分為稱為幀的固定大小的塊(大小為2的冪,介於512位元組和16 MB之間,必須跟蹤所有空閒幀)
虛擬(邏輯)記憶體劃分:將邏輯記憶體分成大小相同的塊(稱為頁,每一塊也是分為相同大小的頁面)
若要執行大小為N頁的程式,需要找到N個空閒幀並載入程式
地址翻譯方案
通過常駐記憶體中的頁表將虛擬地址翻譯為實體地址, CPU生成的虛擬地址被劃分為虛擬頁號(用作頁表索引,該頁表包含實體記憶體中每個頁的基地址)和虛擬頁偏移量(與基址結合找到儲存單元的物理儲存地址)。對於給定的邏輯地址空間2m和頁面大小2n,如下:
分頁記憶體管理方案本質就是通過MMU將CPU產生的虛擬地址通過中間媒介(頁表)進行地址翻譯,如下為簡單翻譯版本,一目瞭然。
上述我們學習了將邏輯地址(虛擬地址)劃分為頁號(注意:頁號並不屬於頁表的一部分,頁號不儲存在主存)和頁偏移量,到底是怎樣藉助頁號和頁偏移量進行翻譯的呢?我們舉個例子:假設如下一個32位元組的實體記憶體,邏輯地址空間為16位元組,說明邏輯地址有4位,而頁幀偏移量為4個位元組,因頁幀偏移量和虛擬頁偏移量相等,所以虛擬頁偏移量也為4個位元組即2位,所以頁號為(4-2)= 2位即邏輯地址共有4頁,如此假設和實際理論計算對等。地址翻譯如下:
若CPU要找出邏輯地址為4的實體地址,通過上述我們知道邏輯地址為4在第1頁且偏移量為0,然後去查詢頁表索引等於1的頁幀號為6,因為實體地址 = (frame * pageSize)+ offset,所以邏輯地址4的實體地址=(6 * 4 bytes)+ 0 byte offset = 24。同理,比如如上邏輯地址為7在第1頁,偏移量為3對應頁表上的幀6,所以其實體地址為:(6 * 4 bytes)+ 3 byte offset = 27,這裡需要注意的是實體地址的偏移量是相對這頁的起始位置偏移。通過上述圖解,我們反推根據邏輯地址和每頁位元組大小計算出其所在頁和偏移量(下面根據虛擬地址計算虛擬頁號和偏移量會用到),比如邏輯地址為7,因每頁大小為4個位元組,則所在頁為(7 / 4) = 1,偏移量(7 mod / 4) = 3。
擴充套件頁表條目(PTE)資訊
現代計算機頁表上的條目除了包含將虛擬地址翻譯為虛擬地址的主要資訊(有效位、頁號)外,其中還包含如下其他資訊(下面講解頁面置換演算法會用到):
保護位(Protection):控制對指定虛擬頁的訪問是否可讀、可寫、可執行
引用位(Refrence):為了近似實現LRU演算法,幫助作業系統估算最近最少使用的頁,當一頁被訪問時該位將被置位,作業系統定期將引用位清零,然後重新記錄,這樣就可以判定在這段特定時間內哪些頁被訪問過,通過檢查引用位是否關閉,作業系統就可以從那些最近最少訪問的頁中選擇一頁
髒位(Modify):當某一頁被替換時,作業系統需要知道該頁是否需要被複制寫回,為了追蹤讀入主存中的頁是否被寫過,增加一個髒位,當頁中任何字被寫時就將這一位置位。如果作業系統選擇替換某一頁,髒位指明瞭把該頁所佔用的主存讓給另一頁之前,是否需要將該頁寫回磁碟,因此,一個被修改的頁通常被稱為髒頁。
TLB快取頁表
上一節我們講過CPU產生邏輯地址後通過MMU轉換為實體地址時,每次都要訪問頁表,訪問快取和主存的時間相差上百個時鐘週期,所以為了提高查詢效能則使用TLB,我們可認為TLB是實現頁表最好的方式,本質上是快取頁表。在沒有TLB作為快取時,我們使用頁號(VPN)作為索引去頁表上查詢物理頁號,引入TLB後,將頁號劃分為TLBT(TLB標記)和TLBI(TLB索引)只是做了一下轉換而已,TLBI佔2位,剩餘的位就是TLBT。下面會通過一個實際例子來講解如何結合TLBT和TLBI在TLB上查詢。
TLB作為頁表的快取,用於存放對映到頁幀中的那些項,TLB包含了頁表中虛擬頁到頁幀對映的一個子集,因為將其作為快取,所以額外還存在如上一個標記區域(TLBT),換句話說頁表不同於TLB並不是作為快取,所以並不需要標記區域,再加上如上額外的PTE擴充套件資訊,所以TLB的儲存結構如下:
TLB缺失
接下來我們開始進入TLB缺失環節,我們假設虛擬地址有14位,實體地址有16位,每頁大小有64個位元組,那麼虛擬地址空間和實體地址空間如下圖所示
因為每頁大小為64位元組即(26),同時虛擬頁偏移量和頁幀偏移量相等,所以虛擬頁偏移量和頁幀偏移量都為6位,那麼將虛擬地址空間和實體地址空間劃分為對應的頁號和頁偏移量則如下圖所示:
接下來則是將虛擬頁號劃分為TLBT和TLBI,因為TLB包含16個條目且4路關聯,那麼說明有S =(16 / 4)= 4組,那麼TLBI佔位 = log2S = 2,剩餘的則是TLBT = (8 - 2) = 6位,如下圖所示
現在我們對虛擬地址和實體地址都有了完整的劃分,現在假設TLB和頁表狀態儲存結構分別如下圖
假設現在CPU產生一個虛擬地址(0x0334),首先我們需要將其轉換為虛擬頁號(VPN),因每個頁面大小為64位元組,所以計算方式如下程式碼
var xvpn = Convert.ToInt32("0x334", 16); var vpn = xvpn / 64; //vpn = 12 var vpo = xvpn % 64; //vpo = 52
上述計算出VPN等於12,然後將其對應虛擬地址上的VPN和VPO用二進位制表示,分別如下圖所示
而儲存在TLB和頁表上的狀態都是16進位制,所以上述VPN = 1210 = 0x0C16和VPO = 5210 = 0x3416,到此已經劃分完VPN和VPO,接下來則是將VPN劃分為TLBT和TLBI,由上述我們已經知道TLBT和TLBI在VPN中所佔位數,所以如下圖所示
由上我們可得出TLBT = 310 = 0x0316,而TLBI = 0,有了TLBT(0x03)和TLBI(0)再去查詢TLB狀態表,如下紅色標記
由上圖我們發現此時標誌無效而且物理頁號也沒有,此時發生TLB缺失,於是通過MMU將虛擬地址得到的VPN去頁表中查詢
此時我們看到在頁表中也缺失,所以這裡將發生缺頁異常。TLB缺失分為如下兩種情況
頁在主存(頁表)中,只需要建立缺失的TLB表項
頁不在主存(頁表)中,需要將控制權交給作業系統來解決缺頁
TLB缺失既可以通過軟體處理也可以通過硬體處理,比如MIPS、Alpha通過軟體處理TLB缺失,x86、ARM通過硬體處理TLB缺失,兩種處理方式在效能差別上很小,無論哪一種方式需要執行的基本操作都是一樣的。理論上來講,在程式分配頁幀時會將對應頁幀更新到頁表上,但是上述情況並未在主存頁表中說明在頁幀列表中沒有空閒的頁幀,所以這是TLB缺失中真正的缺頁情況,此時將觸發缺頁異常,控制權交給作業系統核心中的缺頁異常處理程式,作業系統知道了引起缺頁的虛擬地址,作業系統必須完成以下3個步驟:【1】使用虛擬地址查詢頁表項,並在磁碟上找到被訪問的頁的位置【2】選擇替換一個物理頁,如果該選中的頁被修改過,需要在把新的虛擬頁裝入之前將這個物理頁寫回磁碟,這一過程稱為頁面置換【3】啟動讀操作,將被訪問的頁從磁碟上取回到所選擇的物理頁的位置上【4】重新執行引發缺頁的那條指令。因為第3個步驟需要耗費數百萬個時鐘週期,如果第2個步驟中被替換的物理頁已被重寫過,那麼同樣也會花費這麼長時間,因此作業系統會選擇另外一個程式在處理器上執行直到磁碟訪問結束,所以前3個步驟執行所耗費的時間比較長,最後重新執行缺頁指令。若在頁表中找到了頁幀號(即頁在主存中),那說明TLB缺失只是一次轉換缺失,在這種情況下,CPU只需要將頁表項裝載到TLB並且重新訪問來進行缺失處理。
頁面置換演算法
為了解決缺頁情況,所以必須實現頁面置換作為請求調頁的基礎,這裡我們介紹常見的幾種置換演算法,分別是Optional or MIN algorithm、FIFO(First-In-First-Out)、Clock、LRU(Least Recently Used),針對各個演算法,現假設有(1、2、3、4、1、2、5、1、2、3、4、5)12個引用串,4個空閒頁幀。
FIFO(先進先出)
該演算法記錄了每個頁面記錄調到記憶體的時間,當必須置換頁面時,將選擇最舊的頁面,請注意,並不需要記錄調入頁面的確切時間,可以通過建立一個佇列實現此目的。具體過程太過簡單,這裡就不再細講,此時將發生10次缺頁錯誤,我們可計算出缺頁率為(10/12)= 83%。如下:
OPT or MIN(最優)
最優置換演算法找出最長時間沒有使用的頁,具有最低缺頁率,可以用作離線分析方法,但是難以實現。此時將發生6次缺頁錯誤,我們可計算出缺頁率為(6/12)= 50%。如下:
LRU(最近最少使用)
FIFO演算法使用的是頁面調入記憶體的時間,OPT演算法使用的是頁面將來使用的時間,而LRU演算法採用置換最長時間沒有的頁,該演算法將每個頁面與它上次使用的時間關聯起來,當需要置換頁面時,LRU選擇最長時間沒有使用的頁面,該演算法很難實現。此時將發生8次缺頁錯誤,我們可計算出缺頁率為(8/12)= 67%。如下:
啟動和切換程式
上述我們只是從已經將程式載入到記憶體中所建立的程式角度來分析如何將虛擬地址翻譯為實體地址,由於作業系統負責管理記憶體,因此必須瞭解實體記憶體的分配詳細資訊,分配了哪些頁幀、每個頁幀分配個哪個程式的哪個頁面,哪些頁幀可用,總共有多少幀,對此我們還一無所知。將使用者程式載入到虛擬記憶體中的程式後為其劃分對應的虛擬頁,假設如下劃分了4個虛擬頁,作業系統在跟蹤的頁幀列表找出空閒(作業系統分配幀演算法,這裡暫不討論)的頁幀分配給虛擬頁,然後作業系統再啟動程式。如下圖:
如上節所述頁表儲存在主存中,當排程程式時通過頁表基址暫存器(PTBR)指向啟用的指定程式頁表, 當然也會載入另外一個暫存器(程式計數器,PC),所以每個資料或指令訪問需要進行兩次主存訪問,一次是頁表,另一次則是用於資料或指令。
當程式希望以受限的方式共享資訊時,作業系統必須對其進行協助,這是因為訪問另外一個程式的資訊需要改變訪問程式的頁表,寫訪問位可以用來把共享限制為只讀,並且和頁表中其他位一樣,該位只能被作業系統所修改。為了允許另一程式,設為P1,去讀屬於程式P2的一頁,P2就要請求作業系統在P1地址空間中為一個虛擬頁生成頁表項,指向P2想要共享的物理頁。如果P2要求作業系統可以使用防寫位以防止P1對資料進行改寫,由於只有TLB缺失才會訪問頁表,任何決定頁對的訪問許可權不僅要包含在頁表中,還要包含在TLB中。當作業系統決定從程式P1切換到P2時,我們稱之為上下文切換,它必須保證P2不能P1的頁表,否則不利於資料保護,若沒有TLB,只需要把頁表基址暫存器指向P2的頁表而不是P1就夠了,如果有TLB,我們必須在其中清除屬於P1的表項,不僅僅是為了保護P1的資料,而且是為了迫使TLB裝入P2的表項。如果程式切換的頻率很高,那麼這一舉措效率將會很低。例如,在作業系統切回P1之前,P2可能只裝入了很少的TLB表項,但是,P1隨後發現它所有的表項都不見了,因此不得不通過TLB缺失來重新載入這些表項,產生這個問題的原因在於程式P1和P2使用同一虛擬地址空間,並且我們必須清除TLB以防止地址混淆。另一種常見的方法則是增加程式識別符號和任務識別符號來擴充套件虛擬地址空間,比如FastMATH就有8位地址空間標識域(ASID),這個標識域標識了當前正在執行的程式,當程式切換時,它儲存在由作業系統裝入的暫存器中,程式識別符號與TLB的標記部分相連線,因此只有在頁號和程式識別符號相匹配時,TLB才會發生命中,如此一來,除非特殊情況,我們就不需要清除TLB。 說了怎麼多除了保護機制外,當我們切換程式時主要需要做哪些工作呢(即從一個程式控制塊(Process Control Block,PCB)切換到另一個程式塊,後續會深入講解作業系統執行緒和程式)?
切換頁表到當前PCB
頁表基址暫存器指向當前頁表
清除TLB,並將當前頁表項裝載到TLB(按需載入,程式訪問哪些頁才將對應頁表項載入到TLB)
留個作業
若TLB中的PTE條目達到上限即滿時,不難想象理論上會替換現有條目,那麼採取替換的策略或機制是怎樣的呢?
總結
基於上一節內容我們詳細講解了將虛擬地址翻譯為實體地址的具體過程、程式頁幀分配、頁面置換演算法,在講解TLB缺失時並未涉及快取記憶體,TLB和快取記憶體將在下一節作為詳解。關於虛擬記憶體內容通過一兩篇文章根本講解不清楚,比如還有減少頁表容量方式、TLB和快取記憶體關係、Intel和Linux虛擬記憶體系統等等。我儘量通過圖解方式來帶給大家較好的理解體驗,能夠更好的消化和吸收虛擬記憶體。