操作系統概念-----虛擬內存管理

背景

第八章所介紹的內存管理算法都是基於一個基本要求:執行指令必須在物理內存中,滿足這一要求的第一種方法是整個進程放在內存中。動態載入能幫助減輕這一限制,但是它需要程序員特別小心地做一些額外的工作。

指令必須都在物理內存內的這一限制,似乎是必須和合理的,但也是不幸的,因爲這使得程序的大小被限制在物理內存的大小內。事實上,研究實際程序會發現,許多情況下並不需要將整個程序放到內存中。即使在需要完整程序的時候,也並不是同時需要所有的程序。

因此運行一個部分在內存中的程序不僅有利於系統,還有利於用戶。

虛擬內存(virtual memory)將用戶邏輯內存和物理內存分開。這在現有物理內存有限的情況下,爲程序員提供了巨大的虛擬內存。 


虛擬地址空間 

進程的虛擬地址空間就是進程如何在內存中存放的邏輯(或虛擬)視圖。通常,該視圖爲進程從某一個邏輯地址(如地址0)開始,連續存放。 

虛擬地址空間 

根據第八章,物理地址可以按頁幁來組織,且分配給進程的物理頁幀也可能不是連續的。這就需要內存管理單元(MMU)將邏輯頁映射到內存的物理頁幀。 
如上圖顯示,運行隨着動態內存的分配,堆可向上生長。類似地,還允許隨着子程序的不斷調用,棧可以向下生長。堆與棧之間的巨大空白空間(或hole)爲虛擬地址的一部分,只有在堆與棧生長的時候,才需要實際的物理頁。包括空白的虛擬地址空間成爲稀地址空間,採用稀地址空間的優點是:隨着程序的執行,棧或者堆段的生長或需要載入動態鏈接庫(或共享對象)時,這些空白可以填充。

除了將邏輯內存與物理內存分開,虛擬內存也允許文件和內存通過共享頁而爲兩個或者多個進程所共享,這樣帶來了如下的有點:

  • 通過將共享對象映射到虛擬地址空間,系統庫可爲多個進程所共享。雖然每個進程都認爲共享庫是其虛擬地址空間的一部分,而共享庫所用的物理內存的實際頁是爲所有進程所共享。通常,庫是按制度方式來鏈接每個進程的空間的。
  • 類似的,虛擬內存允許進程共享內存。兩個或者多個進程之間可以通過使用共享內存來相互通信。虛擬內存允許一個進程創建內存區域,以便與其他進程進行共享。共享該內存區域的進程認爲它是其虛擬地址空間的一部分,而事實上這部分是共享的。


虛擬地址空間 

  • 虛擬內存可允許在用系統調用fork()創建進程期間共享頁,從而加快進程的創建。

按需調頁

一個執行程序從磁盤載入內存的時候有兩種方法。 
1. 選擇在程序執行時,將整個程序載入到內存中。不過這種方法的問題是可能開始並不需要整個程序在內存中。如有的程序開始時帶有一組用戶可選的選項。載入整個程序,也就將所有選項的執行代碼都載入到內存中,而不管這些選項是否使用。 
2. 另一種選擇是在需要時才調入相應的頁。這種技術稱爲按需調頁(demand paging),常爲虛擬內存系統所採用。

按需調頁系統看類似於使用交換的分頁系統,進程駐留在第二級存儲器上(通常爲磁盤)。當需要執行進程時,將它換入內存。不過,不是講整個進程換入內存,而是使用懶惰交換(lazy swapper)。懶惰交換隻有在需要頁時,纔將它調入內存。由於將進程看做是一系列的頁,而不是一個大的連續空間,因此使用交換從技術上來講並不正確。交換程序(swapper)對整個進程進行操作,而調頁程序(pager)只是對進程的單個頁進行操作。因此, 在討論有關按需調頁時,需要使用調頁程序而不是交換程序。 


虛擬地址空間 

基本概念

當換入進程時,調頁程序推測在該進程再次換出之前使用到的哪些頁,僅僅把需要的頁調入內存。從而減少交換時間和所需的物理內存空間。

