虛擬內存


虛擬內存

計算機系統使用的各種內存管理策略。所有這些策略都為同一目的:同時將多個進程存放在內存中,以便多道程序設計。不過,這些策略都需要在進程執行之前將整個進程放在內存中。

    虛擬內存技術允許執行進程不必完全在內存中。這種方案的一個顯著優點是程序可以比物理內存大。而且,虛擬內存將內存抽象成一個巨大,統一的存儲數組,進而將用戶看到的邏輯內存與物理內存分開。這種技術允許程序員不受內存的限制。虛擬內存也允許進程很容易地共享文件和地址空間。還為創建進程提供了有效的機制。但是,虛擬內存的實現並不容易,如果使用不當可能會大大的降低性能。

背景

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

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

    能夠執行只有部分在內存中的程序可帶來很多好處:

    程序不再受現有的物理內存空間限制。用戶可以為一個巨大的虛擬地址空間(virtual address space)寫程序,簡化工作量。

    因為每個用戶程序使用了更少的物理內存,所以更多的程序可以同時執行,CPU使用率也相應增加,而響應時間或周轉時間並不增加。

    由於載入或交換每個用戶程序到內存內所需的I/O會更少,用戶程序會運行得更快。

    虛擬內存(virtual memory)將用戶邏輯內存與物理內存分開。

進程的虛擬地址空間就是進程如何在內存中存放的邏輯(或虛擬)視圖。通常,該視圖為進程從某一邏輯地址(如地址0)開始,連續存放。物理地址可以按頁幀來組織,且分配給進程的物理頁幀也可能不是連續的。這就需要內存管理單元(MMU)將邏輯頁映射到物理頁幀。

    如下圖所示,允許隨着動態內存分配,堆可向上生長。類似地,還允許隨着子程序的不斷調用,棧可以向下生長。堆與棧之間的巨大空白空間(或洞)為虛擬地址的一部分,只有在堆與棧生長時,才需要實際的物理頁。包括空白的虛擬地址空間稱為稀地址空間。采用稀地址空間的優點是:隨着程序的執行,堆或棧的生長或需要載入動態鏈接庫或(共享對象)時,這些空白可以填充。

    除了將邏輯內存與物理內存分開,虛擬內存也允許文件和內存通過共享頁而為兩個或多個進程所共享。

通過將共享對象映射到虛擬地址空間,系統庫可為多個進程所共享。雖然每個進程都認為共享庫是其虛擬地址空間的一部分,而共享所用的物理內存的實際頁是為所有進程所共享。通常庫是按只讀方式來鏈接每個進程的空間。

    類似的,虛擬內存允許進程共享內存。兩個或多個進程之間可以通過使用共享內存來通信。虛擬內存允許一個進程創建內存區域,以便與其他進程共享。共享內存區域的進程認為它是其虛擬地址空間的一部分,而事實上這部分是共享的。

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

按需調頁

    看看一個執行程序是如何從磁盤載入內存的。一種選擇是在程序執行時,將整個程序載入到內存。不過,這種方法的問題是可能開始並不需要整個程序在內存。如有的程序開始時帶有一組用戶的可選的選項。載入整個程序,也就將所有選項的執行代碼都載入到內存中,而不管這些選項是否使用。另一種選擇是在需要時才調入相應的頁。這種技術稱為按需調頁(demand paging), 常為虛擬內存系統所采用。對於按需調頁虛擬內存,只有程序執行需要時才載入頁,那些從未訪問的頁不會調入到物理內存。

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

