1. 程式人生 > >虛擬地址、邏輯地址、線性地址、實體地址

虛擬地址、邏輯地址、線性地址、實體地址

1. 兩個記憶體概念

實體記憶體:人盡皆知,就是插在主機板上的記憶體條。他是固定的,記憶體條的容量多大,實體記憶體就有多大(整合顯示卡系統除外)。但是如果程式執行很多或者程式本身很大的話,就會導致大量的實體記憶體佔用,甚至導致實體記憶體消耗殆盡。
虛擬記憶體:簡明的說,虛擬記憶體就是在硬碟上劃分一塊頁面檔案,充當記憶體。當程式在執行時,有一部分資源還沒有用上或者同時開啟幾個程式卻只操作其中一個程式時,系統沒必要將程式所有的資源都塞在實體記憶體中,於是,系統將這些暫時不用的資源放在虛擬記憶體上,等到需要時在調出來用。
2.三個地址概念
實體地址(physical address):用於記憶體晶片級的單元定址
,與處理器和CPU連線的地址匯流排相對應。

——這個概念應該是這幾個概念中最好理解的一個,但是值得一提的是,雖然可以直接把實體地址理解成插在機器上那根記憶體本身,把記憶體看成一個從0位元組一直到 最大空量逐位元組的編號的大陣列,然後把這個陣列叫做實體地址,但是事實上,這只是一個硬體提供給軟體的抽像,記憶體的定址方式並不是這樣。所以,說它是“與 地址匯流排相對應”,是更貼切一些,不過拋開對實體記憶體定址方式的考慮,直接把實體地址與物理的記憶體一一對應,也是可以接受的。也許錯誤的理解更利於形而上的抽像。
邏輯地址(logical address):是指由程式產生的與段相關的偏移地址部分。例如,你在進行C語言指標
程式設計中,可以讀取指標變數本身值(&操作),實際上這個值就是邏輯地址,它是相對於你當前程序資料段的地址,不和絕對實體地址相干。
線性地址(linear address)或也叫虛擬地址(virtual address)
跟邏輯地址類似,它也是一個不真實的地址,如果邏輯地址是對應的硬體平臺式管理轉換前地址的話,那麼線性地址則對應了硬體頁式記憶體的轉換前地址
-------------------------------------------------------------
每個程序都有4GB的虛擬地址空間
這4GB分3部分 
(1)一部分對映實體記憶體
(2)一部分對映硬碟上的交換檔案
(3)一部分什麼也不做

程式中都是使用4GB的虛擬地址,訪問實體記憶體需要使用實體地址,實體地址是放在定址總線上的地址,以位元組(8位)為單位。

2.虛擬記憶體
    我們程式設計時所面對的都是虛擬地址,對於每個程序來說都擁有4G的虛擬記憶體(小補充: 4G虛擬記憶體中,高2G記憶體屬於核心部分,是所有程序共有的,低2G記憶體資料是程序獨有的,每個程序低2G記憶體都不一樣),但注意的是虛擬地址是作業系統自己意淫出來的,打個比方就是說想法還未付諸實踐,所以不構成任何資源損失.比如我們要在0x80000000的地方寫個"UESTC"的時候,作業系統就會將這個虛擬地址對映到一塊實體地址A中,你寫這塊虛擬地址就相當於寫實體地址A.但是加入我們只申請了一段1KB的虛擬記憶體空間,並未讀寫,系統是不會分配任何實體記憶體的,只有當虛擬記憶體要使用時系統才會分配相應的物理空間.

3.實體記憶體
    接下來就到實體記憶體的東東了,其實呢 實體記憶體的管理是基於一個數組來管理的,聽說過分頁機制吧,下面說下分頁.windows下分頁是4kb一頁 那麼假設我們實體記憶體有4GB 那麼windows會將這4GB空間分頁,分成4GB/4KB = 1M頁    那麼每一頁(就是4KB)的物理空間都由一個叫PHYSICAL_PAGE的資料結構管理,這個資料結構就不寫啦....一來我寫的手痠 二來看的人也累~~說說思路就好了.

1. 實體地址和邏輯地址

實體地址載入到記憶體地址暫存器中的地址,記憶體單元的真正地址。在前端總線上傳輸的記憶體地址都是實體記憶體地址,編號從0開始一直到可用實體記憶體的最高階。這些數字被北橋(Nortbridge chip)對映到實際的記憶體條上。實體地址是明確的、最終用在總線上的編號,不必轉換,不必分頁,也沒有特權級檢查(no translation, no paging, no privilege checks)。

邏輯地址CPU所生成的地址。邏輯地址是內部和程式設計使用的、並不唯一。例如,你在進行C語言指標程式設計中,可以讀取指標變數本身值(&操作),實際上這個值就是邏輯地址,它是相對於你當前程序資料段的地址(偏移地址),不和絕對實體地址相干。

為什麼會有這兩種地址?