這種方案需要硬件支持區分哪些頁在內存,哪些在磁盤。採用有效/無效位來表示。當頁表中,一個條目的該位爲有效時,表示該頁合法且在內存中;反之,可能非法,也可能合法但不在內存中。 

這裏寫圖片描述

如果進程從不試圖訪問標記爲無效的頁,那麼並沒有什麼影響,因此,如果推測正確且只調入所有真正需要的頁,那麼進程就可如同所有頁都調入內存一樣正常運行。

當進程試圖訪問這些尚未調入內存的頁時,會引起頁錯誤陷阱(page-fault trap)。這種情況的處理方式如下:

  • 1)檢查進程的內部頁表(通常與PCB一起保存)。以確定該引用是的合法還是非法的地址訪問。
  • 2)如果非法,則終止進程;如果引用有效但是尚未調入頁面,則現在進行調入。
  • 3)找到一個空閒幀(如,從空閒幀表中選取一個)。
  • 4)調度一個磁盤操作,以便將所需頁調入剛分配的幀
  • 5)磁盤讀操作完成後,修改進程的內部表和頁表,表示該頁已在內存中。
  • 6)重新開始因陷阱而中斷的指令。

這裏寫圖片描述

例如,block move 問題。 

這裏寫圖片描述

MVC指令能夠移動256B。

  • 塊可能跨越頁邊界
  • 移動部分字符後出現頁錯誤
  • 如果源和目的塊有重疊
  • 源塊可能已經被修改

解決方案: 
- 試圖存取兩個塊的兩端 
- 使用臨時寄存器保存被覆蓋位置的值

按需調頁的性能

按需調頁對計算機系統的性能有重要影響,下面計算一下關於按需調頁內存的有效訪問時間(effective access time)。

p(0p1)爲頁錯誤的概率,ma爲內存訪問時間: 

=(1p)×ma+p×

如果p0,則不存在頁錯誤;

如果p1,則每次訪問都存在頁錯誤,即純粹按需調頁(pure demand paging)

其中頁錯誤時間有很多,主要是下面三種:

  • 處理頁錯誤中斷
  • 讀入頁(頁換入時間)
  • 重新啓動進程

按需調頁的例子

內存存取時間 =200 ns 
平均頁錯誤服務時間 =8 ms

EAT=(1p)×200 ns+p(8 ms)200+p×7999800

如果每次1000次訪問中有1次頁錯誤,則EAT=8.2ns。即,因按需調頁而慢40倍,如果需要性能降低不超過10%,則需要

p<0.0000025

因此來看,對於按需調頁,降低頁錯誤率至關重要。

另外是對交換空間的處理的使用。磁盤IO到交換空間通常比到文件系統要快,因爲交換空間是按大塊進行分配,並不使用文件查找和間接分配方法。因此,在進程開始時將整個文件鏡像複製到交換空間,並從空間交換執行按頁調度,那麼有可能獲得更好的性能。

另一種選擇是開始時從文件系統進行按需調頁,但置換出來的頁寫入交換空間,而後的調頁則從交換空間中讀取。這種方法確保只有需要的頁才從文件系統中調入,又可以保證一定的性能。

寫時複製

寫時複製Copy-on-Write (COW) 運行父進程與子進程開始時共享同一頁面,這些頁面標記爲寫時複製頁,即如果任何一個進程需要對頁進行寫操作,那就創建一個共享頁的副本。

因爲指標及那些能夠被修改的頁,所以創建進程的過程更有效率。

寫時複製所需的空閒也老子一個空閒緩衝池,系統通常用按需填零(zero-fill-on-demand)的技術分配這些頁。按需填零在需要分配之前先填零,因此清除了以前的內容。

下面的兩個過程提箱了進程1修改C前後的物理內存的情況。 

before這裏寫圖片描述 
after 
這裏寫圖片描述 

如果沒有空閒幀時該如何處理呢?

  • 頁替換:在內存中找到一些不再使用的頁,將它換出去。
  • 一些頁可能被多次加載如內存。

頁面置換