基本概念

    當換入進程時,調頁程序推測在該進程再次換出之前會用到哪些頁。調頁程序不是調入整個進程,而是把那些必須的頁調入內存。這樣,調頁程序就避免了讀入那些不使用的頁,也減少了交換時間和所需的物理內存空間。

    對於這種方案,需要一定形式的硬件支持來區分那些頁在內存中,哪些頁在磁盤上。可以通過有效-無效位來設置。當該位設置為"有效"時,該值表示相關的頁即合法也在內存中。當該位設置為"無效"時,該值表示相關的頁為無效(也就是,不在進程的邏輯地址空間內),或者有效但是在磁盤上。對於調入內存的頁,其也表條目的設置與平常一樣;但是對於不在內存的頁,其頁表條目設置為無效,或包含該頁在磁盤上的地址。

    如果進程從不試圖訪問標記為無效的頁,那么並沒有什么影響。因此,如果推測正確並且只調入所有真正需要的頁,那么進程就如同所有頁都已調入一樣正常運行。當進程執行和訪問那些駐留在內存中的頁時,執行會正常進行。

    

 

    但是當進程試圖訪問那些尚未調入到內存的頁時,對標記為無效的訪問會產生頁錯誤陷阱(page-fault trap)。分頁硬件,在通過頁表轉換地址時,將發現已設置了無效位,會陷入操作系統。這種陷阱時由於操作系統未能將所需的頁調入內存引起的。

    處理這種頁錯誤的程序比較簡單:

  1. 檢查進程的 內部頁表(通常與PCB一起保存),以確定該引用時合法還是非法的地址訪問。
  2. 如果引用非法,那么終止進程。如果引用有效但是尚未調入頁面,那么現在應調入。
  3. 找到一個空閑幀(例如,從空閑幀鏈表中選取一個)。
  4. 調度一個磁盤操作,以便將所需要的頁調入剛分配的幀。
  5. 當磁盤讀操作完成后,修改進程的內部表和頁表,以表示該頁已在內存中。
  6. 重新開始因陷阱而中斷的指令。進程現在能訪問所需的頁,就好像它似乎總在內存中。

存粹按需調頁:所有的頁都不在內存中,就開始執行。

支持按需調頁的硬件與分頁和交換的硬件一樣:

頁表:該表能夠通過有效-無效位或保護位的特定值,將條目設為無效。

次級存儲器:該次級存儲器用來保存不在內存中的頁。次級存儲其通常位快速磁盤。它通常稱為交換設備,用於交換的這部分磁盤稱為交換空間(swap space)。

請求調頁的關鍵要求是能夠在頁錯誤后重新執行指令。在出現頁錯誤時,保存中斷進程的狀態(寄存器,條件代碼,指令計數器),必須能夠按完全相同的位置和地址重新開始執行進,只不過現在所需的頁已在內存中且可以訪問。對絕大多數情況來說,這種要求容易滿足。也錯誤可能出現在任何內存引用中。如果也錯誤出現在指令獲取時,那么可以再次獲取指令。如果頁錯誤出現獲取操作數時,那么可以再次獲取指令、再次譯碼指令,然后再次獲取操作數。

    按需調頁的性能

按需調頁對計算機系統的性能有重要影響。為了說明起見,下面計算一下關於按需調頁內存的有效訪問時間(effective access time)。只要沒有出現頁錯誤,那么有效訪問時間等於內存訪問時間(用ma表示)。然而,如果出現頁錯誤,那么就必須從磁盤中讀入相關頁,再訪問所需要的字。

設p為頁錯誤的概率(0<p<1)。如果p接近與0,即頁錯誤很少。那么有效訪問時間為:

有效訪問時間=(1-p)*ma+p*頁錯誤時間

為了計算有效時間,必須知道處理頁錯誤需要多少時間。頁錯誤會引起如下序列的動作產生:

  1. 陷入操作系統。
  2. 保存用戶寄存器和進程狀態。
  3. 確定中斷是否為頁錯誤。
  4. 檢查頁引用是否合法並確定頁所在磁盤的位置。
  5. 從磁盤讀入頁到空閑幀中。
  6. 在該磁盤隊列中等待,直到處理完請求。
  7. 等待磁盤的尋道和/或延遲時間。
  8. 開始將磁盤的頁傳到空閑幀。
  9. 在等待時,將CPU分配給其他用戶(CPU調度,可選)。
  10. 從I/O子系統接收到中斷(以示I/O完成)。
  11. 保存其他用戶的寄存器和進程狀態。
  12. 確定中斷是否來自磁盤。
  13. 修正頁表和其他表以表示所需頁現已在內存中。
  14. 等待CPU再次分配給本進程。
  15. 恢復用戶寄存器、進程狀態和新頁表,再重新執行中斷的指令。

主要經歷了兩次上下文切換,以及磁盤的讀取等待。