個人覺的原因在於邏輯地址分配更加靈活,可以允許不唯一,看起來也較為直觀,例如,一段程式碼中分配陣列,邏輯地址上是連續的,然而在實體地址上,這個陣列所佔用的頁可能分散開來,實體地址上就是不連續的,這樣對程式的可理解性上有影響。另外,有了邏輯地址這個概念,才能使用虛擬記憶體技術。

2. Paging,分頁記憶體管理方案

(1) 分頁的最大作用就在於:使得程序的實體地址空間可以是非連續的。

實體記憶體被劃分為一小塊一小塊,每塊被稱為幀(Frame)。分配記憶體時,幀是分配時的最小單位,最少也要給一幀。在邏輯記憶體中,與幀對應的概念就是頁(Page)。

邏輯地址的表示方式是:前部分是頁碼後部分是頁偏移。

例如,已知邏輯空間地址為2^m個位元組(也就是說邏輯地址的長度是m位),已知頁大小是2^n位元組。那麼一共可以有2^(m-n)個頁。因此頁碼部分會佔m-n位,之後的n位,用來儲存頁偏移。

舉個例子, 頁大小為4B,而邏輯記憶體為32B(8頁),邏輯地址0的頁號為0,頁號0對應幀5,因此邏輯地址對映為實體地址5*4+0=20。邏輯地址3對映實體地址5*4+3=23。邏輯地址13(4*3+1,頁號為3,偏移為1,因此幀號為2),對映到實體地址9。

採用分頁技術不會產生外部碎片(記憶體都被劃分為幀),但可能產生內部碎片(幀已經是最小單元,因此幀內部可能有空間沒有用到)。

按概率計算下來,每個程序平均可有半個幀大小的內部碎片。

(2) 頁表的硬體實現

上一小節中寫到頁表是邏輯地址轉化到實體地址的關鍵所在。那麼頁表如何儲存?

每個作業系統都有自己的方法來儲存頁表。絕大多數都會為每個程序分配一個頁表。現在由於頁表都比較大,所以放在記憶體中(以往是放在一組專用暫存器裡),其指標存在程序控制塊(PCB)裡,當程序被排程程式選中投入執行時,系統將其頁表指標從程序控制塊中取出並送入使用者暫存器中。隨後可以根據此首地址訪問頁表。

頁表的儲存方式是TBL(Translation look-aside buffer, 翻譯後備緩衝器)+記憶體。TBL實際上是一組硬體緩衝所關聯的快速記憶體。若沒有TBL,作業系統需要兩次記憶體訪問來完成邏輯地址到實體地址的轉換,訪問頁表算一次,在頁表中查詢算一次。TBL中儲存頁表中的一小部分條目,條目以鍵值對方式儲存。

(3) 頁表的資料結構

a.

今年是2013年,現有的膝上型電腦,記憶體地址空間一般為2^32位元組以上。對於具有32位邏輯地址空間的計算機系統,如果系統的頁大小為4KB(2^12B),那麼頁表可以擁有2^(32-12)個,也就是一百多萬個條目,假設每個條目佔有4B,那每個程序都需要4MB的實體地址空間來存放頁表本身。而且,頁表本身需要分配在連續記憶體中。

為此,Hierarchical Paging(層次化分頁)被提出,實際上就是將頁號分為兩部分,第一部分作為索引,第二部分作為頁號的偏移。

以一個4kb頁大小的32位系統為例。一個邏輯地址被分為20位的頁碼和12位的頁偏移。因為要對頁表進行再分頁,所以該頁號可分為10位的頁碼和10位的頁偏移。這樣一個邏輯地址就表示如下形式:

地址轉換過程如下:

地址由外向內轉換,因此此方法也被稱為forward-mapped page table(向前對映表)

b. Hashed Page Tables 雜湊頁表

處理超過32位地址空間的常用方法是使用hashed page table(雜湊頁表),並以虛擬頁碼作為雜湊值。雜湊頁表的每一條目都包括一個連結串列的元素,這些元素雜湊成同一位置。每個元素有三個域:虛擬頁碼,所對映的幀號,指向連結串列中下一個元素的指標。

個人看來,雜湊頁表的地址轉換方式,實際上是Chaining(連結)方式,也就是一種雜湊函式的溢位處理方式(另一種溢位處理方式叫做Open Addressing,開放定址),具體過程如下:

邏輯地址需要大於32bit的地址空間來表示,但是作業系統仍只有32bit來表示地址。此時人們便想到虛擬頁地址,虛擬地址可以在32bit表示範圍之內,然後利用雜湊函式完成邏輯地址到虛擬地址的對映,由於虛擬地址更少,雜湊函式會出現溢位,這裡使用Chaining來解決溢位。

邏輯地址中的頁號(下圖中的p)經過雜湊函式的計算,算出虛擬地址中的頁號,根據虛擬頁號可以在雜湊表中以O(1)方式定址,用p與連結串列中的每一個元素的第一個域相比較。如果匹配,那麼相應的幀號就用來形成實體地址。如果不匹配,就對連結串列中的下一個節點進行比較,以尋找一個匹配的頁號。

