初期的顯存分配機制
在初期的計算機中,要運行一個程序,會把這種程序全都放入顯存,程序都是直接運行在顯存上的,也就是說程序中訪問的顯存地址都是實際的化學顯存地址。當計算機同時運行多個程序時,必須保證這種程序用到的顯存總額要大于計算機實際化學顯存的大小。
那當程序同時運行多個程序時,操作系統是怎樣為這種程序分配顯存的呢?下邊通過實例來說明當時的顯存分配方式:
某臺計算機總的顯存大小是128M,如今同時運行兩個程序A和B,A需占用顯存10M,B需占用顯存110。計算機在給程序分配顯存時會采取這樣的方式:先將顯存中的前10M分配給程序A,接著再從顯存中剩余的118M中界定出110M分配給程序B。這些分配方式可以保證程序A和程序B都能運行,并且這些簡單的顯存分配策略問題好多。
初期的顯存分配方式
問題1:進程地址空間不隔離。因為程序都是直接訪問數學顯存,所以惡意程序可以隨便更改別的進程的顯存數據,以達到破壞的目的。有些非惡意的,并且有bug的程序也可能不留神更改了其它程序的顯存數據,都會造成其它程序的運行出現異常。這些情況對用戶來說是難以容忍的,由于用戶希望使用計算機的時侯,其中一個任務失敗了,起碼不能影響其它的任務。
問題2:顯存使用效率低。在A和B都運行的情況下,假如用戶又運行了程序C,而程序C須要20M大小的顯存能夠運行,而此時系統只剩下8M的空間可供使用,所以此時系統必須在已運行的程序中選擇一個將該程序的數據暫時拷貝到硬碟上,釋放出部份空間來供程序C使用,之后再將程序C的數據全部倒入顯存中運行。可以想像得到,在這個過程中,有大量的數據在倒入裝出,造成效率非常低下。
問題3:程序運行的地址不確定。當顯存中的剩余空間可以滿足程序C的要求后,操作系統會在剩余空間中隨機分配一段連續的20M大小的空間給程序C使用,由于是隨機分配的,所以程序運行的地址是不確定的。
分段
為了解決上述問題,人們想到了一種變通的方式,就是降低一個中間層,借助一種間接的地址訪問方式訪問數學顯存。根據這些技巧,程序中訪問的顯存地址不再是實際的化學顯存地址,而是一個虛擬地址,之后由操作系統將這個虛擬地址映射到適當的數學顯存地址上。這樣,只要操作系統處理好虛擬地址到化學顯存地址的映射,就可以保證不同的程序最終訪問的顯存地址坐落不同的區域,彼此沒有重疊,就可以達到顯存地址空間隔離的療效。
當創建一個進程時,操作系統會為該進程分配一個4GB大小的虛擬進程地址空間。之所以是4GB,是由于在32位的操作系統中,一個表針寬度是4字節,而4字節表針的輪詢能力是從~,最大值表示的即為4GB大小的容量。與虛擬地址空間相對的什么是物理內存和虛擬內存,還有一個數學地址空間,這個地址空間對應的是真實的數學顯存。假如你的計算機上安裝了512M大小的顯存,這么這個數學地址空間表示的范圍是~。當操作系統做虛擬地址到化學地址映射時,只能映射到這一范圍,操作系統也只會映射到這一范圍。當進程創建時,每位進程就會有一個自己的4GB虛擬地址空間。要注意的是這個4GB的地址空間是“虛擬”的,并不是真實存在的,但是每位進程只能訪問自己虛擬地址空間中的數據,難以訪問別的進程中的數據,通過這些方式實現了進程間的地址隔離。那是不是這4GB的虛擬地址空間應用程序可以隨便使用呢?很遺憾,在系統下,這個虛擬地址空間被分成了4部份:NULL表針區、用戶區、64KB禁入區、內核區。
1)NULL表針區(~):假如進程中的一個線程企圖操作這個分區中的數據,CPU才會引起非法訪問。他的作用是,調用等顯存分配函數時,假若難以找到足夠的顯存空間,它將返回NULL。而不進行安全性檢測。它只是假定地址分配成功,并開始訪問顯存地址(NULL)。因為嚴禁訪問顯存的這個分區,因而會發生非法訪問現象,并中止這個進程的運行。
2)用戶模式分區(~):這個分區中儲存進程的私有地址空間。一個進程未能以任何方式訪問另外一個進程留駐在這個分區中的數據(相同exe,通過copy-on-write來完成地址隔離)。(在中,所有.exe和動態鏈接庫都載入到這一區域。系統同時會把該進程可以訪問的所有顯存映射文件映射到這一分區)。
2)隔離區(~):這個分區嚴禁步入。任何企圖訪問這個顯存分區的操作都是違法的。谷歌保留這塊分區的目的是為了簡化操作系統的現實。
3)內核區(~):這個分區儲存操作系統留駐的代碼。線程調度、內存管理、文件系統支持、網絡支持和所有設備驅動程序代碼都在這個分區加載。這個分區被所有進程共享。
應用程序能使用的只是用戶區而已,大概2GB左右(最大可以調整到3GB)。內核區為2GB,內核區保存的是系統線程調度、內存管理、設備驅動等數據,這部份數據供所有的進程共享,但應用程序是不能直接訪問的。
人們之所以要創建一個虛擬地址空間,目的是為了解決進程地址空間隔離的問題。但程序要想執行,必須運行在真實的顯存上,所以,必須在虛擬地址與數學地址間構建一種映射關系。這樣,通過映射機制,當程序訪問虛擬地址空間上的某個地址值時,就相當于訪問了化學地址空間中的另一個值。人們想到了一種分段()的方式,它的思想是在虛擬地址空間和化學地址空間之間做一一映射。例如說虛擬地址空間中某個10M大小的空間映射到化學地址空間中某個10M大小的空間。這些思想理解上去并不難,操作系統保證不同進程的地址空間被映射到化學地址空間中不同的區域上什么是物理內存和虛擬內存,這樣每位進程最終訪問到的。
化學地址空間都是彼此分開的。通過這些方法,就實現了進程間的地址隔離。還是以實例說明,假定有兩個進程A和B,進程A所需顯存大小為10M,其虛擬地址空間分布在到,進程B所需顯存為100M,其虛擬地址空間分布為到。這么根據分段的映射方式,進程A在數學顯存上映射區域為到,,進程B在數學顯存上映射區域為到。于是進程A和進程B分別被映射到了不同的顯存區間,彼此互不重疊,實現了地址隔離。從應用程序的角度看來,進程A的地址空間就是分布在到,在做開發時,開發人員只需訪問這段區間上的地址即可。應用程序并不關心進程A到底被映射到化學顯存的那塊區域上了,所以程序的運行地址也就是相當于說是確定的了。右圖顯示的是分段方法的顯存映射方式:
分段方法的顯存映射方式
這些分段的映射方式似乎解決了上述中的問題一和問題三,但并沒能解決問題二,即顯存的使用效率問題。在分段的映射方式中,每次換入換出顯存的都是整個程序,這樣會導致大量的c盤訪問操作,致使效率低下。所以這些映射方式還是稍顯粗糙,細度比較大。實際上,程序的運行有局部性特征,在某個時間段內,程序只是訪問程序的一小部分數據,也就是說,程序的大部份數據在一個時間段內都不會被用到。基于這些情況,人們想到了細度更小的顯存分割和映射方式,這些方式就是分頁()。
分頁
分頁的基本方式是,將地址空間分成許多的頁。每頁的大小由CPU決定,之后由操作系統選擇頁的大小。目前Inter系列的CPU支持4KB或4MB的頁大小,而PC上目前都選擇使用4KB。按這些選擇,4GB虛擬地址空間共可以分成頁,512M的數學顯存可以分為個頁。其實虛擬空間的頁數要比數學空間的頁數多得多。
在分段的方式中,每次程序運行時總是把程序全部倒入顯存,而分頁的方式則有所不同。分頁的思想是程序運行時用到哪頁就為哪頁分配顯存,沒用到的頁暫時保留在硬碟上。當用到這種頁時再在化學地址空間中為那些頁分配顯存,之后構建虛擬地址空間中的頁和剛分配的數學顯存頁間的映射。
下邊通過介紹一個可執行文件的裝載過程來說明分頁機制的實現方式。一個可執行文件(PE文件)似乎就是一些編譯鏈接好的數據和指令的集合,它也會被分成好多頁,在PE文件執行的過程中,它往顯存中裝載的單位就是頁。當一個PE文件被執行時,操作系統會先為該程序創建一個4GB的進程虛擬地址空間。上面介紹過,虛擬地址空間只是一個中間層而已,它的功能是借助一種映射機制將虛擬地址空間映射到化學地址空間,所以,創建4GB虛擬地址空間雖然并不是要真的創建空間,只是要創建那個映射機制所須要的數據結構而已,這些數據結構就是頁目和頁表。
當創建完虛擬地址空間所須要的數據結構后,進程開始讀取PE文件的第一頁。在PE文件的第一頁包含了PE文件頭和段表等信息,進程依照文件頭和段表等信息,將PE文件中所有的段一一映射到虛擬地址空間中相應的頁(PE文件中的段的寬度都是頁長的整數倍)。這時PE文件的真正指令和數據還沒有被放入顯存中,操作系統只是據PE文件的背部等信息構建了PE文件和進程虛擬地址空間中頁的映射關系而已。當CPU要訪問程序中用到的某個虛擬地址時,當CPU發覺該地址并沒有相相關聯的數學地址時,CPU覺得該虛擬地址所在的頁面是個空頁面,CPU會覺得這是個頁錯誤(PageFault),CPU也就曉得了操作系統還未給該PE頁面分配顯存,CPU會將控制權交還給操作系統。操作系統于是為該PE頁面在數學空間中分配一個頁面,之后再將這個數學頁面與虛擬空間中的虛擬頁面映射上去,之后將控制權再還給進程,進程從剛剛發生頁錯誤的位置重新開始執行。因為此時已為PE文件的那種頁面分配了顯存,所以就不會發生頁錯誤了。隨著程序的執行,頁錯誤會不斷地形成,操作系統也會為進程分配相應的數學頁面來滿足進程執行的需求。
分頁方式的核心思想就是當可執行文件執行到第x頁時,就為第x頁分配一個顯存頁y,之后再將這個顯存頁添加到進程虛擬地址空間的映射表中,這個映射表就相當于一個y=f(x)函數。應用程序通過這個映射表就可以訪問到x頁關聯的y頁了。
邏輯地址、線性地址、物理地址和虛擬地址的區別
邏輯地址()是指由程式形成的和段相關的偏斜地址部份。比如,你在進行C語言表針編程中,能讀取表針變量本身值(&操作),實際上這個值就是邏輯地址,他是相對于你當前進程數據段的地址,不和絕對化學地址相干。只有在Intel實模式下,邏輯地址才和化學地址相等(由于實模式沒有分段或分頁機制,cpu不進行手動地址轉換);邏輯也就是在Intel保護模式下程式執行代碼段限長內的偏斜地址(假設代碼段、數據段假如完全相同)。應用程式員僅需和邏輯地址打交道,而分段和分頁機制對你來說是完全透明的,僅由系統編程人員涉及。應用程式員其實自己能直接操作顯存,那也只能在操作系統給你分配的顯存段操作。
線性地址()是邏輯地址到化學地址變換之間的中間層。程式代碼會形成邏輯地址,或說是段中的偏斜地址,加上相應段的基地址就生成了一個線性地址。假如啟用了分頁機制,這么線性地址能再經變換以形成一個數學地址。若沒有啟用分頁機制,這么線性地址直接就是數學地址。Intel80386的線性地址空間容量為4G(2的32次方即32根地址總線輪詢)。
化學地址()是強調目前CPU外部地址總線上的輪詢化學顯存的地址訊號,是地址變換的最終結果地址。假如啟用了分頁機制,這么線性地址會使用頁目錄和頁表中的項變換成化學地址。假如沒有啟用分頁機制,這么線性地址就直接成為化學地址了。
虛擬顯存()是指計算機呈現出要比實際擁有的顯存大得多的顯存量。因而他準許程式員編制并運行比實際系統擁有的顯存大得多的程式。這促使許多小型項目也就能在具有有限顯存資源的系統上實現。一個特別恰當的比喻是:你何必特別長的軌道才能讓一列列車從北京開到天津。你只須要足夠長的鐵軌(例如說3公里)才能完成這個任務。采取的方式是把前面的鐵軌即時鋪到列車的后面,只要你的操作足夠快并能滿足需求,火車能夠象在一條完整的軌道上運行。這也就是虛擬顯存管理須要完成的任務。在.11內核中,給每位程式(進程)都界定了總容量為64MB的虛擬顯存空間。為此程式的邏輯地址范圍是到。有時我們也把邏輯地址稱為虛擬地址。由于和虛擬顯存空間的概念類似,邏輯地址也是和實際化學顯存容量無關的。邏輯地址和化學地址的“差距”是,是因為虛擬地址->線性地址->化學地址映射剛好差這個值。這個值是由操作系統指定的。機理邏輯地址(或稱為虛擬地址)到線性地址是由CPU的段機制手動轉換的。假如沒有開啟分頁管理,則線性地址就是數學地址。假如開啟了分頁管理,這么系統程式須要參和線性地址到化學地址的轉換過程。具體是通過設置頁目錄表和頁表項進行的。