寫時復制

    通過采用類似頁面共享的技術,采用系統調用fork創建進程的開始階段可能需要按需調頁。這種技術提供了快速進程創建,且最小化新創建進程必須分配的新頁面的數量。

    系統調用fork()是將子進程創建為父進程的復制品。傳統上,fork()為子進程創建一個父進程地址空間的副本,復制屬於父進程的頁。然而,由於許多子進程在創建之后通常馬上會執行系統調用exec(),所以父進程地址空間的復制可能沒有必要。因此,可以使用一種稱為寫時復制(copy-on -write)的技術。這種技術允許父進程與子進程開始時共享同一頁面。這些頁面標記為寫時復制頁,即如果任何一個進程需要對頁進行寫操作,那么就創建一個共享頁的副本。

    例如,假設子進程試圖修改含有部分棧的頁,且操作系統能識別該頁被設置為寫時復制頁,那么操作系統就會創建一個該頁的副本,並將它映射到子進程的地址空間內。這樣,子進程會修改其復制的頁,而不是父進程的頁。采用寫時復制技術,很顯然只有能被進程修改的頁才會被復制;所有非修改頁可為父進程和子進程所共享。

    當確定一個頁采用寫時復制時,從哪些分配空閑頁是很重要的。許多操作系統為這類請求提供空閑緩沖池(pool)。這些空閑頁在進程棧或堆必須擴展時可用於分配。或用於管理寫時復制頁。操作系統通常采用按需填零(zero-fill-on-demand)的技術以分配這些頁。按需填零頁在需要分配之前先填零,因此清除了以前的內容。

頁面置換

基本頁置換

    頁置換采用如下方法。如果沒有空閑幀,那么就查找當前沒有使用的幀,並將其釋放。可采用這樣的方式來釋放一個幀:將其內容寫到交換空間,並改變頁表(和所有其他表)以表示該頁不在內存中。現在可使用空閑幀來保存進程出錯的頁。修改頁錯誤處理程序以包括頁置換:

  1. 查找所需要頁在磁盤上的位置。
  2. 查找一個空閑幀
  3. 如果有空閑幀,那么就使用它。
  4. 如果沒有空閑幀,那么就是用頁置換算法以選擇一個 "犧牲"幀(victim frame)。
  5. 將"犧牲"幀的內容寫到磁盤上,改變頁表和幀表。
    1. 將所需頁讀入(新)空閑幀,改變頁表和幀表。
    2. 重啟用戶進程。

注意,如果沒有空閑幀,那么需要采用兩個頁傳輸(一個換出,一個換入)。這種情況實際上把也錯誤處理時間加倍了,且也相應地增加了有效訪問時間。

    可以通過使用修改位(modify bit)臟位(dirty bit)以降低額外開銷。每頁或幀可以有一個修改位,通過硬件與之相關聯。每當頁內的任何字或字節被寫入時,硬件就會設置該頁的修改位以表示頁已修改。如果修改位已設置,那么就可以知道自從磁盤讀入后該頁已發生了修改。在這種情況下,如果該頁被選擇位替換頁,就必須要把該頁寫到磁盤上去。然而,如果修改位沒有設置,那么也就知道自從磁盤讀入都該頁並沒有發生修改。因此,磁盤上頁的副本的內容沒有必要重寫,因此就避免了將內存頁寫回磁盤上:它已經在那里了。

    為了實現按需調頁,必須解決兩個主要問題:必須開發幀分配算法(frame-allocation algorithm)頁置換算法(page-replacement algorithm)。

    可以采用這樣來評估一個算法:針對特定內存引用序列,運行某個置換算法,並計算出頁錯誤的數量。內存的引用序列稱為引用串(reference string)。為了降低數量量,可利用以下兩個事實。

    第一:對給定頁大小(頁大小通常由硬件或系統來決定),只需要考慮頁碼,而不需要完整地址。

    第二:如果有一個對頁p的引用,那么任何緊跟着對頁p的引用決不會產生頁錯誤。頁p第一次引用時已在內存中,任何緊跟着的引用不會出錯。

針對某一特定引用串和頁置換算法,為了確定頁錯誤的數量,還需知道可用幀的數量。顯然,隨着可用幀數量的增加,頁錯誤的數量會相應地減少。

    為了討論頁置換算法,將采用如下引用串:

    7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1

    而可用幀的數量位3.

 

FIFO頁置換

    最簡單的頁置換算法時FIFO算法。FIFO頁置換算法為每個頁記錄着該頁調入內存的時間。當必須置換一頁時,將選擇最舊的頁。注意並不需要記錄調入一頁的確切時間。可以創建一個FIFO隊列來管理內存中的所有頁。隊列中的首頁將被置換。當需要調入頁時,將它加到隊列的尾部。

    FIFO頁置換算法性能並不總是很好。

    為了說明與FIFO頁置換算法相關可能問題,考慮如下引用串:

