1. 程式人生 > >作業系統概念學習筆記 16 記憶體管理(二) 段頁

作業系統概念學習筆記 16 記憶體管理(二) 段頁

作業系統概念學習筆記 16

記憶體管理 (二)

分頁(paging)

分頁(paging)記憶體管理方案允許程序的實體地址空間可以使非連續的。分頁避免了將不同大小的記憶體塊匹配到交換空間上(前面敘述的記憶體管理方案都有這個問題,當位於記憶體中的程式碼和資料需要換出時,必須現在備份儲存上找到空間,這是問題就產生了。備份儲存也有前面所述的與記憶體相關的碎片問題,只不過訪問更慢)。

傳統上,分頁支援一直是由硬體來處理的。最近的設計是通過將硬體和作業系統相配合來實現分頁。

基本方法

實現分頁的基本方法設計將實體記憶體分為固定大小的塊,稱為幀(frame);而將邏輯記憶體也分為同樣大小的塊,稱為頁(page)

。當需要執行程序時,其頁從備份儲存中調入到可用的記憶體幀中。備份儲存也分為固定大小的塊,其大小與幀相同。

這裡寫圖片描述

由CPU生成個每個地址分為兩個部分:頁號(p)和頁位移(d)。頁號作為頁表的索引。頁表包含每頁所在實體記憶體的基地址,這些基地址與頁偏移的組合形成實體地址,就可送交物理單元。

這裡寫圖片描述

頁大小(與幀大小一樣)是由硬體來決定的。通常為2的冪。選擇頁的大小為2的冪可以方便的將邏輯地址轉換為頁號和頁偏移。如果邏輯地址空間為2^m,且頁大小為2^n單元,那麼邏輯地址的高m-n位表示頁號(頁表的索引),而低n位表示頁偏移。每頁大小從512B到16MB不等。

這裡寫圖片描述

設頁大小為a,根據頁號p得到基地址f,頁偏移為d,則實體地址為f*a+d

分頁是一種動態重定位。每個邏輯地址有分頁硬體繫結為一定的實體地址。採用分頁類似於使用一組基(重定位)地址暫存器,每個基地址對應這一個記憶體幀。

採用分頁技術不會產生外部碎片:每個幀都可以分配給需要它的程序。不過分頁有內部碎片。

每個頁表的條目通常為4B,不過這是可變的,一個32位的條目可以指向2^32個物理幀的任何一個,如果幀為4KB,那麼具有4B條目的系統可以訪問2^44B大小。

當系統程序需要執行時,它將檢查該程序的大小(按頁計算)。程序的每頁都需要一幀。因此,如果程序需要n頁,那麼記憶體中至少應有n個幀。如果有那麼就分配給新程序。程序的第一頁裝入一個已分配的幀,幀號放入程序的頁表中。下一頁分配給另一幀,其幀號也放入程序的頁表中。

分頁的一個重要特點是使用者視角的記憶體和實際的實體記憶體的分離。使用者程式將記憶體作為一整塊來處理,而且它只包括這一個程序。事實上,一個使用者程式與其他程式一起,分佈在實體記憶體上。

使用者視角的記憶體和實際的實體記憶體的差異是通過地址轉換硬體協調的。邏輯地址轉換為實體地址,這種對映是使用者所不知道的,但是受作業系統所控制。注意使用者程序根據定義是不能訪問非它所佔用的記憶體的。它無法訪問其頁表所規定之外的記憶體,頁表只包括程序所擁有的那些頁。

由於作業系統管理實體記憶體,它必須知道實體記憶體的分配細節:哪些幀已佔用,哪些幀可用,總共有多少幀等。這些資訊通常儲存在幀表中。在幀表(frame table)中,每個條目對應一個幀,以表示該幀是空閒還是已佔用,如果被佔用,是被哪個程序的哪個頁所佔用。

另外,作業系統必須意識到使用者程序是在使用者空間內執行,且所有邏輯地址必須對映到實體地址。如果使用者執行一個系統呼叫(如進行I/O),並提供地址作為引數,那麼這個地址必須對映成實體地址。作業系統為每個程序維護一個頁表副本,就如同它需要維護指令計數器和暫存器的內容一樣。當作業系統必須手工將邏輯地址對映成實體地址時,這個副本可用來將邏輯地址轉換為實體地址。當一個程序可分配到CPU時,CPU排程程式可以根據該副本來定義硬體頁表。因此,分頁增加了切換時間。

硬體支援

每個作業系統都有自己的方法來儲存頁表。絕大多數都為每個程序分配一個頁表。頁表的指標與其他暫存器的值(如指令計數器)一起存入程序控制塊。當排程程式需要啟動一個程式時,它必須首先裝入使用者暫存器,並根據所儲存的使用者頁表來定義正確的硬體頁表值。

頁表的硬體實現有很多方法。最為簡單的是將頁表作為一組專用暫存器(register)來實現。這些暫存器應用高速邏輯電路來構造,以便有效的進行分頁地址的轉換。由於對記憶體的每次訪問都要經過分頁表,因此效率很重要。CPU裝入或修改頁表暫存器的指令是特權級的,因此只有作業系統才可以修改記憶體對映圖。

如果頁表比較小(例如256個條目),頁表使用暫存器還是比較合理的。但是,絕大多數當代計算機都允許頁表非常大(如100萬個條目)。對於這些機器,採用快速暫存器來實現頁表就不可行了,因而需要將頁表放在記憶體中,並將頁表基暫存器(page-table base register,PTBR)指向頁表。改變頁表,只需要改變這一暫存器就可以了,這也大大降低了切換時間。

採用這種方法的問題是訪問使用者記憶體位置需要一些時間。如果要訪問位置i,那麼必須先用PTBR中的值再加上頁號i的偏移,來查詢頁表。這一任務需要記憶體訪問,根據所得的幀號,再加上頁偏移,就得到了真實的實體地址,接著訪問記憶體中所需的位置。採用這種方法,訪問一個位元組需要兩次記憶體訪問(一次用於頁表條目,一次用於位元組),這樣記憶體訪問的速度就減半,在絕大多數情況下這種延遲是無法忍受的。

對這一問題的標準解決方案是採用小但專用快速的硬體緩衝,這種緩衝稱為轉換表緩衝區(translation look-aside buffer,TLB)。TLB是關聯的快速記憶體。TLB條目由兩部分組成:鍵(標籤)和值。當關聯記憶體根據給定值查詢時,它會同時與所有鍵進行比較。如果找到條目,那麼就得到相應的值域。這種查詢方式比較快,不過硬體也比較昂貴,通常,TLB中的條目數並不多,通常在64~1024之間。

TLB與頁表一起按如下方式使用:TLB只包括也表中的一小部分條目。當CPU產生邏輯地址後,其頁號提交給TLB。如果頁碼不在TLB中(稱為TLB失效),那麼就需要訪問頁表。將頁號和幀號增加到TLB中。如果TLB中的條目已滿,那麼作業系統會選擇一個來替換。替換策略有很多,從最近最少使用替換(LRU)到隨機替換等。另外,有的TLB允許有些條目固定下來。通常核心程式碼的條目是固定下來的。

有的TLB在每個TLB條目中還儲存地址空間標識碼(address-space identifier,ASID)。ASID可用來唯一標識程序,併為程序提供地址空間保護。當TLB試圖解析虛擬頁號時,它確保當前執行程序的ASID與虛擬頁相關的ASID相匹配。如果不匹配,那麼就作為TLB失效。除了提供地址空間保護外,ASID允許TLB同時包含多個程序的條目。如果TLB不支援獨立的ASID,每次選擇一個頁表時(例如,上下文切換時),TLB就必須被沖刷(flushed)或刪除,以確保下一個程序不會使用錯誤的地址轉換。

這裡寫圖片描述

頁號在TLB中被查詢到的百分比稱為命中率

80%的命中率意味著有80%的時間可以在TLB中找到所需的頁號。

假如查詢TLB需要20ns,訪問記憶體需要100ns,如果訪問位於TLB中的頁號,那麼採用記憶體對映訪問需要120ns。如果不能在TLB中找到(20ns),那麼必須先訪問位於記憶體中的頁表得到幀號(100ns),並進而訪問記憶體中所需位元組(100ns),這總共需要220ns。為了得到有效記憶體訪問時間,必須根據概率對每種情況進行加權。

有效記憶體訪問時間 = 0.80 * 120 + 0.2 * 220 = 140(ns)

對於這種情況,現在記憶體訪問速度要慢40%(100ns~140ns)

如果命中率為98%,那麼

有效記憶體訪問時間 = 0.98 * 120 + 0.02 * 220 = 122(ns)

由於提高了命中率,記憶體訪問時間只慢了22%

保護

在分頁環境下,記憶體保護是通過與每個幀相關聯的保護為來實現的。通常,這些位儲存在頁表中。

可以用一個位來定義一個頁是可讀寫還是隻讀的。每次地址引用都要通過頁表來查詢正確的幀碼,在計算實體地址的同時,可以檢查保護位來驗證。對只讀頁進行寫操作會向作業系統產生硬體陷阱(或記憶體保護衝突)。

可以很容易的擴充套件這一方法以提供更細緻的保護,可以建立硬體以提供只讀、讀寫、只執行保護。或者,通過為每種訪問情況提供獨立保護位,實現這些訪問的各種組合;非法訪問會被作業系統捕捉到。

