1. 程式人生 > >CSAPP學習筆記—虛擬記憶體

CSAPP學習筆記—虛擬記憶體

CSAPP學習筆記—虛擬記憶體

 

 

符號說明

 

虛擬記憶體地址定址

    圖9-12展示了MMU如何利用頁表來實現這種對映。CPU中的一個控制暫存器,頁表基址暫存器(Page Table Base Register,PTBR)指向當前頁表。N位的虛擬地址包含兩個部分:一個p位的虛擬頁表偏移(Virtual Page Offset,VPO)和一個(n-p)位的虛擬頁號(Virtual Page Number,VPN)。MMU利用VPN來選擇適當的PTE。將頁表條目中物理頁號(Physical Page Number,PPN)和虛擬地址中的VPO串聯起來,就得到了相應的實體地址。需要注意的是物理和虛擬頁面都是p位元組的,所以物理頁面偏移(Physical Page Offset,PPO)和VPO是相同的。

    下圖展示了當頁面命中時,CPU硬體執行的步驟。

第一步:處理器生成一個虛擬地址,並把它傳送給MMU。

第二步:MMU生成PTE地址,並從快取記憶體/主存請求得到它。

第三步:快取記憶體/主存向MMU返回PTE。

第四步:MMU構造實體地址,並把它傳送給快取記憶體/主存。

第五步:快取記憶體/主存返回所請求的資料字給處理器。

頁面命中完全是由硬體來處理的,與之不同的是,處理缺頁要求硬體和作業系統核心協作完成。過程如下圖所示:

第一步到第三步和上圖的過程是一樣的。

第四步:PTE中的有效位是0,所以MMU觸發了一次異常,傳遞CPU中的控制到作業系統核心中的缺頁異常處理程式。

第五步:缺頁處理程式確定出實體記憶體中的犧牲頁,如果這個頁面已經被修改了,則把它換出到磁碟。

第六步:缺頁處理程式頁面調入新的頁面,並更新記憶體中的PTE。

第七步:缺頁處理程式返回到原來的程序,再次執行導致缺頁的命令,CPU將引起缺頁的虛擬地址重新發送給MMU。因為虛擬頁面現在快取在實體記憶體中,所以就會命中。

利用TLB加速地地址翻譯

許多系統會在MMU中包括一個關於PTE的小的快取,稱為翻譯後備緩衝器(Translation Lookaside Buffer,TLB)。

    TLB是一個小的、虛擬定址的快取,其中每一行都儲存著一個由單個PTE組成的塊。TLB通常有高度的相聯度。

    如圖所示,用於組選擇和行匹配的索引和標記欄位是從虛擬地址中的虛擬頁號中提取出來的。如果TLB有T=個組,那麼TLB索引(TLBI)是由VPN的t個最底位組成的,而TLB標記(TLBT)是由VPN中剩餘的位組成的。

多級頁表

    如果我們有一個32位的地址空間,4KB的頁面和一個4位元組的PTE,那麼即使應用所引用的只是虛擬地址空間很小的一部分,也總是需要一個4MB的頁表駐留在記憶體中。對於地址空間為64位的系統來說,問題將變得更復雜。

這種方法從兩個方面減少了記憶體要求。第一,如果一級頁表中的一個PTE是空的,那麼對應的二級頁表就根本不會存在。這代表著一種巨大的潛在節約,因為對於一個典型的程式,4GB的虛擬地址空間的大部分都是未分配的。第二,只有一級頁表才需要總是在主存中;虛擬記憶體系統可以在需要時建立、頁面調入或調出二級頁表,這就減少了主存的壓、力。只有最經常使用的二級頁表才需要快取在主存中。

    對於K級的頁表層次結構的地址翻譯如下:

    

案例研究: Intel Core i7/Linux記憶體系統

    一個執行Linux的Intel Core i7,雖然底層的Haswell微體系結構允許完全的64位虛擬和實體地址空間,而現在(以及可預見的未來)Corei7實現支援48位(256TB)虛擬地址空間和52位(4PB)實體地址空間,還有一個相容模式,支援32位(4GB)虛擬和實體地址空間。

    如果所示給出了Core i7記憶體系統的重要部分。處理器封裝(progressor package)包括四個核,一個大的所有核共享的L3快取記憶體,以及一個DDR3記憶體控制器。每個核包含一個層次結構的TLB,一個層次結構的資料和指令快取記憶體,以及一組快速地點到點鏈路,這種鏈路基於QuickPath技術,是為了讓一個核與其他核和外部I/O橋直接通訊。TLB是虛擬定址的,是四路組相聯的。L1,L2和L3快取記憶體是物理定址的,塊大小為64位元組。L1和L2是8路組相聯的。而L3是16組相聯的。頁大小可以在啟動時被配置為4KB或4MB。Linux使用的是4KB的頁。