頁錯誤對現有幀數的曲線,這種最為令人難以置信的結果稱為Belady異常(Belady's anomaly):對有的頁置換算法,頁錯誤率可能會隨着所分配的幀數的增加而增加,而原期望為進程增加內存會改善其性能。在早期研究中,研究人員注意到這種推測並不總是正確的。因此,發現了Belady異常。

最優置換

    Belady異常發現的結果之一是對最優頁置換算法(optimal page-replacement algorithm)的搜索。最優置換算法是所有算法中產生錯誤率最低的,且絕沒有Belady異常的問題。這種算法確實存在,它被稱為OPT或MIN。它會置換將來最長時間不會使用的頁。使用這種頁置換算法確保對於給定數量的幀會產生最低可能的頁錯誤率。

      

 

 

最優置換算法難以實現,因為需要引用串的未來知識。因此,最優算法主要用於比較研究。如果知道一個算法不是最優的,但是與最優相比最壞不差於12.3%,平均不差於4.7%,那么也是很有用的。

LRU頁置換

    FIFO和OPT算法的關鍵區別在於,FIFO算法使用的是頁調入內存的時間,OPT算法使用的是頁將來使用的時間。如果使用離過去最近作為不遠將來的近似,那么可置換最長沒有使用的頁,這種方法稱為最近最少使用算法(least-recently-used(LRU) algorithm)

    LRU策略經常用做頁置換算法,且被認為相當不錯。其主要問題是如何實現LRU置換。LRU頁置換算法可能需要一定的硬件支持。它的問題是為頁幀確定一個排序序列,這個序列按頁幀上次使用的時間來定。有兩種可行實現。

    計數器:最為簡單的情況是,為每個頁表關聯一個使用時間域,並為CPU增加一個邏輯時鍾或計數器。對每次內存引用,計數器都會增加。每次內存引用時,始終寄存器的內容會被復制到相應頁所對應頁表項的使用時間域內。用這種方式就得到每頁的最近引用時間。置換具有最小時間的頁。這種方案需要搜索頁表以查找LRU頁,且每次內存訪問都要寫入內存(到頁表的使用時間域)。在頁表改變時(因CPU調度)也必須保持時間。

    棧:實現LRU置換的另一個方法時采用頁碼棧。每當引用一個頁,該頁就從棧中刪除並放在頂部。這樣,棧頂總是最近使用的頁,棧底部總是LRU頁。由於必須從棧中部刪除項,所以該棧可實現為具有頭指針和尾指針的雙向鏈表。這樣,刪除一頁 並放在棧頂部在最壞情況下需要改變6個指針。雖然說更新有點費時,但是置換不需要搜索;尾指針指向棧底部,就是LRU頁。

    最優置換和LRU置換都沒有Belady異常。這兩個都屬於同一類算法,稱為棧算法(stack algorithm),都絕不可能有Belady異常。

近似LRU頁置換

    頁表內的每項都關聯着一個引用位(reference bit)。每當引用一個頁時(無論是對頁的字節進行讀或寫),相應頁表的引用位就被硬件置位。

    開始,操作系統會將所有引用位清零。隨着用戶進程的執行,與引用頁相關聯的引用位被硬件置位(置為1)。之后,通過檢查引用位,能夠確定那些頁使用過而那些頁未使用過。雖然不知道使用順序,但是知道那些頁用過而那些頁未用過。這些信息是許多近似LRU頁置換算法的基礎。

    附加引用位算法:可以為位於內存內的每個表中的頁保留一個8位的字節。在規定時間間隔內,時鍾定時器產生中斷並將控制轉交給操作系統。操作系統把每個頁的引用位轉移到其8位字節的高位,而將其他位向右移一位,並拋棄最低位。這些8位移位寄存器包含着頁在最近8個時間周期內的使用情況。如果將這8位字節作為無符號整數,那么具有最小值的頁位LRU頁,且可以被置換。注意這些數字並不唯一。可以置換所有具有最小值的頁,或在這些頁之間采用FIFO來選擇置換。

    在極端情況下,只有引用位本身。這種算法稱為第二次機會頁置換算法(second-chance page-replacement algorithm)。

    二次機會算法

    二次機會置換的基本算法是FIFO置換算法。當要選擇一個頁時,檢查其引用位。如果其值為0,那么就直接置換該頁。如果引用位為1,那么就給該頁第二次機會,並選擇下一個FIFO頁。當一個頁獲得第二次機會時,其引用位清零,且其到達時間設為當前時間。獲得第二次機會的頁在所有其他頁置換(或獲得第二次機會)之前,是不會被置換的。另外,如果一個頁經常使用以致其引用位總是被設置,那么它就不會被置換。

    一種實現二次機會算法(有時稱為時鍾算法)的方法是采用循環隊列。用一個指針表示下次要置換哪一頁。當需要一個幀時,指針向前移動直到找到一個引用位位0的頁。在向前移動時,它將清除引用位。一旦找到犧牲頁,就置換該頁,新頁就插入到循環隊列的該位置。

注意:在最壞情況下,所有位均已設置,指針會遍歷整個循環隊列,以便給每個頁第二次機會。它將清除所有引用位后再選擇頁來置換。這樣,如果所有位均已設置,那么第二次機會置換就變成了FIFO置換。

    增強二次機會算法:通過將引用位修改位作為一有序對來考慮,可以改進二次機會算法。采用這兩個位,有下面四種可能類型:

(0,0)最近沒有使用且也沒有修改-----用於置換的最佳頁。

(0,1)最近沒有使用但修改過——不是很好,因為在置換之前需要將頁寫出到磁盤

(1,0)最近使用過但沒有修改——它有可能很快又要被使用

(1,1)最近使用過且修改過——它有可能很快又要被使用,且置換之前需要將頁寫出到磁盤。

每個頁都屬於這四種類型之一。當頁需置換時,可使用時鍾算法,不是檢查所指頁的引用位是否設置,而是檢查所指頁屬於哪個類型。置換在低非空類中所碰到的頁。注意在找到要置換頁之前,可能要多次搜索整個循環隊列。

    這種方法與簡單時鍾算法的主要差別時這里給那些已經修改過的頁以更高的級別,從而降低了所需I/O的數量。

基於計數的頁置換

    還有許多其他算法可用於頁置換。例如,可以為每個頁保留一個用於記錄其引用次數的計數器,並可形成如下兩個方案。

    最不經常使用頁置換算法(least frequently used(LFU) page-replacement algorithm)要求置換計數最小的頁。這種選擇的理由是活動頁應該有更大的引用次數。這種算法會產生如下問題:一個頁在進程開始時使用很多,但以后就不再使用。由於其使用過很多,所以它有較大次數,所以即使不再使用仍然會在內存中。解決方法之一時定期地將次數寄存器右移一位,以形成指數衰減的平均使用次數。

    最常使用頁置換算法(most frequently used(MFU) page-replacement algorithm)是基於如下理論:具有最小次數的頁可能剛剛調進來,且還沒有使用。

頁緩沖算法

    除了特定頁置換算法外,還經常采用其他措施。例如,系統通常保留一個空閑幀緩沖池。當出現頁錯誤時,會像以前一樣選擇一個犧牲幀。然而,在犧牲幀寫出之前,所需要的頁就從緩沖池中讀到空閑內存。這種方法允許進程盡可能快地啟動,而無須等待犧牲幀頁地寫出。當在犧牲幀以后寫出時,它再加入到空閑幀池。

    這種方法地擴展之一是維護一個已修改頁地列表。每當調頁設備空閑時,就選擇一個修改頁並寫到磁盤上,接着重新設置其修改位。這種方法增加了當需要選擇置換時干凈頁地概率而不必寫出。

    另一個修改是保留一個空閑幀池,但是要記住哪些頁再哪些幀中。由於當幀寫到磁盤上時其內容並沒有修改,所以再該幀被重用之前如果需要使用原來頁,那么原來頁可直接從空閑幀池中取出來使用。這時並不需要I/O。當一個頁錯誤發生時,先檢查所需要頁是否在空閑幀池中。如果不在,那么才必須選擇一個空閑幀來讀入所需頁。

幀分配

如何在各個進程之間分配一定地空閑內存?如果有93個空閑幀和2個進程,那么每個進程各得到多少幀?

幀的最少數量

    分配至少最少數量地幀地原因之一時性能。顯然,隨着分配給每個進程地幀數量地減少,頁錯誤會增加,從而減慢進程的執行。另外,記住:當在指令完成之前出現頁錯誤時,該指令必須重新執行。因此,必須有足夠的幀來容納所有單個指令所引用的頁。

分配算法

    在n個進程之間分配m個幀的最為容易的方法是給每個一個平均值,即m/n幀。例如,如果有93個幀和5個進程,那么每個進程可得到18個幀,剩余3個幀可以放在空閑幀緩存池中。這種方案稱為平均分配(equal allocation)。

    也可以使用比例分配(proportional allocation)。根據進程大小,而將可用內存分配給每個進程。設進程Pi的虛擬內存大小位Si,且定義

這樣,如果可用幀的總數為m,那么進程pi可分配到ai個幀,這里ai近似為:

全局分配與局部分配

    為各個進程分配幀的另一個重要因素是頁置換。當多有個進程競爭幀時,可將頁置換算法分為兩個大類:全局置換(global replacement)局部置換(local replacement)。全局置換允許一個進程從所有幀集合中選擇一個置換幀,而不管該幀是否已分配給其他進程,即一個進程可以從另一個進程中拿到幀。局部置換要求每個進程僅從其自己的分配幀中進行選擇。全局置換允許高優先級進程從低優先級進程中選擇幀以便置換。一個進程可以從自己的幀中或任何低優先級進程中選擇置換幀。這種方法允許高優先級進程增加其幀分配而以損失低優先級進程為代價。因為局部置換不能使用其他進程的不常用的內存,所以阻礙一個進程。因此,全局置換通常會有更好的系統吞吐量,且更為常用。

系統顛簸

    如果低優先級進程所分配的幀數量少於計算機體系結構所要求的最少數量,那么必須暫停進程執行。接着應換出其他所有剩余頁,以便使其所有分配的幀空閑。這引入了中程CPU調度的換進換出層。

    沒有"足夠"幀的進程。如果進程沒有它所需要的活躍使用的幀,那么它會很快產生頁錯誤。這時,必須置換某個頁。然而,其所有頁都在使用,它置換一個頁,但又立刻再次需要這個頁。因此,它會一而再地產生頁錯誤,置換一個頁,而該頁又立刻出錯且需要立即調進來。

    這種頻繁地頁調度行為稱為顛簸(thrashing)。如果一個進程在換頁上用的時間要多余執行時間,那么這個進程就在顛簸。

系統顛簸的原因

    在早期調頁系統中,操作系統在監視CPU的使用率,如果CPU使用率太低,那么向系統中引入新進程,以增加多道程序的程序。采用全局置換算法,它會置換頁而不管這些頁是屬於哪個進程的。現在假設一個進程進入一個新執行階段,需要更多的幀。它開始出現頁錯誤,並從其他進程中拿到幀。然而,這些進程也需要這些頁,所以它們也會出現頁錯誤,從而從其他進程中拿到幀。這些頁錯誤進程必須使用調頁設備以換進和換出頁。隨着它們排隊等待換頁設備,就緒隊列會變空,而進程等待調頁設備,CPU使用率就會降低。CPU調度程序發現CPU使用率降低,因此會增加多道程序的程序。新進程試圖從其他運行進程中拿到幀,從而引起更多頁錯誤,形成更長的調頁設備的隊列。

    通過局部置換算法(local replacement algorithm)(或優先置換算法(priority replacement algorithm))能限制系統顛簸。采用局部置換,如果一個進程開始顛簸,那么它不能從其他進程拿到幀,且不能使后者也顛簸。然而這個問題還沒有完全得到解決,如果進程顛簸,那么絕大多數時間內也會排隊來等待調頁設備。由於調頁設備的更長的平均隊列,也錯誤的平均等待時間也會增加。因此,即使對沒有顛簸的進程,其有效訪問時間也會增加。

    為了防止顛簸,必須提供進程所需的足夠多的幀。但是如何知道進程"需要"多少幀呢?有多種技術。工作集合策略是研究一個進程實際正在使用多少幀。這種方法定義了進程執行的局部模型(locality model)。

    局部模型說明,當進程執行時,它從一個局部移向另一個局部。局部是一個經常使用頁的集合,一個程序通常由多個不同局部組成,它們可能重疊。

    例如,當一個子程序被調用時,它就定義了一個新局部。在這個局部里,內存引用包括該子程序的指令、其全局變量和全局變量的子集。當該子程序退出時,因為子程序的局部變量和指令現已不再使用,進程離開該局部。也可能在后面再次返回該局部。

    可以看到局部是由程序結構和數據結構來定義的。局部模型說明了所有程序都具有這種基本的內存引用結構。局部模型是緩存的基礎。如果對任何數據結構的訪問是隨機的而沒有一定的模型,那么緩存就沒有用了。

 

 

工作集合模型

    工作集合模型(working-set model)是基於局部性假設的。該模型使用參數定義工作集合窗口(working-set window)。其思想是檢查最近個頁的引用。這最近個引用過的頁集合稱為工作集合(working set)。如果一個頁正在使用中,那么它就在工作集合內。如果它不再使用,那么它會在其上次引用的時間單位后從工作集合中刪除。因此,工作集合是程序局部的近似。

    最為重要的工作集合的屬性是其大小。如果經計算而得到系統內每個進程的工作集合為,那么就得到

    其中D為總的幀需求量。每個進程都經常要使用位於其工作集合內的頁。因此,進程i需要幀。如果總的需求大於可用幀的數量(D>m),那么有的進程就會得不到足夠得幀,從而出現顛簸。

    一旦確定了,那么工作集合模型得使用就較為簡單。操作系統跟蹤每個進程的工作集合,並為進程分配大於其工作集合的幀數。如果還有空閑幀,那么可啟動另一個進程。如果所有工作集合之和的增加超過了可用幀的總數,那么操作系統會選擇暫停一個進程。該進程的頁被寫出,且其幀可分配給其他進程。掛起的進程可以在以后重啟。

    這種工作集合策略防止了顛簸,並盡可能提高了多道程序的程序。因此,它優化了CPU使用率。工作集合模型的困難是跟蹤工作集合。工作集合窗口是移動窗口。在每次引用時,會增加新引用,而最老的引用會失去。如果一個頁在工作集合窗口內被引用過,那么它就處於工作集合內。

頁錯誤頻率

    工作集合模型是成功的,工作集合知識能用於預先調頁,但是用於控制顛簸有點不太靈活。一種更為直接的方法是采用頁錯誤頻率(page-fault frequency,PFF)策略。

    這里的問題是如何防止顛簸,顛簸具有高的也錯誤率。因此,需要控制頁錯誤率。當也錯誤率太高時,進程需要更多幀。類似地,如果頁錯誤率太低,那么進程可能有太多的幀。可以為所期望的頁錯誤率設置上限和下限。如果實際頁錯誤率超過上限,那么為進程分配更多的幀;如果實際頁錯誤率低於下限,那么可從該進程中移走幀。因此,可以直接測量和控制頁錯誤率以防止顛簸。

    與工作集合策略一樣,也可能必須暫停一個進程。如果頁錯誤增加且沒有可用幀,那么必須選擇一個進程暫停。接着,可將釋放的幀分配給那些具有高頁錯誤率的進程。

內存映射文件

    考慮一下采用標准系統調用open(),read()和write(),並在磁盤上對文件進行一系列的讀操作。文件每次訪問時都需要一個系統調用和磁盤訪問。另外一種方法時,可使用所討論的虛擬內存技術來將文件I/O作為普通內存訪問。這種方法稱為文件的內存映射(memory mapping),它允許一部分虛擬內存與文件邏輯相關聯。

基本機制

    文件的內存映射可將一磁盤塊映射成內存的一頁(或多頁)。開始的文件訪問按普通請求頁面調度來進行,會產生頁錯誤。這樣,一頁大小的部分文件從文件系統讀入物理頁(有的系統會一次讀入多個一頁大小的內容)。以后文件的讀寫就按通常的內存訪問來處理,由於是通過內存操作文件而不是使用系統調用read()和write(),從而簡化了文件訪問和使用。

    多個進程可以允許將同一文件映射到各自的虛擬內存中,以允許數據共享。其中任一進程修改虛擬內存中的數據,都會為其他映射相同文件部分的進程所見。每個共享進程的虛擬內存表都指向物理內存的同一頁,該頁有磁盤塊的復制。內存映射系統調用還支持寫時復制功能,允許進程共享只讀模式的文件,但也有它們所修改數據的各自副本。

內核內存的分配

    當用戶態進程需要額外內存時,可以從內核所維護的空閑頁幀鏈表中獲取頁。該鏈表通常由頁替換算法來更新,且如前所述,這些頁幀通常分散在物理內存中。另外,請記住,如果用戶進程只需要一個字節的內存,那么會產生內部碎片,這是因為進程會得到整個頁幀。

    但是,內核內存的分配通常是從空閑內存池中獲取的,而並不是從滿足普通用戶模式進程的內存鏈表中獲取的。這主要有兩個原因:

  1. 內核需要為不同大小的數據結構分配內存,其中有的不到一頁。因此,內核必須謹慎使用內存,並試圖減低碎片浪費。這一點非常重要,因為許多操作系統的內核代碼與數據不受分頁系統控制。
  2. 用戶進程所分配的頁不必要在連續的物理內存中。然而,有的硬件要直接與物理內存打交道,而不需要經過虛擬內存接口,因此需要內存常駐在連續的物理頁中。

slab分配

    內核分配的另一種方案是slab分配。Slab是由一個或多個物理上連續的頁組成的。高速緩存(cache)含有一個或多個slab。每個內核數據結構都有一個cache,如進程描述符、文件對象、信號量等。每個cache含有內核數據結構的對象實例。例如,信號量cache存儲着信號量對象,進程描述符cache存儲着進程描述對象。下圖描述了slab,cache以及對象三者之間的關系。Slab分配算法采用cache存儲內存對象。當創建cache時,起初包括若干標記為空閑的對象。對象的數量與slab的大小有關。

    Slab分配器有兩個主要優點:

  1. 沒有因碎片而引起的內存浪費。碎片不是問題,這時因為每個內核數據結構都有相應的cache,而每個cache都由若干slab組成,而每個slab又分為若干個與對象大小相同的部分。因此,當內核請求對象內存時,slab分配器可以返回剛好可以表示對象所需的內存。
  2. 內存請求可以快速滿足。Slab分配器對於需要經常不斷分配內存、釋放內存來說特別有效,而操作系統經常這樣做。內存分配與釋放可能費時。然而,由於對象預先創建,所以可從cache上快速分配。另外,當用完對象並釋放時,只需要標記為空閑並返回給cache,以便下次再用。

小結

    需要能執行這樣一個進程,其邏輯地址大於可用物理地址空間。虛擬內存允許將大邏輯地址空間映射到小的物理內存。虛擬內存允許極大的進程運行,且提高了多道程序的程序,增加CPU使用率。再者,它使得程序員不必考慮內存可用性。另外,虛擬內存允許進程共享系統庫與內存。當父子進程共享內存頁時,虛擬內存也允許采用寫時復制來創建進程。

    虛擬內存通常采用按需調頁方式來實現。純按需調頁只有再引用頁時才調入頁。第一次引用就會使操作系統產生頁錯誤。操作系統檢查內部表以確定該頁再備份倉庫上的位置。接着,它找到空閑幀並從備份倉庫中讀入頁。更新頁表以反映這一修改,並重新執行產生頁錯誤的指令。這種方法允許一個進程運行,即使完整內存影像不能同時在內存中。只要頁錯誤率足夠低,那么性能就可以被接受。

    使用按需調頁以降低分配給進程的幀數。這種安排能增加多道程序的程度(允許更多進程同時執行),且增加系統CPU使用率。即使進程內存需要超過總的物理內存,也能允許進程運行。這些進程可以在虛擬內存中運行。

    如果總的內存需求超過了物理內存,那么有可能必須置換內存中的頁以便為新頁所用。可以使用各種頁置換算法。FIFO頁置換算法容易編程,但有Belady異常。最優頁置換算法需要將來的知識。LRU頁置換能近似最優算法,但是它也很難實現。絕大多數頁置換算法,如二次機會算法,都是LRU置換的近似。

    除了頁置換算法,還需要幀分配策略。分配可以是固定的,如局部頁置換算法;或動態的,如全局置換。工作集合模型假定進程在局部內執行。工作集是位於當前局部所有頁的集合。相應地,每個進程應該為其當前工作集合分配足夠多地內存以避免顛簸,可能需要進程交換和調度。

    許多操作系統提供內存映射文件功能,如允許文件I/O像內存訪問操作一樣。Win32API通過內存映射來實現內存共享。

    內核進程通常需要按物理連續方式來分配內存。Buddy系統允許內核進程按2的冪大小來分配,這會產生碎片。Slab分配器允許從由slab組成的cache進行分配,每個slab由若干物理連續的頁組成。采用slab分配器,不會因碎片問題而產生內存浪費,內存請求可以很快得到滿足。

    除了要求解決頁置換和幀分配的主要問題外,合理設計調頁系統還要求考慮頁大小,I/O、加鎖、預調頁、進程創建、程序結構和其他問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM