物理地址與線性地址詳解

1. Linear Space & Physical Space
   

在硬件工程師和普通用戶看來,內存就是插在或固化在主板上的內存條,它們有一定的容量——比如64 MB。但在應用程序員眼中,並不過度關心插在主板上的內存容量,而是他們可以使用的內存空間——他們可以開發一個需要佔用1 GB內存的程序,並讓其在OS平臺上運行,哪怕這臺運行主機上只有128 MB的物理內存條。而對於OS開發者而言,則是介於二者之間,他們既需要知道物理內存的細節,也需要提供一套機制,爲應用程序員提供另一個內存空間,這個 內存空間的大小可以和實際的物理內存大小之間沒有任何關係。
   
我們將主板上的物理內存條所提供的內存空間定義爲物理內存空間;將應用程序員看到的內存空間定義爲線性空間。物理內存空間大小在不同的主機上可以是不一樣 的,隨着主板上所插的物理內存條的容量不同而不同;但爲應用程序員提供的線性空間卻是固定的,不會隨物理內存的變化而變化,這樣才能保證應用程序的可移植 性。儘管物理內存的大小可以影響應用程序運行的性能,並且很多情況下對物理內存的大小有一個最低要求,但這些因素只是爲了讓一個OS可以正常的運行。
   
線性空間的大小在32-bit平臺上爲4 GB的固定大小,對於每個進程都是這樣(一個應用可以是多進程的,在OS眼中,是以進程爲單位的)。也就是說線性空間不是進程共享的,而是進程隔離的,每 個進程都有相同大小的4 GB線性空間。一個進程對於某一個內存地址的訪問,與其它進程對於同一內存地址的訪問絕不衝突。比如,一個進程讀取線性空間地址1234ABCDh可以讀 出整數8,而另外一個進程讀取線性空間地址1234ABCDh可以讀出整數20,這取決於進程自身的邏輯。
   
在任意一個時刻,在一個CPU上只有一個進程在運行。所以對於此CPU來講,在這一時刻,整個系統只存在一個線性空間,這個線性空間是面向此進程的。當進 程發生切換的時候,線性空間也隨着切換。所以結論就是每個進程都有自己的線性空間,只有此進程運行的時候,其線性空間才被運行它的CPU所知。在其它時 刻,其線性空間對於CPU來說,是不可知的。所以儘管每個進程都可以有4 GB的線性空間,但在CPU眼中,只有一個線性空間的存在。線性空間的變化,隨着進程切換而變化。
   
儘管線性空間的大小和物理內存的大小之間沒有任何關係,但使用線性空間的應用程序最終還是要運行在物理內存中。應用所給出的任何線性地址最終必須被轉化爲 物理地址,才能夠真正的訪問物理內存。所以,線性內存空間必須被映射到物理內存空間中,這個映射關係需要通過使用硬件體系結構所規定的數據結構來建立。我 們不妨先稱其爲映射表。一個映射表的內容就是某個線性內存空間和物理內存空間之間的映射關係。OS Kernel一旦告訴某個CPU一個映射表的位置,那麼這個CPU需要去訪問一個線性空間地址時,就根據這張映射表的內容,將這個線性空間地址轉化爲物理 空間地址,並將此物理地址送到地址線,畢竟地址線只知道物理地址。
   
所以,我們很容易得出一個結論,如果我們給出不同的映射表,那麼CPU將某一線性空間地址轉化的物理地址也會不同。所以我們爲每一個進程都建立一張映射 表,將每個進程的線性空間根據自己的需要映射到物理空間上。既然某一時刻在某一CPU上只能有一個應用在運行,那麼當任務發生切換的時候,將映射表也更換 爲響應的映射表就可以實現每個進程都有自己的線性空間而互不影響。所以,在任意時刻,對於一個CPU來說,也只需要有一張映射表,以實現當前進程的線性空 間到物理空間的轉化。
 
--------------------------------------------------------------------------------
 
2. OS Kernel Space & Process Space
 
    
由於OS Kernel在任意時刻都必須存在於內存中,而進程卻可以切換,所以在任意時刻,內存中都存在兩部分,OS Kernel和用戶進程。而在任意時刻,對於一個CPU來說只存在一個線性空間,所以這個線性空間必須被分成兩部分,一部分供OS Kernel使用,另一部分供用戶進程使用。既然OS Kernel在任何時候都佔用線性空間中的一部分,那麼對於所有進程的線性空間而言,它們爲OS Kernel所留出的線性空間可以是完全相同的,也就是說,它們各自的映射表中,也分爲兩部分,一部分是進程私有映射部分,對於OS Kernel映射部分的內容則完全相同。
   
從這個意義上來說,我們可以認爲,對於所有的進程而言,它們共享OS Kernel所佔用的線性空間部分,而每個進程又各自有自己私有的線性空間部分。假如,我們將任意一個4 GB線性空間分割爲1 GB的OS Kernel空間部分和3 GB的進程空間部分,那麼所有進程的4 GB線性空間中1 GB的OS Kernel空間是共享的,而剩餘的3 GB進程空間部分則是各個進程私有的。Linux就是這麼做的,而Windows NT則是讓OS Kernel和進程各使用2 GB線性空間。
 