操作系統爲何要進行頁面置換呢?這是由於操作系統給用戶態的應用程序提供了一個虛擬的“大容量”內存空間,而實際的物理內存空間又沒有那麼大。所以操作系統就就“瞞着”應用程序,只把應用程序中“常用”的數據和代碼放在物理內存中,而不常用的數據和代碼放在了硬盤這樣的存儲介質上。如果應用程序訪問的是“常用”的數據和代碼,那麼操作系統已經放置在內存中了,不會出現什麼問題。但當應用程序訪問它認爲應該在內存中的的數據或代碼時,如果這些數據或代碼不在內存中,則根據上文的介紹,會產生缺頁異常。這時,操作系統必須能夠應對這種缺頁異常,即儘快把應用程序當前需要的數據或代碼放到內存中來,然後重新執行應用程序產生異常的訪存指令。如果在把硬盤中對應的數據或代碼調入內存前,操作系統發現物理內存已經沒有空閒空間了,這時操作系統必須把它認爲“不常用”的頁換出到磁盤上去,以騰出內存空閒空間給應用程序所需的數據或代碼。

操作系統遲早會碰到沒有內存空閒空間而必須要置換出內存中某個“不常用”的頁的情況。如何判斷內存中哪些是“常用”的頁,哪些是“不常用”的頁,把“常用”的頁保持在內存中,在物理內存空閒空間不夠的情況下,把“不常用”的頁置換到硬盤上就是頁面置換算法着重考慮的問題。容易理解,一個好的頁面置換算法會導致缺頁異常次數少,也就意味着訪問硬盤的次數也少,從而使得應用程序執行的效率就高。

下面提供了一種需要頁置換的情況。

這裏寫圖片描述

基本頁置換

基本頁置換採用方法如下。

  • 查找需要頁在磁盤上的位置。
  • 查找一空閒幀: 
    • 如果有空閒幀,那麼就使用它
    • 如果沒有空閒幀,那麼就是用頁置換算法選擇一個“犧牲”幀(victim frame)
    • 將犧牲幀的內容放到磁盤上,改變頁表和幀表。
  • 將所需頁讀入(新)空閒幀,改變頁表和幀表。
  • 重啓用戶進程。

這裏寫圖片描述

頁置換是按需調頁的基礎。爲鎖骨下班按需調頁,必須解決兩個主要問題:必須開發幀分配算法(frame-allocation algorithm)頁置換算法(page-replacement algorithm)。如果在內存中有多個進程,那麼必須決定爲每個進程各分配多少幀。而且,當需要頁置換時,必須選擇要置換的幀。

可以這樣來評估一個算法:針對特定內存引用序列,運行某個置換算法,並計算出頁錯誤的數量。內存的引用序列成爲引用串(reference string)

第一,對給定頁大小(頁大小通常由硬件或系統來決定),只需要考慮頁碼,而不需要完整的地址。第二,如果有一個頁p的引用,那麼任何緊跟着對頁p的引用絕不會產生頁錯誤。頁p在第一次引用時已在內存中,任何緊跟着的引用絕不會出錯。

頁錯誤和幀數量圖 
這裏寫圖片描述

FIFO頁置換

該算法總是淘汰最先進入內存的頁,即選擇在內存中駐留時間最久的頁予以淘汰。只需把一個應用程序在執行過程中已調入內存的頁按先後次序鏈接成一個隊列,隊列頭指向內存中駐留時間最久的頁,隊列尾指向最近被調入內存的頁。這樣需要淘汰頁時,從隊列頭很容易查找到需要淘汰的頁。FIFO算法只是在應用程序按線性順序訪問地址空間時效果纔好,否則效率不高。因爲那些常被訪問的頁,往往在內存中也停留得最久,結果它們因變“老”而不得不被置換出去。FIFO算法的另一個缺點是,它有一種異常現象(Belady現象),即在增加放置頁的頁幀的情況下,反而使缺頁異常次數增多。

問題:隨機一訪問串和駐留集的大小,通過模擬程序顯示淘汰的頁號並統計命中率。示例:

輸入訪問串:7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1