Core i7 地址翻譯

    圖9-22總結了完整的Core i7地址翻譯過程,從CPU產生虛擬地址的時刻一直到來自記憶體的資料字到達CPU。Core i7採用四級頁表層次結構。每個程序都有它自己私有的頁表層次結構。當一個Linux程序在執行時,雖然Core i7體系結構允許頁表換進換出,但是與已分配了的頁相關聯的頁表都是駐留在記憶體中的。CR3控制暫存器指向第一級頁表(L1)的起始位置。CR3的值是每個程序上下文的一部分,每次上下文切換時,CR3的值都會被恢復。

    下圖中給出了第一級,第二級,第三極頁表中條目的格式。

    當P=1時(Linux中就總是如此),地址欄位包含了一個40位物理頁號(PPN),它指向適當的頁表的開始處。注意,這強加一個要求,要求物理頁表4KB對齊。

    圖9-24給出了第四級頁表中條目的格式。當p=1,地址欄位包括一個40位PPN,它指向實體記憶體中某一頁的基地址。這又強加了一個要求,要求物理頁4KB對齊。

    PTE有三個許可權位,控制對頁的訪問。R/W位確定頁的內容是可以讀寫的還是隻讀的。U/S為確定是否能夠在使用者模式中訪問該頁,從而保護作業系統核心中的程式碼和資料不被使用者程式訪問。XD(禁止執行)位是在64位系統中引入的,可以用來禁止從某些記憶體頁取指令。這是一個重要的新特性,通過限制只能執行只讀程式碼段,使得作業系統核心降低了緩衝區溢位攻擊的風險。

    當MMU翻譯每一個虛擬地址時,它還會更新另外兩個核心缺頁處理程式會用到的位每次訪問一個頁時,MMU都會設定A位,稱為引用位(reference bit)。核心可以用這個引用來實現它的頁替換演算法。每次對一個頁進行了寫之後,MMU都會設定D位,又稱修改位或髒位(dirty bit)。修改位告訴核心在複製替換頁之前是否必須寫回犧牲頁。核心可以通過呼叫一條特殊的核心模式指令來清除引用位或修改位。

    下圖給出了Core i7 MMU如何使用四級的頁表來將虛擬地址翻譯成實體地址。36位VPN被劃分成四個9位的片,每個片被用作到一個頁表的偏移量。CR3暫存器包含L1頁表的實體地址。VPN1提供到一個L1 PET的偏移量,這個PTE包含L2頁表的基地址。VPN2提供到一個L2 PTE的偏移量,以此類推。

Linux虛擬記憶體系統

    Linux為每個程序維護了一個單獨的虛擬地址空間,它包括程式碼,資料,堆,共享庫以及棧段。

    核心虛擬記憶體包括核心中的程式碼和資料結構。核心虛擬記憶體的某些區域被對映到所有程序共享的物理頁面。例如,每個程序共享核心的程式碼和全域性資料結構。有趣的是,Linux也將一組連續的虛擬頁面(大小等於系統中DRAM的總量)對映到相應的一組連續的物理頁面。這就為核心提供了一種便利的方法來訪問實體記憶體中任何特定的位置,例如,當它需要訪問頁表,或一些裝置上執行的記憶體對映的I/O操作,而這些裝置被對映到特定的實體記憶體位置時。

    核心虛擬記憶體的其他區域包含每個程序都不相同的資料。比如說,頁表、核心在程序的上下文中執行程式碼時使用的棧,以及記錄虛擬地址空間當前組織的各種資料結構。

Linux虛擬記憶體區域

Linux將虛擬記憶體組織成一些區域(也叫做段)的集合。一個區域(area)就是已經存在著的(已分配的)虛擬記憶體的連續片(chunk),這些也是以某種方式相關聯的。例如,程式碼段,資料段,堆,共享庫段,以及使用者棧都是不同的區域。每個存在虛擬頁面都儲存在某個區域中,而不屬於某個區域的虛擬頁是不存在,並且不能被程序引用。區域的概念很重要,因為它允許虛擬地址空間有間隙。核心不用記錄那些不存在的虛擬頁,而這樣的頁也不佔用記憶體、磁碟或者記憶體本身中的任何額外資源。

    圖9-27強調了記錄一個程序中虛擬記憶體區域的核心資料結構。核心為系統中的每個程序維護一個單獨的任務結構(原始碼中的task_struct)。任務結構中的元素包含或者指向核心執行該程序所需要的所有資訊(例如,PID,指向使用者棧的指標,可執行目標檔案的名字,以及程式計數器)。

    任務結構中的一個條目指向mm_struct,它描述了虛擬記憶體的當前狀態。我們感興趣的兩個欄位是pgd和mmap,其中pgd指向第一級頁表(頁全域性目錄)的基址,而mmap指向一個vm_area_structs(區域結構)的連結串列,其中每個vm_area_structs都描述了當前虛擬地址空間的一個區域。當核心執行這個程序時,就將pgd存放在CR3控制暫存器中。

    