--------------------------------------------------------------------------------
 
3. Segment Mapping & Page Mapping
 
    
所有的線性空間的內容只有被放置到物理內存中才能夠被真正的運行和操作。所以,儘管OS Kernel和進程都被放在線性空間中,但它們最終必須被放置到物理內存中。所以OS Kernel和所有的進程都最終共享物理內存。在現階段,物理內存遠沒有線性空間那麼大——線性空間是4 GB,而物理內存空間往往只有幾百兆,甚至更小。另外即使物理內存有4 GB,但由於每個進程都可以有3 GB線性空間(假如進程私有線性空間是3 GB的話),如果把所有進程的線性空間內容都放在物理內存中,明顯是不現實的。所以OS Kernel必須將某些進程暫時用不到的數據或代碼放在物理內存之外,將有限的內存提供給當前最需要的進程。另外,由於OS Kernel在任何時候都有可能運行,所以OS Kernel最好被永遠放在物理內存中。我們僅僅將進程數據進行換入換出。
   
從線性空間到物理空間的映射需要映射表,映射表的內容是將某段線性空間映射到相同大小的物理內存空間上。從理論上,我們可以使用兩種映射方法:變長映射, 和定長映射。變長映射指的是根據不同的需要,將一個一個變長段映射到物理內存上,其格式可以如下(線性空間段起始地址,物理空間段起始地址,段長度)。假 如一個進程有3個段:10M的數據段,5M的代碼段,和8K的堆棧段,那麼就可以在映射表中建立3項內容,每一項針對一個段。這看起來沒有問題。但假如現 在我們的實際的內存只有32M,其中10M被內核佔用,留給進程的物理空間只有22M,那麼此進程在運行時,就佔據了10M+5M+8K的內存空間。隨後 當進程發生切換時,假如另一個進程和其有相同的內存要求,那麼剩餘的22M-(10M+5M+8K)明顯就不夠用了,這時只能將原進程的某些段換出,並且 必須是整段的換出。這就意味着我們必須至少換出一個10M的數據段,而換出的成本很高,因爲我們必須將這10M的內容拷貝到磁盤上,磁盤I/O是很慢的。
   
所以,使用變長的段映射的結果就是一個段要麼被全部換入,要麼被全部換出。但在現實中,一個程序中並非所有的代碼和數據都能夠被經常訪問,往往被經常訪問 的只佔全部代碼數據的一部分,甚至是一小部分。所以更有效的策略是我們最好只換出那些並不經常使用的部分,而保留那些經常被使用的部分。而不是整個段的換 入換出。這樣可以避免大塊的慢速磁盤操作。
   
這就是定長映射策略,我們將內存空間分割爲一個個定長塊,每個定長塊被稱爲一個頁。映射表的基本格式爲(物理空間頁起始地址),由於頁是定長的,所以不需 要指出它的長度,另外,我們不需要在映射表中指定線性地址,我們可以將線性地址作爲索引,到映射表中檢索出相應的物理地址。當使用頁時,其策略爲:當換出 的時候,我們只將那些不活躍的,也就是不經常使用的頁換出,而保留那些活躍的頁。在換入的時候,只有被請求訪問的頁才被換入,沒有被請求訪問的頁將永遠不 會被換入到物理內存。這就是請求頁(Demand Page)算法的核心思想。
   
這就引出一個頁大小的問題:首先我們不可能以字節爲單位,這樣映射表的大小和線性空間大小相同——假如整個線性空間都被映射的話——我們不可能將全部線性 空間用作存放這個映射表。由此,我們也可以得知,頁越小,則映射表的容量越大。而我們不能讓映射表佔用太多的空間。但如果頁太大,則面臨着和不定長段映射 同樣的問題,每次換出一個頁,都需要大量的磁盤操作。另外,由於爲一個進程分配內存的最小單位是頁,假如我們的頁大小爲4 MB,那麼即使一個進程只需要使用4 KB的內存,也不得不佔用整個4 MB頁,這明顯是一種很大的浪費。所以我們必須在兩者之間進行折衷,一般平臺所規定的頁大小爲1 KB到8 KB,IA-32所規定的頁大小爲4 KB。(IA-32也支持4 MB頁,你可以根據你的OS的用途進行選擇,一般都是使用4 KB頁)。
 
--------------------------------------------------------------------------------
 
4. Page Table
 
    
假如使用4 KB的頁,那麼對於4 GB的線性空間,則需要1,048,576個頁表實體,每個表項佔用4個字節,則需要4,194,304個字節。僅僅頁表就佔用4 MB空間,這是一個很大的需求。但如果確確實實一個進程需要使用全部線性空間的話,那麼這4 MB的頁表空間投入也是必要的。
   
