1. 程式人生 > >作業系統 記憶體地址(邏輯地址、線性地址、實體地址)概念

作業系統 記憶體地址(邏輯地址、線性地址、實體地址)概念

邏輯地址(Logical Address) 
是指由程式產生的與段相關的偏移地址部分。
例如,你在進行C語言指標程式設計中,可以讀取指標變數本身值(&操作),實際上這個值就是邏輯地址,它是相對於你當前程序資料段的地址,不和絕對實體地址相干。只有在Intel真實模式下,邏輯地址才和實體地址相等(因為真實模式沒有分段或分頁機制,Cpu不進行自動地址轉換);邏輯也就是在Intel 
保護模式下程式執行程式碼段限長內的偏移地址(假定程式碼段、資料段如果完全一樣)。
應用程式設計師僅需與邏輯地址打交道,而分段和分頁機制對您來說是完全透明的,僅由系統程式設計人員涉及。應用程式設計師雖然自己可以直接操作記憶體,那也只能在作業系統給你分配的記憶體段操作。



如果是程式設計師,那麼邏輯地址對你來說應該是輕而易舉就可以理解的。我們在寫C程式碼的時候經常說我們定義的結構體首地址的偏移量,函式的入口偏移量,陣列首地址等等。當我們在考究這些概念的時候,其實是相對於你這個程式而言的。並不是對於整個作業系統而言的。也就是說,邏輯地址是相對於你所編譯執行的具體的程式(或者叫程序吧,事實上在執行時就是當作一個程序來執行的)而言。你的編譯好的程式的入口地址可以看作是首地址,而邏輯地址我們通常可以認為是在這個程式中,編譯器為我們分配好的相對於這個首地址的偏移,或者說以這個首地址為起點的一個相對的地址值。
        
當我們雙擊一個可執行程式時,就是給作業系統提供了這個程式執行的入口地址。之後shell把可執行檔案的地址傳入核心。進入核心後,會fork一個新的程序出來,新的程序首先分配相應的記憶體區域。這裡會碰到一個著名的概念叫做Copy 
On 
Write,即寫時複製技術。這裡不詳細講述,總之新的程序在fork出來之後,新的程序也就獲得了整個的PCB結構,繼而會呼叫exec函式轉而去將磁碟中的程式碼載入到記憶體區域中。這時候,程序的PCB就被加入到可執行程序的佇列中,當CPU排程到這個程序的時候就真正的執行了。


   
我們大可以把程式執行的入口地址理解為邏輯地址的起始地址,也就是說,一個程式的開始的地址。以及以後用到的程式的相關資料或者程式碼相對於這個起始地址的位置(這是由編譯器事先安排好的),就構成了我們所說的邏輯地址。邏輯地址就是相對於一個具體的程式(事實上是一個程序,即程式真正被執行時的相對地址)而言的。儘管我們這樣理解可能有一些細節上的偏差,但是比起網上一些含糊其辭,讓人不知所云的描述要好得多,實用得多,等到自己對這個地址有更加深刻的理解的時候,再對上面的理解進行一些補充或者糾正。

   總之一句話,邏輯地址是相對於應用程式而言的。

邏輯地址產生的歷史背景:


追根求源,Intel的8位機8080CPU,

資料匯流排(DB)為8位,地址匯流排(AB)為16位。那麼這個16位地址資訊也是要通過8位資料匯流排來傳送,也是要在資料通道中的暫存器,以及在CPU中的暫存器和記憶體中存放的,但由於AB正好是 
DB的整數倍,故不會產生矛盾!


    
但當上升到16位機後,Intel8086/8088CPU的設計由於當年IC整合技術和外封裝及引腳技術的限制,不能超過40個引腳。但又感覺到8位機原來的地址定址能力2^16=64KB太少了,但直接增加到16的整數倍即令AB=32位又是達不到的。故而只能把AB暫時增加4條成為20條。則 
2^20=1MB的定址能力已經增加了16倍。但此舉卻造成了AB的20位和DB的16位之間的矛盾,20位地址資訊既無法在DB上傳送,又無法在16位的CPU暫存器和記憶體單元中存放。於是應運而生就產生了CPU段結構的原理。




線性地址(Linear Address) 
是邏輯地址到實體地址變換之間的中間層。程式程式碼會產生邏輯地址,或者說是段中的偏移地址,加上相應段的基地址就生成了一個線性地址。
如果啟用了分頁機制,那麼線性地址可以再經變換以產生一個實體地址。若沒有啟用分頁機制,那麼線性地址直接就是實體地址。Intel 
80386的線性地址空間容量為4G(2的32次方即32根地址匯流排定址)。