還有一個位通常與頁表中的每一條目相關聯:有效-無效位。有效,表示相關的頁在程序的邏輯地址空間內,因此是合法的頁;無效,表示相關的頁不在程序的邏輯地址空間內。通過使用有效-無效位可以捕捉非法地址。作業系統通過對該位可以允許或不允許對某頁的訪問。

有些系統提供硬體如頁表長度暫存器(page-table length register,PTLR)來表示頁表的大小,該暫存器的值可用於檢查每個邏輯地址以驗證其是否位於程序的有效範圍內,如果檢測無法通過,會被作業系統捕獲。

共享頁

分頁的優點之一在於可以共享公共程式碼。這種考慮對分時環境特別重要。考慮一個支援40個使用者的系統,每個使用者都執行一個文字編輯器。如果文字編輯器包括150kb的程式碼和50kb的資料空間。則需要8000kb來支援這40個使用者。如果程式碼是可重入程式碼(reentrant code,也稱為純程式碼),則可以共享。如圖所示,看到3個頁的編輯器(每頁50kb)在三個程序間共享,而每個程序都有自己的資料頁。通過這種方法,只需要在實體記憶體中儲存一個編輯器副本。每個使用者的頁表對映到編輯器的同一物理副本,而資料頁對映到不同幀。因此,為支援40位使用者,只需要一個編輯器副本(150k)再加上40個使用者資料空間副本50kb,總的需求空間為2150kb,而不是8000kb,這是一個明顯的節省。

這裡寫圖片描述

可重入程式碼是不能自我修改的程式碼,它從不會在執行期間改變。兩個或多個程序可以在相同的時間執行相同的程式碼。每個程序都有它自己的暫存器副本和資料儲存,以控制程序執行的資料。兩個不同程序的資料也將不同。

其他常用程式也可以共享,如編譯器,視窗系統,執行時庫,資料庫系統等。

共享程式碼的只讀特點不能只通過正確程式碼來保證,而需要作業系統來強制實現。

一個系統多個程序記憶體共享類似於一個任務的多執行緒地址空間共享。有的作業系統通過實現共享頁來實現共享記憶體。

除了允許多個程序共享同樣的物理頁外,按頁組織記憶體也提供了許多其他優點。

頁表結構

層次結構

絕大多數現代作業系統支援大邏輯地址空間(2^32~2^64)。這樣,頁表本身就非常大。

設想具有32位邏輯地址空間的計算機系統,如果系統的頁大小為4kb(2^12B)那麼,一個頁表可以包含一百萬個條目(2^32/2^12)假設每個條目有4B,那麼每個程序需要4MB的實體地址空間來儲存頁表本身。顯然,我們並不可能在記憶體中連續地分配這個頁表。這個問題的一個簡單解決方法是將頁表劃分為更小部分。劃分方法有很多。

一種方法是使用兩級分頁演算法。就是將頁表再分頁。仍以之前的32位系統為例,一個邏輯地址被分為20位的頁碼和12位的頁偏移d。因為要對頁表進行再分頁,該頁號可分為10位的頁碼p1和10位的頁偏移p2。其中p1用來訪問外部頁表的索引,而p2是是外部頁表的頁偏移。由於地址轉換由外向內,這種方法也稱為向前對映頁表(forward-mapped page table)

這裡寫圖片描述
這裡寫圖片描述

VAX體系結構也支援兩層分頁的變種。邏輯地址分為4個區。邏輯地址頭兩位表示適當分割槽,中間21位表示區內的頁號,後9位表示所需頁中的偏移。作業系統可以只在程序需要時才使用某些分割槽。並且VAX體系結構對使用者程序的頁表進行換頁,以減少對主存的使用。

對於64位的邏輯地址空間的系統分層結構不太適用,對於32位的還可以採用三層分頁方案,甚至四層分頁方案。

雜湊頁表(hashed page table)

處理超過32位地址空間的常用方法是使用雜湊頁表(hashed page table),並以虛擬頁碼作為雜湊值。雜湊頁表的每一條目都包括一個連結串列的元素,這些元素雜湊成同一位置(要處理器碰撞)。每個元素有3個域:

  • (1)虛擬頁碼
  • (2)所對映的幀號
  • (3)指向連結串列中下一個元素的指標。

該演算法按照如下方式工作:虛擬地址中的虛擬頁號轉換為雜湊表號,用虛擬頁號與連結串列中的每一個元素的第一個域相比較。如果匹配,那麼相應的幀號(第二個域)就用來形成實體地址,如果不匹配,那麼就對連結串列中的下一個節點進行比較,以尋找一個匹配的頁號。

這裡寫圖片描述

人們提出了這種方法的一個變種,比較適合64位的地址空間,群集頁表(clustered page table),類似於雜湊頁表,不過這種雜湊表的每一條目不只包括一頁資訊,而且包括多頁。一個頁表條目可以儲存多個物理頁幀的對映。群集頁表對於稀疏地址空間特別有用,稀疏地址空間中的地址引用不連續,且分散在整個地址空間。

反向頁表(inversed page table)

通常,每個程序都有一個相關頁表。該程序所使用的每個頁都在頁表中有一項(或者每個虛擬地址都有一項,不管是否有效)。這種頁的表達方式比較自然,這是因為程序是通過頁的虛擬地址來引用頁的。作業系統必須將這種引用轉換成實體記憶體地址。由於頁表是按照虛擬地址排序的,作業系統能夠計算出所對應條目在頁表中的位置,並可以直接使用該值。這種方法的缺點之一是每個頁表可能有很多項,這些表可能消耗大量的實體記憶體,卻僅用來跟蹤實體記憶體是如何使用的。

為了解決這個問題,引入反向頁表(inversed page table)。反向頁表對於每個真正的記憶體幀或頁才有一個條目。每個條目包含儲存在真正記憶體位置的頁的虛擬地址以及擁有該頁的程序的資訊。因此,整個系統只有一個表,對每個實體記憶體的頁只有一條相應的條目。

由於系統只有一個頁表,而有很多地址空間對映實體記憶體,所以反向頁表的條目中通常需要一個地址空間識別符號,以確保一個特定程序的一個邏輯頁可以對映到相應的物理幀。

如圖,process-id作為地址空間的識別符號。當需要記憶體引用時,由process-id和page-number組成的虛擬地址部分送交記憶體子系統,通過查詢反向頁表來尋找匹配。如果匹配找到,例如條目i,那麼就產生了實體地址i+offset。如果沒有匹配,那就是試圖訪問非法地址。

這裡寫圖片描述

雖然這種方案減少了儲存每個頁表所需要的記憶體時間,但是當引用頁時,它增加了查詢頁表所需要的時間。(由於反向頁表按照實體地址排序,而查詢的是虛擬地址,因此可能需要查詢整個表來尋求匹配。這種查詢會花費大量時間)

故可以通過使用雜湊頁表來將查詢限制在一個或少數幾個頁表條目。但這樣,每次訪問雜湊頁表為整個過程增加了一次記憶體引用,因此一次虛擬地址引用至少需要兩次記憶體讀:一個查詢雜湊頁表條目,另一個查詢頁表。為改善效能,可以在訪問雜湊頁表時,先查詢TLB。

採用反向頁表的系統在實現共享記憶體時存在困難。共享記憶體通常作為被對映到一個實體地址的多虛擬地址(其中每一個程序共享記憶體)來實現。這種標準方法不能用到反向頁表,因為此時每個物理頁只有一個虛擬頁條目,一個物理頁不可能有兩個(或更多)的共享虛擬地址。

解決這一問題一個簡單方法是允許頁表僅包含一個虛擬地址到共享實體地址的對映,這意味著對未被對映的虛擬地址的引用將導致頁錯誤。

分段(segmentation)

採用分頁記憶體管理有一個不可避免的問題,就是使用者視角的記憶體和實際實體記憶體的分離。

基本方法

使用者通常願意將記憶體看作是一組不同長度的段的集合,這些段之間並沒有一定的順序。如物件、陣列、堆疊、變數等。

分段(segmentation)就是支援這種使用者視角記憶體管理方法。邏輯地址空間由一組段組成的。每個段都有名稱和長度。地址指定了段名稱和段內偏移。因此使用者通過兩個量來指定地址:段名稱和偏移。

注意這一方案與分頁的對比。在分頁中,使用者只指定一個地址,該地址通過硬體分為頁碼和偏移。

為實現簡單起見,段是編號的,是通過段號而不是段名來引用的。因此,邏輯地址由有序對組成:(segment-number, offset)

通常,在編譯使用者程式時,編譯器會自動根據輸入程式來構造段。

一個C編譯器可能會建立如下段:

①程式碼

②全域性變數,

③堆(記憶體從堆上分配)

④每個執行緒採用的棧

⑤標準的C庫函式

在編譯時連結的庫可能分配為不同的段。載入程式時會裝入所有這些段,併為他們分配段號。

硬體

使用者雖然現在能夠通過二維地址來引用程式中的物件,但是實際實體地址記憶體仍然是一維序列位元組。因此,必須定義一個實現方式,以便將二維的使用者定義地址對映為一維實體地址。這個地址是通過段表(segment table)來實現的。段表的每個條目都有段基地址和段界限。段基地址包含該段在記憶體中的開始實體地址,而段界限指定該段的長度。

一個邏輯地址由兩部分組成:段號s和段內的偏移d。段號用來做段表的索引,邏輯地址的偏移d用位於0和段界限之間。

這裡寫圖片描述