1. 虛擬內(nèi)存
在早期的計(jì)算機(jī)中,程序是直接運(yùn)行在物理內(nèi)存上的,程序在運(yùn)行時(shí)訪問的地址就是物理地址??墒?,當(dāng)計(jì)算機(jī)中同時(shí)運(yùn)行多個(gè)程序時(shí),就會(huì)有很多問題。
假設(shè)我們計(jì)算有128MB內(nèi)存,程序A需要10MB,程序B需要100MB,程序C需要20MB。如果我們需要同時(shí)運(yùn)行程序A和B,那么比較直接的做法是將內(nèi)存的前10MB分配給程序A,10MB~110MB分配給B。
但這樣做,地址空間不隔離,內(nèi)存使用效率低,程序運(yùn)行的地址不確定。
解決這個(gè)問題的辦法是增加中間層,使用虛擬地址。通過某些映射的方法,將虛擬地址轉(zhuǎn)換成實(shí)際的物理地址。
每個(gè)進(jìn)程都有自己獨(dú)立的虛擬地址空間,且每個(gè)進(jìn)程只能訪問自己的地址空間。
2. 分段
最開始人們使用的一種叫做分段的方法,把一段虛擬空間映射到某個(gè)物理地址空間。
比如程序A需要10MB內(nèi)存,我們首先假設(shè)虛擬地址空間為0x000000000x00A00000,然后從實(shí)際物理內(nèi)存中分配一個(gè)相同大小的物理空間0x001000000x00B00000,最后把這兩塊相同大小的地址空間一一映射。

但是分段的方法換入換出內(nèi)存的都是整個(gè)程序,會(huì)造成大量的磁盤操作,嚴(yán)重影響速度。
3. 分頁
程序的局部性原理:當(dāng)一個(gè)程序在運(yùn)行時(shí),在某個(gè)時(shí)間段內(nèi),它只是頻繁的用到一小部分?jǐn)?shù)據(jù)。
于是人們想到了分頁的方法。
基本思想是,把地址空間認(rèn)為的等分成固定大小的頁,由硬件決定支持多種大小的頁,操作系統(tǒng)選擇一個(gè)。
例如,如果虛擬空間有8頁,每頁大小由1KB,那么虛擬地址空間就是8KB。假設(shè)計(jì)算機(jī)有13條地址線,即擁有2^13的物理尋址能力,那么理論上物理空間可以多達(dá)8KB。假設(shè),只配備了6KB的內(nèi)存。
那么,我們就可以把常用的數(shù)據(jù)和代碼頁裝載到內(nèi)存中,把不常用的代碼和數(shù)據(jù)保存在磁盤里,當(dāng)需要的時(shí)候再把它從磁盤中讀取到內(nèi)存中即可。
我們把虛擬空間的頁叫做虛擬頁(Virtual Page),把物理內(nèi)存中的頁叫做物理頁(Physical Page),把磁盤中的頁叫做磁盤頁(Disk Page)。
如果進(jìn)程需要的頁不在內(nèi)存中時(shí),就會(huì)觸發(fā)頁錯(cuò)誤(Page Fault),硬件會(huì)捕捉到這個(gè)消息,由操作系統(tǒng)接管,把所需要的磁盤頁,裝入內(nèi)存中,并建立虛擬內(nèi)存與物理內(nèi)存的對(duì)應(yīng)關(guān)系。
4. 覆蓋裝入
程序執(zhí)行時(shí)所需要的指令和數(shù)據(jù)必須在內(nèi)存中才能正常運(yùn)行。最簡(jiǎn)單的辦法就是將程序運(yùn)行所需要的指令和數(shù)據(jù)全都裝入內(nèi)存中。但很多情況下,程序所需的內(nèi)存數(shù)量大于物理內(nèi)存數(shù)量,而且相對(duì)于磁盤來說,內(nèi)存是昂貴的,所以人們想盡各種辦法,盡可能的有效利用內(nèi)存。
后來研究發(fā)現(xiàn),程序運(yùn)行時(shí)有局部性原理,于是可以將程序最常用的部分駐留在內(nèi)存中,而將一些不太常用的數(shù)據(jù)存放在磁盤里,即動(dòng)態(tài)裝入。
覆蓋裝入(Overlay)和頁映射(Paging)是兩種很典型的動(dòng)態(tài)裝載方法。
覆蓋裝入在虛擬存儲(chǔ)發(fā)明之前使用比較廣泛,現(xiàn)在幾乎已經(jīng)被淘汰了。覆蓋裝入的方法把挖掘內(nèi)存潛力的任務(wù)交給了程序員,程序在編寫時(shí)必須手工分割成若干塊,然后編寫一個(gè)小的輔助代碼來管理這些模塊何時(shí)應(yīng)該駐留內(nèi)存,何時(shí)應(yīng)該被替換掉。這個(gè)小的輔助代碼就是所謂的覆蓋管理器(Overlay Manager)。
程序員需要手工將模塊按照它們之間的調(diào)用依賴關(guān)系組織成樹狀結(jié)構(gòu)。覆蓋管理器,保證某個(gè)模塊被調(diào)用時(shí),整個(gè)調(diào)用路徑上的模塊都在內(nèi)存中。
覆蓋裝入的速度比較慢,是典型的用時(shí)間換空間的方法。
5. 頁映射
與覆蓋裝入類似,頁映射也不是一下子就把程序的所有數(shù)據(jù)和指令都裝入內(nèi)存,而是將內(nèi)存和所有磁盤中的數(shù)據(jù)和指令按照頁(Page)為單位裝載和操作。
由于頁映射包含操縱系統(tǒng)對(duì)頁錯(cuò)誤的自動(dòng)處理,可執(zhí)行文件的裝載和執(zhí)行就簡(jiǎn)化了,
(1)創(chuàng)建一個(gè)具有獨(dú)立虛擬地址空間的進(jìn)程
實(shí)際上并沒有分配空間,而是創(chuàng)建一個(gè)頁映射函數(shù),將虛擬地址頁映射到物理地址頁。這些映射關(guān)系也可以等到后面程序發(fā)生頁錯(cuò)誤的時(shí)候再進(jìn)行設(shè)置。
(2)讀取可執(zhí)行文件頭,建立虛擬地址空間與可執(zhí)行文件的映射關(guān)系
這一步所作的是虛擬空間與可執(zhí)行文件的映射關(guān)系。當(dāng)程序執(zhí)行發(fā)生頁錯(cuò)誤時(shí),操作系統(tǒng)將從物理內(nèi)存中分配一個(gè)物理頁,然后將該“缺頁”從磁盤中讀取到內(nèi)存中,再設(shè)置缺頁的虛擬頁和物理頁的映射關(guān)系,這樣程序才得以正常運(yùn)行。
所以,當(dāng)操作系統(tǒng)捕獲到缺頁錯(cuò)誤時(shí),它必須知道程序當(dāng)前所需要的頁在可執(zhí)行文件中的哪一個(gè)位置,這就是虛擬空間與可執(zhí)行文件之間的映射關(guān)系。
與可執(zhí)行文件各個(gè)段對(duì)應(yīng)的,Linux中將虛擬空間劃分為了相應(yīng)的段,成為虛擬內(nèi)存區(qū)域(VMA),在Windows中叫做虛擬段(Virtual Section)。
例如:代碼VMA,數(shù)據(jù)VMA,堆VMA,棧VMA
(3)將CPU的指令寄存器設(shè)置成可執(zhí)行文件的入口地址,啟動(dòng)運(yùn)行
操作系統(tǒng)通過設(shè)置CPU的指令寄存器將控制權(quán)轉(zhuǎn)交給進(jìn)程,由此進(jìn)程開始執(zhí)行,這個(gè)入口地址一般是代碼段VMA的起始地址。
注:
當(dāng)CPU打算執(zhí)行這個(gè)地址的指令的時(shí)候,發(fā)現(xiàn)此頁面是一個(gè)空白頁,于是它就認(rèn)為這是一個(gè)頁錯(cuò)誤,CPU將控制權(quán)交給操作系統(tǒng),操作系統(tǒng)有專門的頁錯(cuò)誤處理例程來處理這種情況,操作系統(tǒng)查找虛擬頁與可執(zhí)行文件頁之間的映射關(guān)系,計(jì)算出相應(yīng)頁面在可執(zhí)行文件中的偏移,然后在物理內(nèi)存中分配一個(gè)物理頁面,將虛擬頁與分配的物理頁建立映射,再把控制權(quán)返還給進(jìn)程,進(jìn)程從剛才頁錯(cuò)誤的位置重新開始執(zhí)行。