在早期,構建計算機系統是很簡單的。爲何?你可能會想。由於用戶指望值不高。都是那些『該死』的用戶,想要一個"易用"、"高性能"、"可靠"的系統,才引發了一系列頭疼的問題。下一次你遇到這些計算機用戶們,別忘記感謝一下他們製造出來的問題:)編程
從內存的角度來講,早期的機器並無提供太多的抽象給用戶。通常而言,機器的物理內存能夠用下圖表示:緩存
整個 OS(圖中的陰影部分,譯者注) 不過就是物理內存中的一些routines(本質上來講,一個 library),在這個例子中,從物理地址的 address 0開始。同時,還有一個運行的程序(進程)位於物理地址的 address 64k 處,而且獨自佔據了整個剩下的內存。用戶並沒對操做系統寄予太大的指望,操做系統程序員的生活仍是很輕鬆的,不是嗎?函數
一段時間事後,由於計算機實在是太貴了,人們開始更有效地共享有限的計算資源。因而多道編程
DV66:Programming Semantics for Multiprogrammed Computations 的時代開始了,在一個時刻,多個進程能夠同時等待被執行,而後操做系統不斷在這些進程中切換(好比當前進程進行 IO 的時候就能夠切換到其餘進程)。這樣頗有效地提升了 CPU 的利用效率。在那個計算機動輒幾百萬美圓的時代,這些效率的提升是很是重要的。性能
然而,人是永遠不會知足的,人名開始期待可以從操做系統那獲取更多功能,因而分時系統的時代開始了。不少人尤爲是程序員們本身意識到了 batch programming 的侷限性,厭倦了很長(因而效率低下)的 debug 週期。由於不少用戶在同時使用一臺計算機,每一個人都在等待(甚至能夠說是指望)本身的程序返回結果,可交互性(interactivity)的概念開始變得愈來愈重要。操作系統
實現 time sharing 的一種方法是讓一個進程運行一小段時間,這段時間內讓它佔據全部的內存,而後中止它,把其全部的狀態保持到硬盤上(包括全部的物理內存!),接口導入另一個進程,運行一段時間...... 這也算是一種簡陋的實現方法。debug
顯然,這種方法有很大的問題:太慢了,尤爲是內存不斷增大的時候。保存和歡迎寄存器級別的狀態(好比 PC,general-purpose registers等等)是很快的,可是要把整個內存的所有內容保存到硬盤,這無疑不是一種高效的方法。因此,咱們得想辦法在切換的時候把進程的狀態保持在內存裏面。設計
在這個圖中,有三個進程(A,B,C),每一個佔據 512KB內存的一小部分。如今對於 CPU,他能夠選擇任何一個抽象來運行(好比說 A),而後讓另外兩個(B 和 C)等待。3d
隨着分時系統愈來愈流行,你確定猜到了人們又開始提出新的需求了。尤爲重要的一點是:讓這麼多程序同時保存在內存裏,保護措施就很重要,你可不但願一個進程能夠看到其餘進程的內容,更別說能夠隨意修改其餘進程的內容。code
用戶是很難伺候的,操做系統的設計者得對物理內存作一個抽象,提供一個好用的接口給他們。咱們把這種抽象就叫作地址空間。對於運行中的程序而言,它對物理內存是無感知的,它知道的只有地址空間(虛擬的)。理解基本的操做系統抽象,是理解內存是如何虛擬化的關鍵!
進程的地址空間包含了運行進程的全部狀態。好比說,代碼總得保存在某個地方吧?因此代碼就在地址空間裏面。進程運行的時候,會用 stack 來保存當前函數調用鏈的位置,分配空間給局部變量,而且返回值。還有一部分,叫作 heap,被用來保存動態分配、用戶管理的內存,好比調用 malloc() 函數,或者 new 一個對象,都是從這裏面獲得須要的內存。固然,保存在地址空間的內容還有不少,目前而言咱們只須要關注於這三點就好了。
上圖中,有一個很小的地址空間(16kb)。程序的代碼保存在地址空間的最上方(0-1kb)。代碼是靜態的,不會增長也不會減小,能夠直接放在地址空間的最上部。還有兩部分大小可變的區域,那就是 heap 和 stack。上圖中,stack 和 heap 往兩個不一樣的方向增加。如進行 malloc()
申請內存時,heap 往下增加;當進行一個procedure call
時,stack 往上增加。固然這只是一個慣例,並無物理上的限制要求必定要這樣,你能夠以其餘方法從新組織地址空間。(事實上,當多個 threads 共存的時候,也不存在這麼優雅的地址空間劃分方法。)
咱們談地址空間的時候,咱們談的是操做系統提供給進程的抽象。上圖的進程並非在物理內存的0-16kb 位置,它能夠被存放在物理內存的任意位置。回顧一下圖13.2,你能夠看到每一個進程被加載到內存的不一樣位置。
當操做系統這麼作的時候,咱們說操做系統是在虛擬化內存,由於運行中的進程是覺得本身佔據了全部的內存的。然而現實很不同。
好比說,圖13.2的進程A想要加載地址0處的數據(這裏指的是虛擬內存),操做系統,具有特定的硬件支持,會把這個這個地址0映射到物理內存中另外的位置。這個就是內存虛擬化的核心,幾乎每個現代計算機系統都在試用這種方法。
虛擬內存系統的最主要目標是透明化
。也就是底層的物理地址對運行的程序不可見。因此,程序根本就不會意識到內存是虛擬化的,而是認爲本身有一個完整的私有物理地址。操做系統在幕後,承當着虛擬地址到物理地址的轉換工做。這極大地下降了程序開發者地複雜度!
第二個目標是高效率
。操做系統應該更可能讓抽象更加高效,包括時間上的和空間上的。好比,操做系統會依賴於一些硬件,好比 TLB 緩存,能夠縮短操做系統訪問用戶內存的時間。
最後一個目標是保護
。操做系統須要確保進程間是隔離的,一個進程不能隨意讀取另外一個進程的數據,尤爲是不能隨意在別人的地盤亂寫數據,否則會帶來不少不少不可預期的問題。
虛擬內存本質上就是一層抽象。操做系統給程序提供了一個簡單易用的接口,把虛擬內存映射成底層的物理內存,這中間的複雜度所有交給了操做系統,極大地下降了軟件開發的難度,同時下降了軟件出錯的可能性。
再次深入地體會到了那句名言:
計算機領域的一切難題均可以經過抽象解決。
個人公衆號:全棧不存在的