操作系統概念學習筆記 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和段界限之間。

這裏寫圖片描述

發佈了87 篇原創文章 · 獲贊 204 · 訪問量 76萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章