駐留集大小:3

這裏寫圖片描述

紅色表示:指針指向調入內存的頁面中“最老“的頁面

通過模擬程序輸出淘汰的頁號分別爲:7 0 1 2 3 0 4 2 3

命中率爲:513

注意:內存的頁面中“最老“的頁面,會被新的網頁直接覆蓋,而不是“最老“的頁面先出隊,然後新的網頁從隊尾入隊。

最優(Optimal)置換

由Belady於1966年提出的一種理論上的算法。其所選擇的被淘汰頁面,將是以後永不使用的或許是在最長的未來時間內不再被訪問的頁面。採用最佳置換算法,通常可保證獲得最低的缺頁率。但由於操作系統其實無法預知一個應用程序在執行過程中訪問到的若干頁中,哪一個頁是未來最長時間內不再被訪問的,因而該算法是無法實際實現,但可以此算法作爲上限來評價其它的頁面置換算法。

LRU(Least Recently Used)頁置換

FIFO置換算法性能之所以較差,是因爲它所依據的條件是各個頁調入內存的時間,而頁調入的先後順序並不能反映頁是否“常用”的使用情況。

最近最久未使用(LRU)置換算法,是根據頁調入內存後的使用情況進行決策頁是否“常用”。由於無法預測各頁面將來的使用情況,只能利用“最近的過去”作爲“最近的將來”的近似,因此,LRU置換算法是選擇最近最久未使用的頁予以淘汰。該算法賦予每個頁一個訪問字段,用來記錄一個頁面自上次被訪問以來所經歷的時間t,,當須淘汰一個頁面時,選擇現有頁面中其t值最大的,即最近最久未使用的頁面予以淘汰。

問題:隨機一訪問串和駐留集的大小,通過模擬程序顯示淘汰的頁號並統計命中率。示例:

輸入訪問串:7 0 1 2 0 3 0 4 2 3 0 3 2

駐留集大小:3

算法的實現:由於LRU算法淘汰的是上次使用距離t時刻最遠的頁,故需記錄這個距離。

有兩張方法:

  1. 計數器:爲每個頁表項關聯一個使用時間域,併爲CPU增加一個邏輯時鐘或計數器。對每次內存引用,計數器都會增加。每次內存引用時,時鐘寄存器的內容會複製到相應頁所對應頁表項的使用時間域內。置換具有最小時間的頁,這種方案需要搜索頁表以查找LRU頁,且每次內存訪問都要寫入內存。在頁表改變時也必須要保證時間,必須考慮時鐘溢出。

  2. 堆棧:實現LRU置換的另一個方法是採用頁碼堆棧。每當引用一個頁,該頁就從堆棧中刪除並放在頂部,這樣,堆棧底部總是LRU頁,該堆棧可實現爲具有頭指針和尾指針的雙向鏈表

每次內存引用都必須更新時鐘域或堆棧,如果每次引用都採用中斷,以允許軟件更新這些數據結構,那麼它會使內存引用慢至少10倍

這裏寫圖片描述

紅色表示:每個頁幀對應的計數器值

通過模擬程序輸出淘汰的頁號分別爲:7 1 2 3 0 4

命中率爲:413

LRU的另一種通俗理解:

例如一個三道程序,等待進入的是1,2,3,4,4,2,5,6,3,4,2,1。先分別把1,2,3導入,然後導入4,置換的是1,因爲他離導入時間最遠。然後又是4,不需要置換,然後是2,也不需要,因爲內存中有,到5的時候,因爲3最遠,所以置換3,依次類推。

注意:雖然兩個算法都是用隊列這種數據結構實現的,但具體操作不完全遵從隊列的原則。這一點不必糾結。

命中率是指在隊滿的情況下,新的元素的加入,不影響隊列其它元素。即該元素已存在在隊列中。

OPT、LRU以及FIFO算法的對比圖如下所示: 

這裏寫圖片描述

近似LRU頁置換

