操作系統內存管理之虛擬內存


9.1 背景

虛擬地址空間:進程在內存中存放的邏輯視圖。如圖所示。

虛擬內存:是一種內存管理技術,它會使程序自己認為自己擁有一塊很大且連續的內存,然而,這個程序在內存中不是連續的,並且有些還會在磁盤上,在需要時進行數據交換 。

允許隨着動態內存分配,堆向上生長;允許隨着子程序的不斷調用,棧向下生長。只有在堆和棧生長時,才需要實際的物理頁。包括空白的虛擬地址空間成為稀地址空間。其優點是:隨着程序的執行,棧或堆段的生長或需要載入動態鏈接庫(或共享對象)時,這些空白可以填充。

虛擬內存也允許文件和內存通過共享頁而為兩個或多個進程所共享。如圖所示:

 

9.2 按需調頁              在需要時才調入相應的頁(可視為懶惰交換)

區分:交換程序是對整個進程操作(因此這里是懶惰交換),調頁程序只是對進程的單個頁進行操作,按需調頁使用的是調頁程序。

有效位和無效位:有效:表示相關的頁既合法也在內存中;無效:表示該頁不在進程的邏輯地址空間內(有的博客說此時應顯示NULL)或有效但是在磁盤中。如圖所示:

 

Q:當進程試圖訪問未調入內存(在磁盤上)的頁,將會如何?

A:對這些標記為無效的頁,將產生頁錯誤陷阱。由於操作系統未能將所需的頁調入內存而產生。處理流程:

(1)檢查進程的內部頁表(通常與PCB一起保存),以確定該引用是合法的還是非法的地址訪問;

(2)若引用非法,則終止進程。若引用有效但尚未調入頁面,則現在應調入;

(3)找到一個空閑幀(如從空閑鏈表中選一個);

(4)調度一個磁盤操作,以便將所需要的頁調入剛分配的幀;

(5)當磁盤讀操作完成后,修改進程的內部表和頁表,以表示該頁已在內存中;

(6)重新開始因陷阱而中斷的指令,進程現在可以訪問所需頁。如圖所示:

極端情況:所有的頁都不在內存中,成為純粹按需調頁:只有在需要時才將頁調入內存

原理:局部性:

空間局部性:電腦中某一塊內存在使用時,在不久的將來會使用內存地址相近的一塊內存區域。

 時間局部性:電腦中某一條指令在使用完之后,在不久的將來有很大可能再次使用它。

因此,有的程序的單個指令可能訪問多個頁的內存,從而一個指令可能產生多個頁錯誤。不過由於局部性,這種情況很少見。

硬件支持:(1)頁表:該表能夠通過有效-無效位或保護位的特定值,將條目設為無效;

                  (2)次級存儲器:保存不在內存中的頁,通常為快速磁盤,通常稱為交換設備,用於交換的這部分磁盤通常稱為交換空間

關鍵:能夠在頁錯誤后重新執行指令,出現頁錯誤時,保存中斷進程的狀態(寄存器、條件代碼、指令計數器),必須能夠按完全相同的位置和地址重新開始執行進程。

性能分析:計算按需調頁內存的有效訪問時間:

                  設p為頁錯誤的概率(0<=p<=1),則有效訪問時間為:

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

 

9.3 寫時復制

fork():為子進程創建一個父進程地址空間的副本,復制屬於父進程的頁。但由於許多子進程在創建之后立即執行exec(),所以父進程地址空間的復制可能沒有很必要。

exec():

fork()函數通過系統調用創建一個與原來進程(父進程)幾乎完全相同的進程(子進程是父進程的副本,它將獲得父進程數據空間、堆、棧等資源的副本。注意,子進程持有的是上述存儲空間的“副本”,這意味着父子進程不共享這些存儲空間。linux將復制父進程的地址空間內容給子進程,因此,子進程由了獨立的地址空間。),也就是這兩個進程做完全相同的事。

在fork后的子進程中使用exec函數族,可以裝入和運行其它程序(子進程替換原有進程,和父進程做不同的事)。

exec函數族可以根據指定的文件名或目錄名找到可執行文件,並用它來取代原調用進程的數據段、代碼段和堆棧段。在執行完后,原調用進程的內容除了進程號外,其它全部被新程序的內容替換了。另外,這里的可執行文件既可以是二進制文件,也可以是Linux下任何可執行腳本文件。

 

