一個系統中的進程是與其他進程共用CPU和記憶體資源的,然而,如果太多的進程需要太多的儲存空間,那麼他們中的一些就根本無法運行。儲存空間還很容易破壞。如果某個進程不小心寫了另外一個進程的儲存空間,它就可能易某種完全跟程式邏輯無關的方式失敗。為了更加有效地管理儲存空間並且少出錯,現代系統提供了一種對記憶體的抽象概念,叫做虛擬儲存空間(VM).
作為程式員我們為什麼要瞭解虛擬儲存空間呢。1)虛擬儲存空間是中心。虛擬儲存編輯系統的所有層面。2),虛擬儲存空間是強大的。虛擬儲存空間給予應用程式強大的能力,可以建立和銷毀儲存空間片(chunk)、將儲存空間片映射到磁碟檔案的某個部分,以及其他進程共用儲存空間。3),虛擬儲存空間是危險的。每次應用程式引用一個變數、間接引用一個指標,或者調用一個類似與malloc的動態分配的函數,就會跟虛擬儲存空間打交道。
我們電腦上啟動並執行程式都是使用虛擬儲存,跟實體記憶體根本不搭邊。
9.1物理和虛擬定址
早期的電腦採用物理定址(Physical Addressing),現代的電腦採用虛擬定址(virtual addressing),將一個虛擬位址轉換為物理地址的任務叫做地址翻譯。地址翻譯需要CPU硬體和作業系統之間的緊密合作。CPU晶片中有一個MMU的儲存空間嵌入式管理單元(Memory Management Unit)。如所示:
從上面兩張圖上發現,虛擬定址只是CPU的一種技術。
9.2地址空間
地址空間是一個非負數地址的有序集合:如果地址空間的整數是連續的,那麼我們說它是一個線性地址空間。那麼一個擁有虛擬定址的CPU,也就會有一個虛擬位址空間。記憶體中的一個位元組,就會有一個物理地址和一個虛擬位址。如果CPU有N個虛擬位址空間,那麼該位元組就有N個虛擬位址。
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程式中常見的與儲存空間有關的錯誤。
●間接引用壞指標
●讀未初始化的儲存空間
●允許棧緩衝去溢出
●假設指標和它們指向的對象是相同大小的
●造成錯位
●引用指標,而不是它指向的對象
●誤解指標運算
●應用不存在的變數
●應用空閑塊中的資料
●引起儲存空間泄露
總結 虛擬儲存空間的三個重要功能: 第一,它在記憶體中自動緩衝“最近使用的存放在磁碟上的”虛擬位址空間的內容。 第二,虛擬儲存空間簡化了儲存空間的管理,簡化連結和共用 第三,虛擬儲存空間通過在每條頁表條目中加入保護位從而簡化了儲存空間保護。 點擊《深入理解電腦系統》一
點擊《深入理解電腦系統》二
點擊《深入理解電腦系統》三