
原文作者:Cooci_和諧學(xué)習(xí)_不急不躁
https://juejin.im/post/5ccd6bb3e51d453a5604c42e
在早期的計算機中,程序是直接運行在物理內(nèi)存上的,也就是說:程序在運行時訪問的地址就是物理地址。這樣也就是單運行的時候沒有什么問題!可是,計算機會有多到程序、分時系統(tǒng)和多任務(wù),當(dāng)我們能夠同時運行多個程序時,CPU的利用率將會比較高。那么有一個非常嚴(yán)重的問題:如何將計算機的有限的物理內(nèi)存分配給多個程序使用
假設(shè)我們計算有128MB內(nèi)存,程序A需要10MB,程序B需要100MB,程序C需要20MB。如果我們需要同時運行程序A和B,那么比較直接的做法是將內(nèi)存的前10MB分配給程序A,10MB~110MB分配給B。

但這樣做,會造成以下問題:
當(dāng)多個程序需要運行時,必須保證這些程序用到的內(nèi)存總量要小于計算機實際的物理內(nèi)存的大小。
進程地址空間不隔離,由于程序是直接訪問物理內(nèi)存的,所以每一個進程都可以修改其他進程的內(nèi)存數(shù)據(jù),設(shè)置修改內(nèi)核地址空間中的數(shù)據(jù),所以有些惡意程序可以隨意修改別的進程,就會造成一些破壞
內(nèi)存使用效率低 內(nèi)存空間不足,就需要將其他程序展示拷貝到硬盤當(dāng)中,然后將新的程序裝入內(nèi)存。然而由于大量的數(shù)據(jù)裝入裝出,內(nèi)存的使用效率會非常低
程序運行的地址不確定;因為內(nèi)存地址是隨機分配的,所以程序運行的地址也是不正確的
解決這幾個問題的思路就是使用我們非常牛逼的方法:增加中間層 - 即使用一種間接的地址訪問方式。
把程序給出的地址看做是一種虛擬地址,然后通過某種映射,將這個虛擬地址轉(zhuǎn)化到實際的物理地址。這樣,只需要控制好映射過程,就能保證程序所能訪問的物理內(nèi)存區(qū)域跟別的程序不重疊,達到空間隔離的效果。
隔離
普通的程序它只需要一個簡單的執(zhí)行環(huán)境,一個單一的地址空間,有自己的CPU。
地址空間比較抽象,如果把它想象成一個數(shù)組,每一個數(shù)組是一字節(jié),數(shù)組大小就是地址空間的長度,那么32位的地址空間大小就是2^32=4294967296字節(jié),即4G,地址空間有效位是0x00000000~0xFFFFFFFF。
地址空間分為兩種:
物理空間:就是物理內(nèi)存。32位的機器,地址線就有32條,物理空間4G,但如果只裝有512M的內(nèi)存,那么實際有效的空間地址就是0x00000000~0x1FFFFFFF,其他部分都是無效的。
虛擬空間:每個進程都有自己獨立的虛擬空間,而且每個進程只能訪問自己的空間地址,這樣就有效的做到了進程隔離。
分段
基本思路: 把一段與程序所需要的內(nèi)存空間大小的虛擬空間映射到某個地址空間。虛擬空間的每個字節(jié)對應(yīng)物理空間的每個字節(jié)。這個映射過程由軟件來完成。
比如A需要10M,就假設(shè)有0x00000000 到0x00A00000大小的虛擬空間,然后從物理內(nèi)存分配一個相同大小的空間,比如是0x00100000到0x00B00000。操作系統(tǒng)來設(shè)置這個映射函數(shù),實際的地址轉(zhuǎn)換由硬件完成。如果越界,硬件就會判斷這是一個非法訪問,拒絕這個地址請求,并上報操作系統(tǒng)或監(jiān)控程序。

這樣一來利用:分段的方式可以解決之前的個(地址空間不隔離)和第三個問題(程序運行地址不確定)
首先做到了地址隔離,因為A和B被映射到了兩塊不同的物理空間,它們之間沒有任何重疊,如果A訪問虛擬空間的地址超過了0x00A00000這個范圍,硬件就會判斷這是一個非法的訪問,并將這個請求報告給操作系統(tǒng)或者監(jiān)控程序,由它決定如何處理。
再者,對于每個程序來說,無論它們被分配到地址空間的哪一個區(qū)域,對于程序來說都是透明的,它們不需要關(guān)心物理地址的變化,它們只要按照從地址0x00000000到0x00A00000來編寫程序、放置變量,所以程序不需要重定位。
第二問題內(nèi)存使用效率問題依舊沒有解決。
但是分段的方法沒有解決內(nèi)存使用效率的問題。分段對于內(nèi)存區(qū)域的映射還是按照程序為單位,如果內(nèi)存不足,被換入換出的磁盤的都是整個程序,這樣勢必會造成大量的磁盤訪問操作,從而嚴(yán)重影響速度,這種方法還是顯得粗糙,粒度比較大。事實上根據(jù)程序的局部性原理,當(dāng)一個程序正在運行時,在某個時間段內(nèi),它只是頻繁用到了一小部分數(shù)據(jù),也就是說,程序的很多數(shù)據(jù)其實在一個時間段內(nèi)是不會被用到的。人們很自然地想到了更小粒度的內(nèi)存分割和映射方法,使得程序的局部性原理得到充分利用,大大提高了內(nèi)存的使用率。這種方法就是分頁。
分頁
分頁的基本方法是把地址空間人為得等分成固定大小的頁,每一個頁的大小由硬件決定,或硬件支持多種頁的大小,由操作系統(tǒng)選擇決定頁的大小。 目前幾乎所有PC的操作系統(tǒng)都是用4KB大小的頁。我們使用的PC機是32位虛擬地址空間,也就是4GB,按4KB分頁,總共有1048576個頁。
那么,當(dāng)我們把進程的虛擬地址空間按頁分割,把常用的數(shù)據(jù)和代碼裝載到內(nèi)存中,把不常用的代碼和數(shù)據(jù)保存在磁盤里,當(dāng)需要用到的時候再把它們從磁盤里取出即可。圖中的線表示映射關(guān)系,我們可以看到虛擬空間有些頁被映射到同一個物理頁,這樣就可以實現(xiàn)內(nèi)存共享。
虛擬頁,物理頁,磁盤頁根據(jù)內(nèi)存空間不一樣而區(qū)分
我們可以看到Process 1 的VP2和VP3不在內(nèi)存中,但是當(dāng)進程需要用到這兩個頁的時候,硬件就會捕獲到這個消息,就是所謂的頁錯誤(Page Fault),然后操作系統(tǒng)接管進程,負責(zé)將VP2和VP3從磁盤讀取出來裝入內(nèi)存,然都將內(nèi)存中的這兩個頁和VP2和VP3建立映射關(guān)系。以頁為單位存取和交換數(shù)據(jù)非常方便,硬件本身就支持這種以頁為單位的操作方式。

保護頁也是頁映射的目的之一,簡單地說就是每個頁可以設(shè)置權(quán)限屬性,誰可以修改,誰可以訪問,而且只有操作系統(tǒng)有權(quán)修改這些屬性,那么操作系統(tǒng)就可以做到保護自己和保護進程。
-
虛擬存儲的實現(xiàn)需要硬件支持,幾乎所有CPU都采用稱為MMU的部件來進行頁的映射:
在頁映射模式下,CPU發(fā)出的是Virtual Address,即我們程序看到的是虛擬地址。經(jīng)過MMU轉(zhuǎn)換以后就變成了Physical Address。一般MMU集成在CPU內(nèi)部,不會以獨立的部件存在。
