一、 虛擬地址的由來(lái)
在早期的計(jì)算機(jī)中,要運(yùn)行一個(gè)程序,會(huì)把這個(gè)程序全部都加載到內(nèi)存,程序是直接運(yùn)行到物理內(nèi)存上的。也就是說(shuō),程序運(yùn)行時(shí)直接訪問(wèn)的就是實(shí)際的物理內(nèi)存地址。當(dāng)計(jì)算機(jī)要運(yùn)行某些程序時(shí)(運(yùn)行中的程序稱為進(jìn)程),只要這些進(jìn)程所需要的內(nèi)存空間不超過(guò)計(jì)算機(jī)所擁有的物理內(nèi)存空間,那么就不會(huì)出問(wèn)題。由于程序都是直接訪問(wèn)物理內(nèi)存,所以一個(gè)進(jìn)程是可以隨意修改別的進(jìn)程的內(nèi)存數(shù)據(jù)。如果某個(gè)"惡意"的進(jìn)程修改了其它進(jìn)程的內(nèi)存數(shù)據(jù),往往就會(huì)導(dǎo)致系統(tǒng)奔潰。這種情況對(duì)我們來(lái)說(shuō)是無(wú)法容忍的,因?yàn)槲覀兿M褂糜?jì)算機(jī)的時(shí)候,任意一個(gè)任務(wù)出現(xiàn)問(wèn)題了,不要去影響其它任務(wù)的執(zhí)行。當(dāng)在使用不同順序去運(yùn)行多個(gè)任務(wù)時(shí),這些程序的運(yùn)行內(nèi)存地址是不確定的。假如當(dāng)時(shí)的計(jì)算機(jī)物理內(nèi)存空間有128MB,操作系統(tǒng)的運(yùn)行要78MB的物理內(nèi)存空間,那么就剩余50MB的物理內(nèi)存空間了,此時(shí)再運(yùn)行一個(gè)任務(wù)A需要30MB的物理內(nèi)存空間,若要再運(yùn)行一個(gè)任務(wù)B,那么任務(wù)B所要求的物理內(nèi)存空間不能超過(guò)20MB,否則就會(huì)出問(wèn)題,下一次先運(yùn)行任務(wù)B,再運(yùn)行任務(wù)A,此時(shí)它們的地址和上次是不一樣的。而且在程序運(yùn)行時(shí),如果去竄改程序上運(yùn)行的物理內(nèi)存的數(shù)據(jù)的,輕則程序掛掉,重則計(jì)算機(jī)系統(tǒng)奔潰。從上面可以看到,計(jì)算機(jī)的物理內(nèi)存使用率是比較低下的,而且也是不安全的,輕微的一個(gè)失誤都可能導(dǎo)致系統(tǒng)的奔潰,而且某個(gè)程序在運(yùn)行時(shí),并不需要運(yùn)行所有的功能,只是某一時(shí)段運(yùn)行某一功能。在程序的執(zhí)行過(guò)程中,也存在著大量在物理內(nèi)存和硬盤之間的數(shù)據(jù)交換過(guò)程
二、分段
為了解決上述一系列問(wèn)題,人們想到了一種有效的方法,增加一個(gè)程序與物理內(nèi)存之間的中間層,利用一種間接的地址訪問(wèn)方法去訪問(wèn)物理內(nèi)存。按照這種方法,程序中訪問(wèn)的內(nèi)存地址不再是實(shí)際的物理內(nèi)存地址,而是一個(gè)虛擬地址,然后由操作系統(tǒng)將這個(gè)虛擬地址映射到物理內(nèi)存地址上。這樣,只要操作系統(tǒng)處理好虛擬地址到物理內(nèi)存地址的映射關(guān)系,就可以保證不同的程序最終訪問(wèn)的內(nèi)存地址位于不同的區(qū)域,彼此不會(huì)存在地址重疊的現(xiàn)象,就可以達(dá)到物理內(nèi)存地址被隔離的效果,進(jìn)而保護(hù)計(jì)算機(jī)不會(huì)被"輕易"破壞。比如在一個(gè)32位的windows操作系統(tǒng)中,當(dāng)創(chuàng)建一個(gè)進(jìn)程時(shí),操作系統(tǒng)會(huì)為該進(jìn)程分配一個(gè)4GB大小的虛擬進(jìn)程地址空間。之所以是4GB,是因?yàn)樵?2位的操作系統(tǒng)中,一個(gè)指針長(zhǎng)度是4字節(jié),而4字節(jié)指針的尋址能力是從0x00000000~0xFFFFFFFF,最大值0xFFFFFFFF表示的即為4GB大小的容量。與虛擬地址空間相對(duì)的,還有一個(gè)物理地址空間,這個(gè)地址空間對(duì)應(yīng)的是真實(shí)的物理內(nèi)存。如果你的計(jì)算機(jī)上安裝了512MB大小的內(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)程只能訪問(wèn)自己虛擬地址空間中的數(shù)據(jù),無(wú)法訪問(wèn)別的進(jìn)程中的數(shù)據(jù),通過(guò)這種方法實(shí)現(xiàn)了進(jìn)程間的地址隔離。那是不是這4GB的虛擬地址空間應(yīng)用程序可以隨意使用呢?很遺憾,在Windows系統(tǒng)下,這個(gè)虛擬地址空間被分成了4部分:NULL指針區(qū)、用戶區(qū)、隔離區(qū)和內(nèi)核區(qū)。
1) NULL指針區(qū)(0x00000000~0x0000FFFF):如果進(jìn)程中的一個(gè)線程試圖操作這個(gè)分區(qū)中的數(shù)據(jù),CPU就會(huì)引發(fā)非法訪問(wèn)。它的作用是,當(dāng)調(diào)用 malloc 等內(nèi)存分配函數(shù)時(shí),如果無(wú)法找到足夠的內(nèi)存空間,它將返回 NULL。而不進(jìn)行安全性檢查。它只是假設(shè)地址分配成功,并開始訪問(wèn)內(nèi)存地址 0x00000000(NULL)。由于禁止訪問(wèn)內(nèi)存的這個(gè)分區(qū),因此會(huì)發(fā)生非法訪問(wèn)現(xiàn)象,并終止這個(gè)進(jìn)程的運(yùn)行。
2) 用戶區(qū)(0x00010000~0xBFFEFFFF):這個(gè)分區(qū)中存放進(jìn)程的私有地址空間。一個(gè)進(jìn)程無(wú)法以任何方式訪問(wèn)另外一個(gè)進(jìn)程駐留在這個(gè)分區(qū)中的數(shù)據(jù)(相同exe,通過(guò)copy-on-write來(lái)完成地址隔離)。(在windows中,所有.exe和動(dòng)態(tài)鏈接庫(kù)都載入到這一區(qū)域。系統(tǒng)同時(shí)會(huì)把該進(jìn)程可以訪問(wèn)的所有內(nèi)存映射文件映射到這一分區(qū))。
3) 隔離區(qū)(0xBFFF0000~0xBFFFFFFF):這個(gè)分區(qū)禁止進(jìn)入。任何試圖訪問(wèn)這個(gè)內(nèi)存分區(qū)的操作都是違規(guī)的。微軟保留這塊分區(qū)的目的是為了簡(jiǎn)化操作系統(tǒng)的現(xiàn)實(shí)。
4) 內(nèi)核區(qū)(0xC0000000~0xFFFFFFFF):這個(gè)分區(qū)存放操作系統(tǒng)駐留的代碼。線程調(diào)度、內(nèi)存管理、文件系統(tǒng)支持、網(wǎng)絡(luò)支持和所有設(shè)備驅(qū)動(dòng)程序代碼都在這個(gè)分區(qū)加載,該分區(qū)被所有進(jìn)程共享。
虛擬地址空間
由于程序的運(yùn)行必須運(yùn)行在真實(shí)的物理內(nèi)存上,而直接操作物理內(nèi)存的危險(xiǎn)性較大,所以通過(guò)創(chuàng)建了一個(gè)虛擬地址空間,虛擬地址和物理地址之間存在一一映射的關(guān)系,程序運(yùn)行地址和物理地址的隔離,確保不同的進(jìn)程地址空間被映射到不同的人物理地址空間上去。從而解決了同一計(jì)算機(jī)相同程序運(yùn)行地址不確定的問(wèn)題,也解決了因程序運(yùn)行時(shí)被其它程序修改物理內(nèi)存數(shù)據(jù)直接導(dǎo)致系統(tǒng)奔潰的弊病。但是物理內(nèi)存使用效率低下的問(wèn)題依然沒有得到解決。
三、分頁(yè)
為了解決物理內(nèi)存使用效率低下的問(wèn)題,于是人們又提出了分頁(yè)的辦法。分頁(yè)的基本方法是,將地址空間分成許多頁(yè),每頁(yè)的大小由CPU決定,然后由操作系統(tǒng)選擇執(zhí)行頁(yè)的多少。有些CPU頁(yè)的大小為4KB,也有些CPU頁(yè)的大小為4MB。假設(shè)頁(yè)大小為4KB,那么4GB的虛擬內(nèi)存空間共可分為4×1024×1024÷4=1048576頁(yè),如果此時(shí)計(jì)算機(jī)安裝的內(nèi)存條大小為512MB,那么512MB的物理內(nèi)存可分為512×1024÷4=131072頁(yè),顯然虛擬內(nèi)存地址空間的頁(yè)數(shù)要比物理內(nèi)存空間的頁(yè)數(shù)多得多。
在分段方法中,每次程序的運(yùn)行都會(huì)被全部加載到虛擬內(nèi)存中;而分頁(yè)方法則不同,它是將程序的大部分存在硬盤中,此時(shí)該部分硬盤稱為"交換區(qū)",而將少部分要運(yùn)行的加載到虛擬內(nèi)存中,通過(guò)映射在物理內(nèi)存中運(yùn)行,從而提高了物理內(nèi)存的使用率。
物理內(nèi)存與交換區(qū)
為了方便CPU高效執(zhí)行管理物理內(nèi)存,每一次都需要從虛擬內(nèi)存中拿一個(gè)頁(yè)的代碼放到物理內(nèi)存。虛擬內(nèi)存頁(yè)有三種狀態(tài),分別是未分配、已緩存和未緩存狀態(tài)。
未分配:指的是未被操作系統(tǒng)分配或者創(chuàng)建的,未分配的虛擬頁(yè)不存在任何數(shù)據(jù)和代碼與它們關(guān)聯(lián),因此不占用磁盤資源;
已緩存:表示的是物理內(nèi)存中已經(jīng)為該部分分配的,存在虛擬內(nèi)存和物理內(nèi)存映射關(guān)系的;
未緩存:指的是已經(jīng)加載到虛擬內(nèi)存中的,但是未在物理內(nèi)存中建立映射關(guān)系的。
四、頁(yè)表
虛擬內(nèi)存中的一些虛擬頁(yè)是要緩存在物理內(nèi)存中才能被執(zhí)行的,因此操作系統(tǒng)存在一種機(jī)制用來(lái)判斷某個(gè)虛擬頁(yè)是否被緩存在物理內(nèi)存中,還需要知道這個(gè)虛擬頁(yè)存放在磁盤上的哪個(gè)位置,從而在物理內(nèi)存中選擇空閑頁(yè)或者更新緩存頁(yè),并將需要的虛擬頁(yè)從磁盤復(fù)制到物理內(nèi)存中。這些功能是由軟硬件結(jié)合完成的,他存放在物理內(nèi)存中一個(gè)叫頁(yè)表的數(shù)據(jù)結(jié)構(gòu)中。頁(yè)表的結(jié)構(gòu)如下圖所示:
頁(yè)表實(shí)際上是一個(gè)數(shù)組。該數(shù)組存放的是一個(gè)稱為頁(yè)表?xiàng)l目(PTE)的結(jié)構(gòu)。虛擬地址空間的每一個(gè)頁(yè)在頁(yè)表中,都有一個(gè)對(duì)應(yīng)的頁(yè)表?xiàng)l目(PTE)。虛擬頁(yè)地址翻譯的時(shí)候就是查詢的各個(gè)虛擬頁(yè)在頁(yè)表中的PTE,從而進(jìn)行地址翻譯的。地址翻譯的過(guò)程如下所示:
假設(shè)每一個(gè)PTE都有一個(gè)有效位和一個(gè)n位字段的地址。其中有效位表示對(duì)應(yīng)的虛擬頁(yè)是否緩存在了物理內(nèi)存中。0表示未緩存。1表示已緩存。n位地址字段表示如果未緩存(有效字段為0),n位地址字段不為空的話,這個(gè)n位地址字段就表示該虛擬頁(yè)在磁盤上的起始的位置。如果這個(gè)n位字段為空,那么就說(shuō)明該虛擬頁(yè)未分配;如果已緩存(有效字段為1),n位地址字段則不為空,它表示該虛擬頁(yè)在物理內(nèi)存中的起始地址。
在上圖中,四個(gè)虛擬頁(yè)VP1 , VP2, VP4 , VP7 是被緩存在物理內(nèi)存中。 兩個(gè)虛擬頁(yè)VP0, VP5還未被分配。但是剩下的虛擬頁(yè)VP3 ,VP6已經(jīng)被分配了,但是還沒有緩存到物理內(nèi)存中去執(zhí)行。
五、內(nèi)存管理單元MMU(Memory Management Unit)
內(nèi)存管理單元MMU的主要功能是虛擬地址到物理地址的轉(zhuǎn)換。除此之外,它還可以實(shí)現(xiàn)內(nèi)存保護(hù)、緩存控制、總線仲裁以及存儲(chǔ)體切換。也就是說(shuō)程序運(yùn)行的過(guò)程所需要的物理內(nèi)存地址都是經(jīng)過(guò)一個(gè)叫內(nèi)存管理單元的東西完成的,需要注意的是內(nèi)存管理單元是硬件管理,而不是軟件實(shí)現(xiàn)內(nèi)存管理的。內(nèi)存管理單元使每一個(gè)程序都有自己獨(dú)立的虛擬地址空間,提高了物理內(nèi)存的使用率,它還提供了內(nèi)存保護(hù)功能,可以將特定的內(nèi)存塊設(shè)置為讀、寫或者可執(zhí)行屬性,可以防止被程序惡意竄改內(nèi)存。它的工作流程如下所示:
六、邏輯地址、線性地址、物理地址和虛擬地址的區(qū)別
1)邏輯地址(Logical Address):是指由程序產(chǎn)生的和分段相關(guān)的偏移地址部分。例如,你在進(jìn)行 C 語(yǔ)言指針編程中,能讀取指針變量本身值( &操作 ),實(shí)際上這個(gè)值就是邏輯地址,它是相對(duì)于你當(dāng)前進(jìn)程數(shù)據(jù)段的地址,不和絕對(duì)物理地址相干。只有在 Intel 實(shí)模式下,邏輯地址才和物理地址相等(因?yàn)閷?shí)模式?jīng)]有分段或分頁(yè)機(jī)制,CPU不進(jìn)行自動(dòng)地址轉(zhuǎn)換),邏輯地址也就是在Intel保護(hù)模式下,程序執(zhí)行代碼的偏移地址(假定代碼段、數(shù)據(jù)段如果完全相同)。程序員開發(fā)應(yīng)用程序時(shí)是需要和邏輯地址打交道,而不需要和分段和分頁(yè)機(jī)制打交道,分段分頁(yè)是系統(tǒng)編程人員要涉及的。程序員雖然能直接操作內(nèi)存,那也只能在操作系統(tǒng)給你分配的內(nèi)存段操作。
2)線性地址(Linear Address):是邏輯地址到物理地址變換之間的中間層。程序代碼會(huì)產(chǎn)生邏輯地址,或說(shuō)是段中的偏移地址,加上相應(yīng)段的基地址就生成了一個(gè)線性地址。如果啟用了分頁(yè)機(jī)制,那么線性地址能再經(jīng)變換以產(chǎn)生一個(gè)物理地址。若沒有啟用分頁(yè)機(jī)制,那么線性地址直接就是物理地址。Intel 80386 的線性地址空間容量為 4G(2的32次方即32根地址總線尋址)。
3)物理地址(Physical Address):是指出目前 CPU 外部地址總線上的尋址物理內(nèi)存的地址,是地址變換的最終結(jié)果地址,也就是計(jì)算機(jī)安裝的內(nèi)存條大小。如果啟用了分頁(yè)機(jī)制,那么線性地址會(huì)使用頁(yè)目錄和頁(yè)表中的項(xiàng)變換成物理地址。如果沒有啟用分頁(yè)機(jī)制,那么線性地址就直接成為物理地址了。
4)虛擬地址(Virtual Address):是指計(jì)算機(jī)呈現(xiàn)出要比實(shí)際擁有的內(nèi)存大得多的內(nèi)存量,每一個(gè)程序都用自己的虛擬內(nèi)存,而且一般和物理內(nèi)存大小一樣。因此允許程序員編寫并運(yùn)行比實(shí)際系統(tǒng)擁有的內(nèi)存大得多的程序。這使得許多大型項(xiàng)目也能夠在具有有限內(nèi)存資源的系統(tǒng)上實(shí)現(xiàn)。一個(gè)恰如其分的比喻是,你要從深圳去北京,你不必?fù)碛幸涣虚L(zhǎng)度像從深圳到北京距離的火車,只需要有兩段比火車稍長(zhǎng)的鐵軌就能完成這個(gè)任務(wù),在火車行駛時(shí),只要鋪鐵軌的動(dòng)作足夠快,就可以使用兩段鐵軌交替鋪在路上的辦法使火車從深圳行駛到北京,讓火車就像行駛在一條鐵路上一樣。這也就是虛擬內(nèi)存管理需要完成的任務(wù)。在linux 0.11內(nèi)核中,給每個(gè)進(jìn)程都劃分了總?cè)萘繛?4MB的虛擬內(nèi)存空間。因此程序的邏輯地址范圍是0x0000000到0x4000000。有時(shí)我們也把邏輯地址稱為虛擬地址。邏輯地址和物理地址的差值是0xC0000000,是由于虛擬地址->線性地址->物理地址映射正好差這個(gè)值。這個(gè)值是由操作系統(tǒng)指定的。邏輯地址(或稱為虛擬地址)到線性地址是由CPU的分段機(jī)制自動(dòng)轉(zhuǎn)換的。如果沒有開啟分頁(yè)管理,則線性地址就是物理地址。如果開啟了分頁(yè)管理,那么系統(tǒng)程序需要參和線性地址到物理地址的轉(zhuǎn)換過(guò)程。具體是通過(guò)設(shè)置頁(yè)目錄表和頁(yè)表項(xiàng)進(jìn)行的。