接下來幾節都是對虛擬存儲的講解。虛擬存儲是非連續存儲管理的擴展。通過將內存中的數據暫存到外存的方式,為進程提供更大的內存空間。虛擬存儲出現的主要原因是因為程序規模的增長速度遠遠大于存儲器容量的增長速度,導致內存空間不夠用。其實針對內存空間不夠用的問題有多重解決方案,比如覆蓋、交換、虛擬存儲。它們的概念如下:
- 覆蓋:應用程序手動把需要的指令和數據加載到內存;
- 交換:操作系統自動把暫時不能執行的程序保存到外存中;
- 虛擬存儲:在有限的內存中,以頁為單位自動裝入更多更大的程序
覆蓋
目標:在較小的可用內存中運行較大的程序。
方法:依據程序邏輯,將程序劃分為若干功能相對獨立的模塊,不會同時執行的模塊共享同一塊內存區域。具體有以下幾點:
- 必要部分(常用功能)的代碼和數據常駐內存;
- 可選部分(不常用功能)放在其它程序模塊中,只在需要時加載到內存;
- 不存在調用關系的模塊可以相互覆蓋,共用同一塊內存區域。
具體操作如下:假設有一個程序總大小190K,調用關系如下圖。我們按照上述原則對程序進行分組,比如A單獨一組,B與C沒有調用關系因此B、C一組,D、E、F沒有調用關系分為一組,分配內存時按照組內最大的模塊分配內存,如圖中右側,A劃分20K,B、C這組分配50K,D、E、F這組分配40K,按照需求向內存中加載。這樣這個程序可以在內存大小為110K的機器中運行。那么這種分組方式是不是最優的呢?答案是否定的,我們可以將大小相近的分到一組,比如A單獨一組20K,B、E、F無調用關系50K,C、D無調用關系30K,總共需要100K內存。因此不同的分組方式對內存的需求是不一樣的。想要嚴格討論哪種方式內存需求最小是很復雜的。
缺點
- 增加編程困難。需要程序員劃分功能模塊,并確定模塊之間的覆蓋關系,增加了編程的復雜度;
- 增加執行時間。需要將各程序模塊在內存模塊中換入換出,實際是用時間換空間。
交換
交換與覆蓋討論的尺度是不太一樣的。覆蓋是一個程序內,如果無法全部加載到內存,而交換是內存足夠放一個程序,而無法放多個程序。
目標:增加正在運行或需要運行的程序的內存。
實現方法:
- 可將暫時不能運行的程序放到外存;
- 換入換出的基本單位是進程;
- 換出是把一個進程的整個地址空間保存到外存;
- 換入是把一個進程的整個地址空間從外存加載到內存。
問題
- 交換時機:只當內存空間不足或有不足的可能的時候才進行換入換出;
- 交換區大小:存放所有用戶進程的所有內存映像的拷貝;
- 程序換入時的重定位:換入時不是放回原處,因此需要程序運行過程中采用動態地址映射。
覆蓋與交換對比:
- 覆蓋:
- 只能發生在沒有調用關系的模塊之間;
- 程序員須給出模塊之間的邏輯覆蓋關系;
- 發生在運行程序的內部模塊間。
- 交換:
- 以進程為單位;
- 不需要模塊間的邏輯覆蓋關系;
- 發生在內存進程間。
交換是可以通過操作系統實現的,而覆蓋是無法在操作系統中實現的,必須有程序員具體實現,增大了編程的難度。
虛擬存儲
虛擬存儲是想把一部分內存中的內容暫時存放到外存中,以提供更大的內存空間。例如
虛擬存儲的目標:
- 只把部分程序放到內存中,從而運行比物理內存大的程序。并且由操作系統完成,無需程序員的干涉。
- 實現進程在內存和外存在之間交換,從而獲得更多的空閑內存空間。在內存與外存之間只交換進程的部分內容。
局部性原理
具體講虛擬存儲之前先了解一下局部性原理。局部性原理是指程序在執行過程中的一個較短時期,所執行的指令地址和指令的操作數地址,分別局限于一定區域。比如通常指令在代碼段,操作數在數據段。具體體現在以下幾個方面:
- 時間局部性。一條指令的一次執行和下次執行,一個數據的一次訪問與下次訪問都集中在一個較短時期內;
- 空間局部性。當前指令和鄰近的幾條指令,當前訪問的數據和鄰近的幾條數據都集中在一個較小的區域內;
- 分支局部性。一條跳轉指令的兩次執行,很可能跳轉到相同的內存位置。
由于局部性原理的存在,從理論上講,虛擬存儲技術是能夠實現的,可以取得滿意的效果。
虛擬存儲基本概念:
- 思路:將不常用的部分內存塊暫存到外存。
- 原理:裝載程序時,只將當前指令執行需要的部分頁面或段裝入內存;指令執行中需要的指令或數據不在內存中(缺頁或缺段)時,處理器通知操作系統將相應的頁或段調入內存中;操作系統將內存中暫時不用的頁面或段保存到外存中,如何判定哪些不常用,需要后續的置換算法。
- 實現方式:虛擬頁式存儲、虛擬段式存儲。
虛擬存儲的基本特征:
- 不連續性。物理內存分配非連續,虛擬地址空間使用非連續。
- 大用戶空間。提供給用戶的虛擬內存可大于實際的物理內存。
- 部分交換。虛擬存儲支隊部分虛擬地址空間進行調入和調出。
虛擬存儲的支撐技術:
- 硬件支持。頁式或段式存儲中的地址轉換機制。除之前非連續內存管理中所講的地址轉換,還要增加判斷所需要的內容不再內存中。
- 操作系統支持。管理內存和外存間頁面或段的換入和換出。
虛擬頁式存儲:
在頁式存儲管理的基礎上,增加請求調頁和頁面置換。具體思路是當用戶程序要裝載到內存運行時,只裝入部分頁面就啟動程序運行。進程在運行中發現有需要的代碼或數據不在內存時,則向系統發出缺頁異常請求。操作系統在處理缺頁異常時,將外存中相應的頁面調入內存,使得進程能夠繼續運行。如下圖所示,在頁表中加入標志位,當發現缺頁時觸發缺頁異常,操作系統接管,找到相應頁面置入內存,并將標志位修改為有效。
虛擬頁式管理中,頁表項結構有相應調整,如下圖:增加了幾個標志位。其中駐留位用來表示是否在內存中;修改位表示在內存中的該頁是否被修改過,因為如果沒有修改過而外存中又有這一頁面時不需要置換,直接作廢該頁表項即可,如果被修改過則需要重新置換;訪問位表示該頁是否被訪問過(讀或寫),用于置換算法,用于近似統計是否經常訪問;保護位表示該頁的允許訪問方式,如可讀可寫。
示例:
下面是X86 32位系統以4K為頁幀大小時的頁表項結構:幀號項占20位,標志位中除了前面提到的駐留位、可寫標志、訪問位、修改位,還有幾個沒提到過的。一個是用戶態標志,表示用戶態是否可以訪問;保留位,用于擴展,比如現在很多32位系統不僅支持4G內存;CD位,緩存是否有效,是否允許啟用高速緩存;WT是否寫通。
缺頁異常:
缺頁異常即發現需要的內存頁不在內存中。缺頁異常處理流程如下圖:
執行命令時CPU給出需要的邏輯地址,查找頁表;發現缺頁,產生缺頁異常;操作系統啟用缺頁異常例程,查找頁面在外存中的位置;從物理內存中找空閑頁幀并將頁面換入空閑處;將頁表項修改為有效;重新執行導致異常的指令。
可以看到這是最順利的情況,如果換入時發現沒有空閑頁幀的話則需要額外幾步:根據頁面置換算法選擇要被換出的頁幀;判斷該頁幀是否被修改過,如果被修改過則寫回外存,否則直接作廢;將其對應的頁表項改為無效;然后將需要的頁幀換入空出來的區域,后續與最開始的流程一致。
虛擬頁式存儲中的外存是如何管理的呢?首先是在何處保存未被映射的頁?因為必須能夠方便地找到在外存中的頁面內容,因此我們需要一個交換空間,有兩種方式實現:磁盤或文件。Linux就是直接有交換分區。或者文件采用特殊格式存儲未被映射的頁面。
虛擬頁式存儲中的外存選擇也是個問題。代碼段本身就是存儲在可執行文件中的,因此代碼段是直接指向你的可執行文件;動態加載的共享庫程序段也直接指向相應的文件;其他段放在對換區。
虛擬頁式存儲管理的性能如何評價呢?
我們定義了一個有效存儲訪問時間(effective memory access time)EAT。
舉個例子:可以看到想要效率較高,需要缺頁率p極小。