第九章 虛拟存儲器
虛拟存儲器提供了三種重要的能力
- 它将主存看成是一個存儲在磁盤上的位址空間的高速緩存,在主存中隻儲存活動區域,并根據需要在磁盤和主存之間來回傳送資料。
- 它為每個程序提供了一緻的位址空間,簡化了存儲器的管理。
- 它保護了每個程序的位址空間不被其他程序破壞
為什麼要了解虛拟存儲器
- 虛拟存儲器是中心的,遍及計算機系統的所有層面。2、虛拟存儲器是強大的;3、虛拟存儲器是危險的
9.1 實體和虛拟尋址
虛拟尋址:CPU生成虛拟位址通路主存,虛拟位址被翻譯成實體位址再送到存儲器。
早期PC使用實體尋址。現代處理器使用虛拟尋址。
9.2 位址空間
位址空間是一個非負整數位址的有序集合。如果位址空間中的整數是連續的,則成為線性位址空間。
虛拟位址空間:的大小是由表示最大位址所需要的位數來描述。
9.3 虛拟存儲器作為緩存工具
9.3虛拟存儲器作為緩存的工具
概念上而言, 虛拟存儲器 被組織為一個由“存放在磁盤上的N個連續位元組大小的單元”組成的數組。磁盤上的内容被緩存到記憶體中,記憶體和磁盤之間采用塊block格式傳輸。由于虛拟存儲的技術早于高速緩存的技術,是以當時這種塊被稱之為頁(page),也就是現在的說法。如下圖:
上圖展示了一個8個虛拟頁的小型虛拟存儲器。綠色的3個頁表示已經緩存到記憶體了(參考記憶體中綠色的三個頁)連個未配置設定和三個未緩存的。
由于記憶體的通路速度比磁盤快100000倍,是以如果記憶體不命中,那麼代價将是非常高的(程式運作将非常慢),另外,讀取磁盤某一個扇區第一個位元組的開銷 比起讀取整個扇區的位元組慢100000倍。這兩個條件決定了虛拟存儲的頁的大小最好是一個扇區或幾個扇區。典型地是4KB-2MB。
還有,即便是有個很大的頁,如果作業系統在替換頁的操作處理不當,速度将會很慢。缺頁是一種異常,名字就叫“缺頁”,發生異常後,虛拟存儲器加載磁盤到記憶體并替換犧牲頁。作業系統再次運作該指令,就不再缺頁了。
當我們與多人了解了虛拟存儲器的概念之後,我們的第一印象是:“它的效率應該很低下吧,因為如果不命中,那麼代價将非常高。”我們也擔心頁面的排程破壞程式的性能。實際上,虛拟存儲器運作的相當好,主要歸功于程式 局部性 (locality).
盡管整個運作過程中程式引用的不同頁面的總數可能超出實體存儲器的總的大小,但是局部性原則保證了在任意時刻,程式将往往在一個較小的活動頁面(active page)集合工作,這個集合叫做工作集。
tips 在Unix系統中使用getrusage函數來檢測缺頁的數量 。
9.4虛拟存儲器作為存儲器管理工具
實際上,作業系統為“每一個程序”提供了一個獨立的頁表,因而也就是一個獨立的虛拟位址空間。展現形式就是:讓人感覺這個程式正在完全是使用CPU和記憶體資源。如下圖,多個虛拟葉面同僚映射到同一個共享實體頁面上(動态連結庫或者叫共享庫就是這個原理)。
程序獨立的位址空間允許每個程序的存儲器映像使用相同的基本格式,而不管代碼和資料實際存放在實體存儲器的地方。比如Linux系統上每個程序文本節總是 從虛拟位址0x08048000初開始。總結起來說:虛拟存儲器簡化了連結、簡化了加載、簡化共享和簡化存儲器配置設定。
9.5虛拟存儲器作為存儲器保護的工具
現代作業系統都嚴格限制普通應用程式通路自己的隻讀資料和别程序的資料,還有作業系統的核心部分。是以虛拟存儲器可以在頁的開始部分設定幾個标志位,用于 标明這個程序是系統程序還是使用者程序。如果某一天指令違反了這個條件,那麼CPU就觸發一個異常。Unix外殼一般将這種異常報告為“段錯誤 segmentation fault”
9.6位址翻譯,有點兒專業。跟應用程式層面關系不大。
9.7案例研究:Intel Core i7/Linux存儲器系統
在64位的mac電腦上列印一個變量的指針, 結果指針是6個位元組 。這是因為i7的CPU隻支援48位的虛拟位址,和52位的實體位址空間。雖然我的mac是i5的但是估計也是48位虛拟位址。其他内容是位址翻譯,缺頁異常。
Linux虛拟存儲器區域:Linux将虛拟存儲器組織成一些區域的集合。一個區域(area)就是已經存在着(已配置設定的)的虛拟存儲器的連續片(chunk)
9.8存儲器映射
有一個疑問:“電腦4G的記憶體,加載一個程式很慢,但是看看記憶體剩餘量,還有剩餘記憶體,不應該慢啊。如果更新到8G的記憶體,程式加載的快了。這是因為操作 系統有一個叫做交換空間的東西,交換空間可能顯示虛拟存儲器的頁數“。明白了,如果記憶體大,交換空間就大,程式加載就快。
概念:私有有的寫時拷貝,了解這個概念很重要。用圖來說明:
上圖右部分說明了寫時拷貝:程序1和2都共享記憶體中的一組頁,但是程序2需要寫最後一個頁。那麼啟動寫時拷貝,拷貝最後一頁到記憶體其他地方。(記憶體中的頁,可能不是連續的)。
fork函數直接在虛拟存儲器上開辟一段,跟主程序一不一樣的拷貝,那麼,也就跟主程序指向同一個實體存儲器。從上圖左邊部分中我們可以印證一下程序2可以看做是從程序1調用fork函數建立的。
execve是如何加載應用程式的。execve函數在目前程序中加載并運作a.out,用a.out程式有效的替換了目前程式。加載a.out時候需要以下幾個步驟:
1)删除已存在的使用者區域。
2)映射私有區域。為新的程式的文本、資料、bss、和棧區域建立新的區域結構。
3)映射共享區域。
4)設定程式技術器PC。使之指向文本區域的入口點。
9.9動态存儲器配置設定malloc
動态配置設定,我自己的了解就是配置設定在虛拟存儲器中的區域,這個區域可能已經在記憶體中了,也可能在磁盤上。
下面是真正的定義:動态存儲器配置設定器,維護着一個程序的虛拟存儲器區域,成為”堆“,對于每一個程序,作業系統核心維護着一個變量brk(break)指向堆的頂部。配置設定器有兩種基本風格。
1)顯式配置設定器:例如C和C++的malloc 和new 運算法。但是需要程式員收到free和delete處理。
2)隐式配置設定器:也叫做垃圾收集器,其自動釋放未使用的已配置設定的塊的過程叫做垃圾收集。例如:Java的垃圾回收機制。
動态存儲配置設定器的要求和目标
●處理任意請求序列。
●立即響應請求。配置設定器必須立即響應配置設定請求。是以不允許配置設定器提高性能,從新排列或者緩沖請求。
●隻是用堆。
●對齊塊,比如8個位元組的對齊。
●不修改已配置設定的塊。不能壓縮已配置設定的塊。
目标
●1最大化吞吐率
●2最大化存儲器的使用率。天真的程式員經常不正确的假設虛拟存儲器是一個無限的資源,事實上,一個系統中被所有程序配置設定的虛拟存儲器的全部數量是受磁盤上 交換空間 的數量限制的。
存儲器碎片(這是一個很有意思的話題)。首先介紹内部碎片和外部碎片。如圖:
外部碎片要比内部碎片處理複雜得多!配置設定器還要有其他功能: 合并空閑的塊,也是非常有意思的(原書使用19頁的量,來描述配置設定器的原理和動作 ),聰明的程式員采用了很多種技巧來實作,參考p568頁。
9.10 垃圾收集(回收)
垃圾收集器就是一個動态配置設定器,它自動釋放這些程式不再需要的已配置設定的塊。垃圾收集可以追溯到20世紀60年代早起在MIT開發的Lisp系統。他是諸如Java、ML、Perl和Mathematica等現代語言系統的一個重要的部分。
垃圾回收的原理,如圖。那些灰色的小圓點表示已經是垃圾了,需要清理。
是用專業的詞語是:上圖是一張可達圖,白色的塊表示可達,灰色的塊表示不可達(從任意根節點出發不可達)。
9.11程式中常見的與存儲器有關的錯誤。
●間接引用壞指針
●讀未初始化的存儲器
●允許棧緩存去溢出
●假設指針和它們指向的對象是相同大小的
●造成錯位
●引用指針,而不是它指向的對象
●誤解指針運算
●應用不存在的變量
●應用空閑塊中的資料
●引起存儲器洩露
總結
虛拟存儲器的三個重要功能:
第一,它在記憶體中自動緩存“最近使用的存放在磁盤上的”虛拟位址空間的内容。
第二,虛拟存儲器簡化了存儲器的管理,簡化連結和共享
第三,虛拟存儲器通過在每條頁表條目中加入保護位進而簡化了存儲器保護。