c. Inverted page table 反向頁表

時間關係,這段暫時略過。

3. Segmentation,分段記憶體管理方案

採用分頁記憶體管理有一個不可避免的問題:使用者視角的記憶體和實際記憶體的分離。設想一段main函式程式碼,裡面包含Sqrt函式的呼叫。按照編寫者的理解,這段程式碼執行時,作業系統應該分配記憶體給:符號表(編譯時使用),棧(存放區域性變數與函式引數值),Sqrt程式碼段,主函式程式碼段等。這樣,編寫者就可以方便地指出:"函式sqrt記憶體模組的第五條指令",來定位一個元素。而實際上,由於採用Paging的管理方式,所有的一切都只是散落在實體記憶體中的各個幀上,並不是以編寫者的理解來劃分模組。

Segmentation的記憶體管理方式可以支援這種思路。邏輯地址空間由一組段組成。每個段都有名字和長度。地址指定了段名稱和段內偏移。因此使用者通過兩個量來指定地址:段名稱和偏移。段是編號的,通過段號而非段名稱來引用。因此邏輯地址由有序對構成:

 <segment-number,offset>(<段號s, 段內偏移d>)

段偏移d因該在0和段界限之間,如果合法,那麼就與基地址相加而得到所需位元組在實體記憶體中的地址。因此段表是一組基地址和界限暫存器對。

例如下圖,有5個段,編號0~4,例如段2為400B開始於位置4300,對段2第53位元組的引用對映成位置4300+53=4353。而段0位元組1222的引用則會觸發地址錯誤,因為該段的僅為1000B長(界限為1000)。

4. 合併分段和分頁的管理方案

在現有的Intel相容計算機(x86)上,採用的記憶體管理方案是分段和分頁合併的管理方案。

在這個方案中,邏輯地址,如前一節中所說,是由一個段識別符號加上一個指定段內相對地址的偏移量,表示為 [段識別符號:段內偏移量]。

這樣的邏輯地址轉換的過程是怎樣呢?如下圖所示:

CPU要執行一條引用了記憶體地址的指令時,轉換過程就開始了。第一步是把邏輯地址轉換成線性地址。但是,為什麼不跳過這一步,而讓軟體直接使用線性地址(或實體地址呢?)原因主要是因為:

(1) Intel的更新是漸進式而非革命式,新的處理器需要相容和保留過往的設定。具體的原因,博文Memory Translation and Segmentation (http://blog.csdn.net/drshenlei/article/details/4261909) 中講的較為清楚。

(2) 如上節所說,採用段記憶體管理,可以跟方便地進行地址保護(同一型別的地址邏輯地址在一起)。

下面講邏輯地址到線性地址的部分。

在IBM OS/2 32位版本的作業系統,和Intel 386的環境下。作業系統採用的記憶體分配方式就是分段和分頁合併的方式。

邏輯地址的實際上是一對<選擇符,偏移>。

選擇符的內容如下:

從左開始,13位是索引(或者稱為段號),通過這個索引,可以定位到段描述符(segment descriptor),而段描述符是可以真正記載了有關一個段的位置和大小資訊, 以及訪問控制的狀態資訊。段描述符一般由8個位元組組成。由於8B較大,而Intel為了保持向後相容,將段暫存器仍然規定為16-bit(儘管每個段暫存器事實上有一個64-bit長的不可見部分,但對於程式設計師來說,段暫存器就是16-bit的),那麼很明顯,我們無法通過16-bit長度的段暫存器來直接引用64-bit的段描述符。因此在邏輯地址中,只用13bit記錄其索引。而真正的段描述符,被放於陣列之中。

這個記憶體中的陣列就叫做GDT(Global Descriptor Table,全域性描述表),Intel的設計者門提供了一個暫存器GDTR用來存放GDT的入口地址。程式設計師將GDT設定在記憶體中某個位置之後,可以通過LGDT指令將GDT的入口地址裝入此暫存器,從此以後,CPU就根據此暫存器中的內容作為GDT的入口來訪問GDT了。

除了GDT之外,還有LDT(Local Descriptor Table,本地描述表),但與GDT不同的是,LDT在系統中可以存在多個,每個程序可以擁有自己的LDT。LDT的記憶體地址在LDTR暫存器中。

在之前圖中的TI位,就是用來表示此索引所指向的段描述符是存於全域性描述表中,還是本地描述表中。=0,表示用GDT,=1表示用LDT。

RPL位,佔2bit,是保護資訊位,還沒有仔細瞭解過這一塊,暫時先不寫。

找到,段描述符後,加上偏移量,便是線性地址。轉換過程如下:

在Intel 386的環境下,線性地址轉換為實體地址的過程,和第二節分頁式記憶體管理中,層次分頁中,邏輯地址轉換為實體地址的方法類似。如下圖。

Intel 80386的地址轉換全過程如下圖: