對(duì)進(jìn)程地址空間的一點(diǎn)認(rèn)識(shí) 在進(jìn)入正題前先來談?wù)劜僮飨到y(tǒng)內(nèi)存管理機(jī)制的發(fā)展歷程,了解這些有利于我們更好的理解目前操作系統(tǒng)的內(nèi)存管理機(jī)制。 一 早期的內(nèi)存分配機(jī)制 在 早期的計(jì)算機(jī)中,要運(yùn)行一個(gè)程序,會(huì)把這些程序全都裝入內(nèi)存,程序都是直接運(yùn)行在內(nèi)存上的,也就是說程序中訪問的內(nèi)存地址都是實(shí)際的物理內(nèi)存地址。當(dāng)計(jì)算 機(jī)同時(shí)運(yùn)行多個(gè)程序時(shí),必須保證這些程序用到的內(nèi)存總量要小于計(jì)算機(jī)實(shí)際物理內(nèi)存的大小。那當(dāng)程序同時(shí)運(yùn)行多個(gè)程序時(shí),操作系統(tǒng)是如何為這些程序分配內(nèi)存 的呢?下面通過實(shí)例來說明當(dāng)時(shí)的內(nèi)存分配方法: 某臺(tái)計(jì)算機(jī)總的內(nèi)存大小是128M,現(xiàn)在同時(shí)運(yùn)行兩個(gè)程序A和B,A需占用內(nèi)存10M,B需占用內(nèi)存110。計(jì)算機(jī)在給程序分配內(nèi)存時(shí)會(huì)采取這樣的方法:先將內(nèi)存中的前10M分配給程序A,接著再?gòu)膬?nèi)存中剩余的118M中劃分出110M分配給程序B。這種分配方法可以保證程序A和程序B都能運(yùn)行,但是這種簡(jiǎn)單的內(nèi)存分配策略問題很多。
圖一 早期的內(nèi)存分配方法 問題1:進(jìn)程地址空間不隔離。由于程序都是直接訪問物理內(nèi)存,所以惡意程序可以隨意修改別的進(jìn)程的內(nèi)存數(shù)據(jù),以達(dá)到破壞的目的。有些非惡意的,但是有bug的程序也可能不小心修改了其它程序的內(nèi)存數(shù)據(jù),就會(huì)導(dǎo)致其它程序的運(yùn)行出現(xiàn)異常。這種情況對(duì)用戶來說是無法容忍的,因?yàn)橛脩粝M褂糜?jì)算機(jī)的時(shí)候,其中一個(gè)任務(wù)失敗了,至少不能影響其它的任務(wù)。 問題2:內(nèi)存使用效率低。在A和B都運(yùn)行的情況下,如果用戶又運(yùn)行了程序C,而程序C需要20M大小的內(nèi)存才能運(yùn)行,而此時(shí)系統(tǒng)只剩下8M的空間可供使用,所以此時(shí)系統(tǒng)必須在已運(yùn)行的程序中選擇一個(gè)將該程序的數(shù)據(jù)暫時(shí)拷貝到硬盤上,釋放出部分空間來供程序C使用,然后再將程序C的數(shù)據(jù)全部裝入內(nèi)存中運(yùn)行??梢韵胂蟮玫?,在這個(gè)過程中,有大量的數(shù)據(jù)在裝入裝出,導(dǎo)致效率十分低下。 問題3:程序運(yùn)行的地址不確定。當(dāng)內(nèi)存中的剩余空間可以滿足程序C的要求后,操作系統(tǒng)會(huì)在剩余空間中隨機(jī)分配一段連續(xù)的20M大小的空間給程序C使用,因?yàn)槭请S機(jī)分配的,所以程序運(yùn)行的地址是不確定的。 二 分段 為 了解決上述問題,人們想到了一種變通的方法,就是增加一個(gè)中間層,利用一種間接的地址訪問方法訪問物理內(nèi)存。按照這種方法,程序中訪問的內(nèi)存地址不再是實(shí) 際的物理內(nèi)存地址,而是一個(gè)虛擬地址,然后由操作系統(tǒng)將這個(gè)虛擬地址映射到適當(dāng)?shù)奈锢韮?nèi)存地址上。這樣,只要操作系統(tǒng)處理好虛擬地址到物理內(nèi)存地址的映 射,就可以保證不同的程序最終訪問的內(nèi)存地址位于不同的區(qū)域,彼此沒有重疊,就可以達(dá)到內(nèi)存地址空間隔離的效果。 當(dāng)創(chuàng)建一個(gè)進(jìn)程時(shí),操作系統(tǒng)會(huì)為該進(jìn)程分配一個(gè)4GB大小的虛擬進(jìn)程地址空間。之所以是4GB,是因?yàn)樵?/span>32位的操作系統(tǒng)中,一個(gè)指針長(zhǎng)度是4字節(jié),而4字節(jié)指針的尋址能力是從0x00000000~0xFFFFFFFF,最大值0xFFFFFFFF表示的即為4GB大小的容量。與虛擬地址空間相對(duì)的,還有一個(gè)物理地址空間,這個(gè)地址空間對(duì)應(yīng)的是真實(shí)的物理內(nèi)存。如果你的計(jì)算機(jī)上安裝了512M大小的內(nèi)存,那么這個(gè)物理地址空間表示的范圍是0x00000000~0x1FFFFFFF。當(dāng)操作系統(tǒng)做虛擬地址到物理地址映射時(shí),只能映射到這一范圍,操作系統(tǒng)也只會(huì)映射到這一范圍。當(dāng)進(jìn)程創(chuàng)建時(shí),每個(gè)進(jìn)程都會(huì)有一個(gè)自己的4GB虛擬地址空間。要注意的是這個(gè)4GB的地址空間是“虛擬”的,并不是真實(shí)存在的,而且每個(gè)進(jìn)程只能訪問自己虛擬地址空間中的數(shù)據(jù),無法訪問別的進(jìn)程中的數(shù)據(jù),通過這種方法實(shí)現(xiàn)了進(jìn)程間的地址隔離。那是不是這4GB的虛擬地址空間應(yīng)用程序可以隨意使用呢?很遺憾,在Windows系統(tǒng)下,這個(gè)虛擬地址空間被分成了4部分:NULL指針區(qū)、用戶區(qū)、64KB禁入?yún)^(qū)、內(nèi)核區(qū)。應(yīng)用程序能使用的只是用戶區(qū)而已,大約2GB左右(最大可以調(diào)整到3GB)。內(nèi)核區(qū)為2GB,內(nèi)核區(qū)保存的是系統(tǒng)線程調(diào)度、內(nèi)存管理、設(shè)備驅(qū)動(dòng)等數(shù)據(jù),這部分?jǐn)?shù)據(jù)供所有的進(jìn)程共享,但應(yīng)用程序是不能直接訪問的。 人 們之所以要?jiǎng)?chuàng)建一個(gè)虛擬地址空間,目的是為了解決進(jìn)程地址空間隔離的問題。但程序要想執(zhí)行,必須運(yùn)行在真實(shí)的內(nèi)存上,所以,必須在虛擬地址與物理地址間建 立一種映射關(guān)系。這樣,通過映射機(jī)制,當(dāng)程序訪問虛擬地址空間上的某個(gè)地址值時(shí),就相當(dāng)于訪問了物理地址空間中的另一個(gè)值。人們想到了一種分段(Sagmentation)的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。比如說虛擬地址空間中某個(gè)10M大小的空間映射到物理地址空間中某個(gè)10M大小的空間。這種思想理解起來并不難,操作系統(tǒng)保證不同進(jìn)程的地址空間被映射到物理地址空間中不同的區(qū)域上,這樣每個(gè)進(jìn)程最終訪問到的 物理地址空間都是彼此分開的。通過這種方式,就實(shí)現(xiàn)了進(jìn)程間的地址隔離。還是以實(shí)例說明,假設(shè)有兩個(gè)進(jìn)程A和B,進(jìn)程A所需內(nèi)存大小為10M,其虛擬地址空間分布在0x00000000到0x00A00000,進(jìn)程B所需內(nèi)存為100M,其虛擬地址空間分布為0x00000000到0x06400000。那么按照分段的映射方法,進(jìn)程A在物理內(nèi)存上映射區(qū)域?yàn)?/span>0x00100000到0x00B00000,,進(jìn)程B在物理內(nèi)存上映射區(qū)域?yàn)?/span>0x00C00000到0x07000000。于是進(jìn)程A和進(jìn)程B分別被映射到了不同的內(nèi)存區(qū)間,彼此互不重疊,實(shí)現(xiàn)了地址隔離。從應(yīng)用程序的角度看來,進(jìn)程A的地址空間就是分布在0x00000000到0x00A00000,在做開發(fā)時(shí),開發(fā)人員只需訪問這段區(qū)間上的地址即可。應(yīng)用程序并不關(guān)心進(jìn)程A究竟被映射到物理內(nèi)存的那塊區(qū)域上了,所以程序的運(yùn)行地址也就是相當(dāng)于說是確定的了。 圖二顯示的是分段方式 的內(nèi)存映射方法。
圖二 分段方式的內(nèi)存映射方法 這 種分段的映射方法雖然解決了上述中的問題一和問題三,但并沒能解決問題二,即內(nèi)存的使用效率問題。在分段的映射方法中,每次換入換出內(nèi)存的都是整個(gè)程序, 這樣會(huì)造成大量的磁盤訪問操作,導(dǎo)致效率低下。所以這種映射方法還是稍顯粗糙,粒度比較大。實(shí)際上,程序的運(yùn)行有局部性特點(diǎn),在某個(gè)時(shí)間段內(nèi),程序只是訪 問程序的一小部分?jǐn)?shù)據(jù),也就是說,程序的大部分?jǐn)?shù)據(jù)在一個(gè)時(shí)間段內(nèi)都不會(huì)被用到。基于這種情況,人們想到了粒度更小的內(nèi)存分割和映射方法,這種方法就是分頁(Paging)。 三 分頁 分頁的基本方法是,將地址空間分成許多的頁。每頁的大小由CPU決定,然后由操作系統(tǒng)選擇頁的大小。目前Inter系列的CPU支持4KB或4MB的頁大小,而PC上目前都選擇使用4KB。按這種選擇,4GB虛擬地址空間共可以分成1048576個(gè)頁,512M的物理內(nèi)存可以分為131072個(gè)頁。顯然虛擬空間的頁數(shù)要比物理空間的頁數(shù)多得多。 在 分段的方法中,每次程序運(yùn)行時(shí)總是把程序全部裝入內(nèi)存,而分頁的方法則有所不同。分頁的思想是程序運(yùn)行時(shí)用到哪頁就為哪頁分配內(nèi)存,沒用到的頁暫時(shí)保留在 硬盤上。當(dāng)用到這些頁時(shí)再在物理地址空間中為這些頁分配內(nèi)存,然后建立虛擬地址空間中的頁和剛分配的物理內(nèi)存頁間的映射。 下面通過介紹一個(gè)可執(zhí)行文件的裝載過程來說明分頁機(jī)制的實(shí)現(xiàn)方法。一個(gè)可執(zhí)行文件(PE文件)其實(shí)就是一些編譯鏈接好的數(shù)據(jù)和指令的集合,它也會(huì)被分成很多頁,在PE文件執(zhí)行的過程中,它往內(nèi)存中裝載的單位就是頁。當(dāng)一個(gè)PE文件被執(zhí)行時(shí),操作系統(tǒng)會(huì)先為該程序創(chuàng)建一個(gè)4GB的進(jìn)程虛擬地址空間。前面介紹過,虛擬地址空間只是一個(gè)中間層而已,它的功能是利用一種映射機(jī)制將虛擬地址空間映射到物理地址空間,所以,創(chuàng)建4GB虛擬地址空間其實(shí)并不是要真的創(chuàng)建空間,只是要?jiǎng)?chuàng)建那種映射機(jī)制所需要的數(shù)據(jù)結(jié)構(gòu)而已,這種數(shù)據(jù)結(jié)構(gòu)就是頁目和頁表。 當(dāng)創(chuàng)建完虛擬地址空間所需要的數(shù)據(jù)結(jié)構(gòu)后,進(jìn)程開始讀取PE文件的第一頁。在PE文件的第一頁包含了PE文件頭和段表等信息,進(jìn)程根據(jù)文件頭和段表等信息,將PE文件中所有的段一一映射到虛擬地址空間中相應(yīng)的頁(PE文件中的段的長(zhǎng)度都是頁長(zhǎng)的整數(shù)倍)。這時(shí)PE文件的真正指令和數(shù)據(jù)還沒有被裝入內(nèi)存中,操作系統(tǒng)只是根據(jù)PE文件的頭部等信息建立了PE文件和進(jìn)程虛擬地址空間中頁的映射關(guān)系而已。當(dāng)CPU要訪問程序中用到的某個(gè)虛擬地址時(shí),當(dāng)CPU發(fā)現(xiàn)該地址并沒有相相關(guān)聯(lián)的物理地址時(shí),CPU認(rèn)為該虛擬地址所在的頁面是個(gè)空頁面,CPU會(huì)認(rèn)為這是個(gè)頁錯(cuò)誤(Page Fault),CPU也就知道了操作系統(tǒng)還未給該PE頁面分配內(nèi)存,CPU會(huì)將控制權(quán)交還給操作系統(tǒng)。操作系統(tǒng)于是為該PE頁面在物理空間中分配一個(gè)頁面,然后再將這個(gè)物理頁面與虛擬空間中的虛擬頁面映射起來,然后將控制權(quán)再還給進(jìn)程,進(jìn)程從剛才發(fā)生頁錯(cuò)誤的位置重新開始執(zhí)行。由于此時(shí)已為PE文件的那個(gè)頁面分配了內(nèi)存,所以就不會(huì)發(fā)生頁錯(cuò)誤了。隨著程序的執(zhí)行,頁錯(cuò)誤會(huì)不斷地產(chǎn)生,操作系統(tǒng)也會(huì)為進(jìn)程分配相應(yīng)的物理頁面來滿足進(jìn)程執(zhí)行的需求。 分頁方法的核心思想就是當(dāng)可執(zhí)行文件執(zhí)行到第x頁時(shí),就為第x頁分配一個(gè)內(nèi)存頁y,然后再將這個(gè)內(nèi)存頁添加到進(jìn)程虛擬地址空間的映射表中,這個(gè)映射表就相當(dāng)于一個(gè)y=f(x)函數(shù)。應(yīng)用程序通過這個(gè)映射表就可以訪問到x頁關(guān)聯(lián)的y頁了。 |
|