很少有計算機系統能夠提供足夠的硬件來支持真正的LRU頁置換。有的系統不提供任何支持,因此必須使用其他置換算法。然而,許多系統都通過應用爲方式提供一定支持,頁表內的每項都關聯着一個引用位(reference bit),每當引用一個頁時,相應頁表的引用位就會被引腳置位。如添加一個8bit的引用位(極端情況下只有一個引用位,即二次機會算法)。每個時鐘都向右移位,引用的話高位置1,否則置0。

開始,操作系統會將所有引用位都清零。隨着用戶進程的執行,與引用頁相關聯的引用位被硬件置位。通過檢查引用位,能確定那些用過而那些沒用過。這種部分排序信息導致了許多近似LRU算法的頁置換算法。

  • 附加引用位算法: 
    • 可以爲位於內存中的每個表中的頁保留一個8bit的字節。操作系統把每個頁的引用位轉移到其8bit字節的高位,而將其他位右移,並拋棄最低位。如果將8bit字節作爲無符號整數,那麼具有最小值的頁爲LRU頁,且可以被置換。
  • 二次機會算法: 
    • 二次機會置換的基本算法是FIFO置換算法。當要選擇一個頁時,檢查其引用位。如果其值爲0,那麼就直接置換該頁。如果引用位爲1,那麼就給該頁第二次機會,並選擇下一個FIFO頁。當一個頁獲得第二次機會時,其引用位清零。且其到達時間設爲當前時間。因此獲得第二次機會的頁,在所有其他頁置換之前,是不會被置換的。另外,如果一個頁經常使用以致於其引用位總是得到設置,那麼它就不會被置換。
    • 一種實現二次機會算法的方法是採用循環隊列。用一個指針表示下次要置換哪個頁。當需要一個幀時,指針向前移動直到找到一個引用位爲0的頁。在向前移動時,它將清除引用位。
  • 增強型二次機會算法 
    • 通過將引用位和修改位作爲一個有序對來考慮,能增強二次機會算法。有下面四種可能類型: 
      • 1 (0,0)最近沒有使用且也沒有修改。—用於置換的最佳頁
      • 2 (0,1)最近沒有使用但修改過。—不是很好,因爲在置換之前需要將頁寫出到磁盤
      • 3 (1,0)最近使用過但沒有修改—它有可能很快又要被使用
      • 4 (1,1)最近使用過且修改過—它有可能很快又要被使用,且置換之前需要將頁寫出到磁盤

當頁需要置換時,每個頁都屬於這四種類型之一。置換在最低非空類型中所碰到的頁,可能要多次搜索整個循環隊列。

基於計數的頁置換

有如下兩種算法: 
1. 最不經常使用頁置換算法(LFU) 
2. 最常使用頁置換算法(MFU)

這兩種算法的實現都很費時,且不能很好地近似OPT置換算法。

頁緩衝算法

系統通常保留一個空閒幀緩衝池。當出現頁錯誤時,會像以前一樣選擇一個犧牲幀,在犧牲幀寫出之前,所需要的頁就從緩衝池中讀到空閒內存。

幀分配

如何在各個進程之間分配一定的空閒內存? 
簡單辦法是將幀掛在空閒幀鏈表上,當發生頁錯誤之時即進行分配。進程終止時幀再次放回空閒幀鏈表。 
幀分配策略受到多方面限制。例如, 分配數不能超過可用幀數,也必須分配至少最少數量。保證最少量的原因之一是性能。頁錯誤增加會減慢進程的執行。並且,在指令完成前出現頁錯誤,該指令必須重新執行。所以有足夠的幀至關重要。 
每個進程幀的最少數量由體系結構決定,而最大數量是由可用物理內存數量決定。

分配算法

1)平均分配,每個進程一樣多 
2)按進程大小使用比例分 
3)按進程優先級分 
4)大小和優先級組合分

全局分配和局部分配

全局置換允許進程從所有幀集合中選擇一個進行置換,而不管該幀是否已分配給其他進程,即它可以從其他進程搶奪幀,比如高優先級搶奪低優先級的幀;局部分配則要求每個進程只能從自己的分配幀中分配。 
全局置換通常有更好的吞吐量,且更爲常用。

1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章