線性地址:
我們知道每臺計算機有一個CPU(我們從單CPU來說吧。多CPU的情況應該是雷同的),最終所有的指令操作或者資料等等的運算都得由這個CPU來進行,而與CPU相關的暫存器就是暫存一些相關資訊的儲存記憶裝置。因此,從CPU的角度出發的話,我們可以將計算機的相關裝置或者部件簡單分為兩類:一是資料或指令儲存記憶裝置(如暫存器,記憶體等等),一種是資料或指令通路(如地址線,資料線等等)。線性地址的本質就是“CPU所看到的地址”。如果我們追根溯源,就會發現線性地址的就是伴隨著Intel的X86體系結構的發展而產生的。當32位CPU出現的時候,它的可定址範圍達到4GB,而相對於記憶體大小來說,這是一個相當巨大的數字,我們也一般不會用到這麼大的記憶體。那麼這個時候CPU可見的4GB空間和記憶體的實際容量產生了差距。而線性地址就是用於描述CPU可見的這4GB空間。我們知道在多程序作業系統中,每個程序擁有獨立的地址空間,擁有獨立的資源。但對於某一個特定的時刻,只有一個程序運行於CPU之上。此時,CPU看到的就是這個程序所佔用的4GB空間,就是這個線性地址。而CPU所做的操作,也是針對這個線性空間而言的。之所以叫線性空間,大概是因為人們覺得這樣一個連續的空間排列成一線更加容易理解吧。其實就是CPU的可定址範圍。
   
對linux而言,CPU將4GB劃分為兩個部分,0-3GB為使用者空間(也可以叫核外空間),3-4GB為核心空間(也可以叫核內空間)。作業系統相關的程式碼,即核心部分的程式碼資料都會對映到核心空間,而使用者程序則會對映到使用者空間。至於系統是如何將線性地址轉換到實際的實體記憶體上,那是另外的話題了。網上到處可以找到相關文章,我不在此囉嗦。對於X86,無外乎段式管理和頁式管理。

實體地址(Physical Address) 
是指出現在CPU外部地址總線上的定址實體記憶體的地址訊號,是地址變換的最終結果地址。
如果啟用了分頁機制,那麼線性地址會使用頁目錄和頁表中的項變換成實體地址。如果沒有啟用分頁機制,那麼線性地址就直接成為實體地址了。


虛擬記憶體(Virtual Memory) 
是指計算機呈現出要比實際擁有的記憶體大得多的記憶體量。
因此它允許程式設計師編制並執行比實際系統擁有的記憶體大得多的程式。這使得許多大型專案也能夠在具有有限記憶體資源的系統上實現。一個很恰當的比喻是:你不需要很長的軌道就可以讓一列火車從上海開到北京。你只需要足夠長的鐵軌(比如說3公里)就可以完成這個任務。採取的方法是把後面的鐵軌立刻鋪到火車的前面,只要你的操作足夠快並能滿足要求,列車就能象在一條完整的軌道上執行。這也就是虛擬記憶體管理需要完成的任務。在Linux 
0.11核心中,給每個程式(程序)都劃分了總容量為64MB的虛擬記憶體空間。因此程式的邏輯地址範圍是0x0000000到0x4000000。


有時我們也把邏輯地址稱為虛擬地址。因為與虛擬記憶體空間的概念類似,邏輯地址也是與實際實體記憶體容量無關的。 

邏輯地址與實體地址的“差距”是0xC0000000,是由於虛擬地址->線性地址->實體地址對映正好差這個值。這個值是由作業系統指定的。



虛擬地址到實體地址的轉化方法是與體系結構相關的。一般來說有分段、分頁兩種方式。以現在的x86 cpu為例,分段分頁都是支援的。Memory 
Mangement 
Unit負責從虛擬地址到實體地址的轉化。邏輯地址是段標識+段內偏移量的形式,MMU通過查詢段表,可以把邏輯地址轉化為線性地址。如果cpu沒有開啟分頁功能,那麼線性地址就是實體地址;如果cpu開啟了分頁功能,MMU還需要查詢頁表來將線性地址轉化為實體地址:
邏輯地址 
----(段表)---> 線性地址 — (頁表)—> 
實體地址
不同的邏輯地址可以對映到同一個線性地址上;不同的線性地址也可以對映到同一個實體地址上;所以是多對一的關係。另外,同一個線性地址,在發生換頁以後,也可能被重新裝載到另外一個實體地址上。所以這種多對一的對映關係也會隨時間發生變化。