記憶體對映

    Linux通過將一個虛擬記憶體區域與一個磁碟上的物件(object)關聯起來,以初始化這個虛擬記憶體區域的內容,這個過程稱為記憶體對映(memory mapping)。虛擬記憶體區域可以對映到兩種型別的物件中的一種:

Linux檔案系統中的普通檔案:一個區域可以對映到一個普通磁碟檔案的連續部分,例如一個可執行的目標檔案。檔案區(section)被分成頁大小的片,每一片包含一個虛擬頁面的初始內容。因為按需進行頁面排程,所以這些虛擬頁面沒有實際交換進入實體記憶體,直到CPU第一次引用到頁面(即發射一個虛擬地址,落在地址空間這個頁面的範圍之內)。如果區域比檔案區要大,那麼就用零來填充這個區域的餘下部分。

匿名檔案:一個區域也可以對映到一個匿名檔案,匿名檔案是由核心建立的,包含的全是二進位制零。CPU第一次引用這樣一個區域內的虛擬頁面時,核心就在實體記憶體中找到一個合適的犧牲頁面,如果該頁面被修改過,就將這個頁面換出來,用二進位制零覆蓋犧牲頁面並更新頁面,如果該頁面標記為是駐留在記憶體中的。注意在磁碟和記憶體之間並沒有實際的資料傳送。因為這個原因,對映到匿名檔案的區域中的頁面有時也叫做請求二進位制零的頁(demand-zero page)。

    無論哪種情況下,一旦一個虛擬頁面被初始化了,它就在一個由核心維護的專門的交換檔案(swap file)之間換來換去。交換檔案也叫作交換空間(swap space)或者交換區域(swap area)。需要意識到的很重要的一點是,在任何時刻,交換空間都限制著當前執行著的程序能夠分配的虛擬頁面的總數。

小結

    虛擬記憶體是對主存的一個抽象。支援虛擬記憶體的處理器通過使用一種叫做虛擬定址的間接形式來引用主存。處理器產生一個虛擬地址,在被送到主存之前,這個地址被翻譯成一個實體地址。從虛擬地址空間到實體地址空間的地址翻譯要求應硬體和軟體緊密合作。專門的硬體通過使用頁表來翻譯虛擬地址,而頁表的內容是由操作提供的。

    虛擬記憶體提供三個重要的功能。第一,它在主存中自動快取最近使用的存放磁碟上的虛擬地址空間的內容。虛擬記憶體快取中的塊叫做頁。對磁碟上頁的引用會觸發缺乏,缺頁將控制轉移到作業系統中的一個缺頁處理程式。缺頁處理程式將頁面從磁碟複製到主存快取,如果必要,將寫回被驅逐的頁。第二,虛擬記憶體簡化了記憶體管理,進而又簡化了連結,在程序間共享資料,程序的記憶體分配以及程式載入。最後,虛擬記憶體通過在每條頁表條目加入保護位,從而簡化了記憶體保護。

    地址翻譯的過程必須和系統中所有的硬體快取的操作整合在一起。大多數頁表條目位於L1快取記憶體中,但是一個稱為TLB的頁表條目的片上快取記憶體,通常會消除訪問在L1上的頁表條目的開銷。

    現代系統通過將虛擬記憶體片和磁碟上的檔案片關聯起來,來初始化虛擬記憶體片,這個過程稱為記憶體對映。記憶體對映為共享資料、建立新的程序以及載入程式提供了一種高效的機制。應用可以使用mmap函式來手工地建立和刪除虛擬地址空間的區域。然而,大多數程式依賴於動態記憶體分配器,例如malloc,它管理虛擬地址空間區域內一個稱為堆的區域。動態記憶體分配器是一個感覺像系統級程式的應用級程式,它直接操作記憶體,而無需型別系統的很多幫助。分配器有兩種型別。顯示分配器要求應用顯示釋放它們的記憶體塊。隱式分配器(垃圾收集器)自動釋放任何未使用的和不可達的塊。

    對於C程式設計師來說,管理和使用虛擬記憶體是一件困難和容易出錯的任務。常見的錯誤示例包括:間接引用壞指標,讀取未初始化的記憶體,允許棧緩衝區溢位,假設指標和它們指向的物件大小相同,引用指標而不是它所指向的物件,誤解指標運算,引用不存在的變數,以及引起記憶體洩漏。