寫時復制技術:允許父進程與子進程開始時共享同一頁面,這些頁面被標記為寫時復制頁,即如果任何一個進程需要對頁進行寫操作,就創建一個共享頁的副本。如圖所示:

 

 

  假設子進程試圖修改含有部分棧的頁,且操作系統可識別出該頁為寫時復制頁(注意:只有可能修改的頁才需要被標記為寫時復制,不能修改的頁(即包含可執行代碼的頁)可以為父進程與子進程所共享),則  OS創建一個該頁的副本,將其映射到紫禁城的地址空間內,這樣,子進程會修改自己的頁,而非父進程的頁。

Q:如何選擇一個空閑頁被分配用於寫時復制?

A:OS提供了空閑緩沖池。這些空閑頁在進程棧或堆必須擴展時用於分配,或用於管理寫時復制頁。OS通常采用按需填零的技術分配頁。即這些頁在分配之前先填零,清除以前內容。

 

擴展:vfork()?(fork()的變種,虛擬內存fork)

           vfork()會將父進程掛起,子進程使用父進程的地址空間。vfork不采用寫時復制,若子進程修改父進程地址空間的任何頁,則這些修改后的頁在父進程重啟后可見。

fork()與vfork()的區別:https://blog.csdn.net/ValDC_Morning/article/details/77414826

  

 

9.4 頁面置換

Q:當出現內存的過度分配時怎么辦?過度分配:當一個用戶進程執行時,一個頁錯誤發生。OS會確定所需頁在磁盤上的位置,但發現空閑幀列表上沒有空閑幀,所有內存都在使用。

A:采用頁置換

 

9.4.1 基本頁面置換

若沒有空閑幀,則查找當前沒有使用的幀,將其釋放。釋放方法:將其內容寫到交換空間,並改變頁表(和所有其他頁表)以表示該頁不在內存中

現在頁錯誤處理程序應包括頁置換:

(1)查找所需頁在磁盤上的位置;

(2)查找一個空閑幀:

     a.若有空閑幀,則使用它;

     b.若沒有,則使用頁置換算法選擇一個犧牲幀;

     c.將犧牲幀的內容寫到磁盤上,改變頁表和幀表。

(3)將所需頁讀入(新)空閑幀,改變頁表和幀表;

(4)重啟用戶進程。

      可以通過修改位/臟位降低額外開銷。當頁內的任何字或字節被寫入時,硬件會設置該頁的修改位表示該頁已修改。通過查看該位被設置可知自從磁盤讀入后該頁已發生修改。這樣,當發生頁面置換時,如果該位未被設置,說明該頁未被修改,故不需將其寫回磁盤上

實現按需調頁,主要解決兩個問題:幀分配算法 && 頁置換算法

 

9.4.2    FIFO頁置換

     選最舊的頁來置換

      存在Belady異常:頁錯誤率可能會隨着所分配的幀數的增加而增加。

 

9.4.3 最優置換   OPT/MIN   optimal pape-replacement algorithm

          置換(未來)最長時間不會使用的頁。(看未來)。確保對於給定數量的幀會產生最低可能的頁錯誤率。難以實現,主要用於比較研究。

         

 

9.4.4 LRU頁置換   最近最少使用算法   least-recently-used algorithm

         使用離過去最近作為不遠將來的近似,可置換最長時間沒使用的頁。(過去距離現在越遠,越應該被替換

         

   硬件實現:用計數器or棧。

 

9.4.5 近似LRU頁置換

          頁表中每項關聯一個引用位,每當引用一個頁時(無論是對頁的字節進行讀/寫),相應頁表的引用位會被硬件置位。

         二次機會算法:

          基本算法是FIFO算法。當要選擇一個頁時,檢查其引用位,如果其值為0,則直接置換該頁。若引用位為1,則給該頁第二次機會,並將其引用位清零,且其到達時間設為當前時間,並選擇下一個FIFO頁。如圖所示:

 

9.4.6   基於計數的頁置換      為每個頁保留一個用於記錄其引用次數的計數器。

最不經常使用頁置換算法:LFU      置換計數最小的頁

最常使用頁置換算法 :  MFU          置換計數最大的頁:   基於理論:具有最小次數的頁可能剛調進來,且還沒有使用。

 


免責聲明!

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



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