一、早期系統
? ? ???從內存來看,早期的機器并沒有提供多少抽象給用戶。基本上,機器的物理內存如圖13.1所示
????????操作系統曾經是一組函數(實際上是一個庫),在內存中(在本例中,從物理地址0開始),然后有一個正在運行的程序(進程),目前在物理內存中(在本例中,從物理地址64KB開始), 并使用剩余的內存。這里幾乎沒有抽象,用戶對操作系統的要求也不多。
二、多道程序和時分共享
? ? ? ? 由于機器昂貴,人們開始更有效地共享機器。因此,多道程序系統時代開啟,其中多個進程在給定時間準備運行,比如當有一個進程在等待 I/O 操作的時候,操作系統會切換這些進程,從而增加了 CPU 的有效利用率。
? ? ? ? 但很快,分時系統的時代誕生了,許多人意識到批量計算的局限性,尤其是程序員本身,厭倦了長時間的(低效率的)編程——調試循環。交互性變得很重要,因為許多用戶可能同時在使用機器,每個人都在等待(或希望)它們執行的任務及時響應。
????????一種實現時分共享的方法,是讓一個進程單獨占用全部內存運行一小段時間(見圖 13.1),然后停止它,并將它所有的狀態信息保存在磁盤上(包含所有的物理內存),加載其他進程的狀態信息,再運行一段時間,這就實現了某種比較粗糙的機器共享。?
? ? ? ? 這種方法的問題:太慢,特別是當內存增長的時候。雖然保存和恢復寄存器級的狀態信息(程序計數器、通用寄存器等)相對較快,但將全部的內存信息保存到磁盤就太慢了。因此,在進程切換的時候,我們仍然將進程信息放在內存中,這樣操作系統可以更有效率地實現時分共享(見圖13.2)。
????????在圖13.2中,有3個進程(A、B、C),每個進程擁有從 512KB 物理內存中切出來給它們的一小部分內存。假定只有一個CPU,操作系統選擇運行其中一個進程(比如A),同時其他進程(B和C)則在隊列中等待運行。?
???????隨著時分共享變得更流行,對操作系統又有了新的要求。特別是多個程序同時駐留在內存中,使保護(protection) 成為重要問題。不希望一個進程可以讀取其他進程的內存,更別說修改
三、地址空間
? ? ? ? 操作系統需要提供一個易用的物理內存抽象。這個抽象叫做地址空間,是運行的程序看到的系統中的內存。理解這個基本的操作系統內存抽象,是了解內存虛擬化的關鍵。
?????????一個進程的地址空間包含運行的程序的所有內存狀態。比如:程序的代碼(code,指令) 必須在內存中,因此它們在地址空間里。當程序在運行的時候,利用棧(stack)來保存當前的函數調用信息,分配空間給局部變量,傳遞參數和函數返回值。最后,堆(heap)用于管理動態分配的、用戶管理的內存,就像從C語言中調用 malloc() 或面向對象語言(如C ++ 或Java)中調用new 獲得內存。當然,還有其他的東西(例如,靜態初始化的變量),但現在假設只有這3個部分:代碼、棧和堆。
? ? ? ? 在圖 13.3 的例子中,有一個很小的地址空間(16KB)。程序代碼位于地址空間的頂部(本例中從 0 開始,并且裝入到地址空間的前 1KB)。代碼是靜態的(因此很容易放在內存中),所以可以將它放在地址空間的頂部,我們知道程序運行時不再需要新的空間。
????????接下來,在程序運行時,地址空間有兩個區域可能增長(或者收縮)。它們就是堆(在頂部) 和棧(在底部)。通過將它們放在地址空間的兩端,可以允許這樣的增長:它們只需要在相反的方向增長。因此堆在代碼(1KB) 之下開始并向下增長(當用戶通過 malloc()請求更多內存時),棧從 16KB 開始并向上增長(當用戶進行程序調用時)。然而,堆棧和堆的這種放置方法只是一種約定,如果你愿意, 可以用不同的方式安排地址空間 [當多個線程(threads)在地址空間中共存時,就沒有像這樣分配空間的好辦法了]。
? ? ? ? 當然,描述地址空間時,所描述的是操作系統提供給運行程序的抽象。程序不在物理地址0~16KB的內存中,而是加載在任意的物理地址。回顧圖13.2中的進程 A、B和C,可以看到每個進程如何加載到內存中的不同地址。因此問題來了:
?????????????????????????????????????????????????關鍵問題:如何虛擬化內存
????????操作系統如何在單一的物理內存上為多個運行的進程(所有進程共享內存)構建一個私有的、可能很大的地址空間的抽象?
????????當操作系統這樣做時,我們認為操作系統在虛擬化內存(virtualizing memory),因為運行的程序認為它被加載到特定地址(例如 0)的內存中,并且具有非常大的地址空間(例如 32 位或64位)。現實很不一樣。
????????例如,當圖13.2中的 進程A 嘗試在地址0(我們將稱其為虛擬地址,virtual address) 執行加載操作時,然而操作系統在硬件的支持下,出于某種原因,必須確保不是加載到物理地址0,而是物理地址320KB(這是A載入內存的地址)。這是內存虛擬化的關鍵,這是世界上每一個現代計算機系統的基礎。
四、目標
? ? ? ? 操作系統的工作——虛擬化內存。操作系統不僅虛擬化內存,還有一定的風格。為了確保操作系統這樣做,我們需要一些目標來指導。
?????????虛擬內存(VM)系統的一個主要目標是透明(transparency)。操作系統實現虛擬內存的方式,應該讓運行的程序看不見。因此,程序不應該感知到內存被虛擬化的事實,相反,程序的行為就好像它擁有自己的私有物理內存。在幕后,操作系統(和硬件)完成了 所有的工作,讓不同的工作復用內存,從而實現這個假象。
?????????虛擬內存的另一個目標是效率(efficiency)。操作系統應該追求虛擬化盡可能高效 ,包括時間上(即不會使程序運行得更慢)和空間上(即不需要太多額外的內存來支持虛擬化)。在實現高效率虛擬化時,操作系統將不得不依靠硬件支持,包括TLB這樣的硬件功能。
?????????虛擬內存第三個目標是保護(protection)。操作系統應確保進程受到保護(protect), 不會受其他進程影響,操作系統本身也不會受進程影響。當一個進程執行加載、存儲或指令提取時,它不應該以任何方式訪問或影響任何其他進程或操作系統本身的內存內容(即在它的地址空間之外的任何內容)。因此,保護讓我們能夠在進程之間提供隔離(isolation) 的特性,每個進程都應該在自己的獨立環境中運行,避免其他出錯或惡意進程的影響。
????????在接下來的章節中,我們將重點介紹虛擬化內存所需的基本機制(mechanism),包括硬件和操作系統的支持。我們還將研究一些較相關的策略(policy),你會在操作系統中遇到它們,包括如何管理可用空間,以及在空間不足時哪些頁面該釋放。通過這些內容,你 會逐漸理解現代虛擬內存系統真正的工作原理①。
五、小結
????????介紹了操作系統的一個重要子系統:虛擬內存。虛擬內存系統負責為程序提供一 個巨大的、稀疏的、私有的地址空間的假象,其中保存了程序的所有指令和數據。操作系統在專門硬件的幫助下,通過每一個虛擬內存的索引,將其轉換為物理地址,物理內存根據獲得的物理地址去獲取所需的信息。操作系統會同時對許多進程執行此操作,并且確保程序之間互相不會受到影響,也不會影響操作系統。整個方法需要大量的機制(很多底層機制)和一些關鍵的策略。?