但在現實中,很少有那個程序需要使用這麼大空間,一般的程序往往很小,從幾KB到幾MB,再使用這麼大的頁表就純粹是一種浪費。那我們該怎麼辦?
   
一種策略是建立變長頁表——我們只建立所需長度的頁表。但這種策略帶來很大的限制,並且仍然會造成比較大的空間浪費。由於頁表機制是使用線性地址作爲索 引,到頁表中進行檢索。那麼如果我們想讓OS Kernel使用C0000000h-FFFFFFFFh,也就是3 GB-4 GB之間的線性空間,那麼頁表的3 MB以上部分肯定被使用,那麼頁表還是不得不佔用大於3 MB空間的空間,即使這個進程僅僅使用1 KB的線性空間。除非我們把OS Kernel放在0h-3FFFFFFFh,也就是第一個1 GB線性空間。即使是這樣,我們的頁表也必須至少佔用1 MB的空間,儘管實際上我們的內核可能只有4 MB,只需要1024個表項,也就是4 KB表項空間——因爲進程私有的線性空間是從40000000h開始的。另外,對於共享庫而言,它們一般被放在物理內存的某個位置,同時映射到某個線性空 間位置,這個映射關係對所有的進程都是一致的。每個進程都將所需共享庫的映射關係放在自己的頁表中。爲了給用戶進程留出足夠的空間,共享庫一般被映射到較 高的線性空間,比如2 GB的位置。那麼頁表則至少需要2 MB以上的空間。總之,變長頁表無法真正解決頁表空間浪費的問題。
   
另外一種策略是使用多級頁表。我們以4 KB頁的二級頁表爲例來說明多級頁表的原理。
   
二級頁表的第一級稱爲頁目錄(Page Directory),第二級稱爲頁表(Page Table)。
   
如果使用一級頁表,則對於4 KB頁的來說,其起始地址都是以4 KB=2^12對齊的,所以一個線性地址的低12-bit用來做頁內尋址。高20-bit被用來做頁表索引。而如果使用2級頁表,則將其高20-bit分 爲兩部分,比如我們分爲2個10-bit,高位的10-bit用來做Page Directory的索引,用於定位Page Table;低位10-bit用來做Page Table的索引,用於定位Page;最低的12-bit用於定位頁內Offset。所以通過3部分的組合,一個32-bit的線性地址就最終轉化爲一個 32-bit的物理地址。
  
在整個2級頁表架構中,只存在一個Page Directory,由於頁目錄索引是10-bit,所以頁目錄中有2^10=1024個頁目錄表項(Directory Entry)。一個Directory Entry佔4個字節,所以頁目錄大小爲1024*4 = 4 KB,恰好被放在一個頁中。每一個Directory Entry指向一個頁表,所以最多有1024個Page Table,但並非所有Page Table都需要存在。每一個Page Table的索引也是10-bit,所以一個Page Table中有2^10=1024個頁表表項(Page-Table Entry);一個Page-Table Entry佔4個字節,所以一個Page Table大小爲1024*4 = 4 KB,也恰好被放在一個頁中。所以一個Directory Entry的高20-bit加上全爲0的低12-bit,就是其所指向的Page Table所在頁的起始地址。一個Page-Table Entry的高20-bit加上全爲0的低12-bit,就是其所指向的Page的起始地址。
 
這樣,當我們給出一個32-bit的線性地址時,首先取出高10-bit作爲Page Directory的索引,找到相應的Directory Entry,然後根據此Directory Entry的高20-bit找到相應的Page Table所在的Page,再根據線性地址的中間10-bit作爲索引,在此Page Table中找到相應的Page-Table Entry,然後根據此Page-Table Entry的高20-bit找到相應的Page,最後根據線性地址的低12-bit作爲Offset,加上Page Base Address就轉化成了32-bit的物理地址。
 
這就是2級頁表的線性地址到物理地址的映射機制。在2級頁表中,Page Directory佔用4 KB字節,這是2級頁表的最小內存需求。Page Table則根據實際的需要而創建。假如OS Kernel被放在3 GB的位置,大小爲4 MB,則只需要創建一個Page Table,然後將Page Directory的第768個Directory Entry設爲指向此Page Table所在的頁基地址。如果進程自身佔用4 MB,佔用線性地址0-4MB,則只需要再創建一個Page Table,然後將Page Directory的第0個Directory Entry設爲指向此Page Table所在的頁基地址。其它的線性地址沒有被使用,所以不需要再創建其它的Page Table,而將Page Directory中的其它Directory Entry都設爲空。這種情況下,頁表所佔據的空間就是4 KB*3=12 KB。而如果使用1級頁表,即使是變長的,也需要3M+4K。所以使用2級頁表很大的節省了頁表空間。
 
基於絕大多數程序都是幾百KB或一兩兆的事實,爲了更進一步的節省頁表空間,可以使用**或多級頁表。但這樣同時也造成對一個線性地址到物理地址的轉換層次過多。另外,對於大型程序來說,其佔用的頁表空間可能更多。所以,很少有系統使用3級以上頁表。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章