2、線性地址轉物理地址
前面說了Linux中邏輯地址等於線性地址,那麼線性地址怎麼對應到物理地址呢?這個你們都知道,那就是經過分頁機制,具體的說,就是經過頁表查找來對應物理地址。
分頁是CPU提供的一種機制,Linux只是根據這種機制的規則,利用它實現了內存管理。
分頁的基本原理是把線性地址分紅固定長度的單元,稱爲頁(page)。頁內部連續的線性地址映射到連續的物理地址中。X86每頁爲4KB(爲簡化分析,咱們不考慮擴展分頁的狀況)。爲了能轉換成物理地址,咱們須要給CPU提供當前任務的線性地址轉物理地址的查找表,即頁表(page table),頁表存放在內存中。
在保護模式下,控制寄存器CR0的最高位PG位控制着分頁管理機制是否生效,若是PG=1,分頁機制生效,需經過頁表查找才能把線性地址轉換物理地址。若是PG=0,則分頁機制無效,線性地址就直接做爲物理地址。
爲了實現每一個任務的平坦的虛擬內存和相互隔離,每一個任務都有本身的頁目錄表和頁表。
爲了節約頁表佔用的內存空間,x86將線性地址經過頁目錄表和頁表兩級查找轉換成物理地址。
32位的線性地址被分紅3個部分:
最高10位 Directory 頁目錄表偏移量,中間10位 Table是頁表偏移量,最低12位Offset是物理頁內的字節偏移量。
頁目錄表的大小爲4KB(恰好是一個頁的大小),包含1024項,每一個項4字節(32位),表項裏存儲的內容就是頁表的物理地址(由於物理頁地址4k字節對齊,物理地址低12位老是0,因此表項裏的最低12字節記錄了一些其餘信息,這裏作簡化分析)。若是頁目錄表中的頁表還沒有分配,則物理地址填0。
頁表的大小也是4k,一樣包含1024項,每一個項4字節,內容爲最終物理頁的物理內存起始地址。
每一個活動的任務,必需要先分配給它一個頁目錄表,並把頁目錄表的物理地址存入cr3寄存器。頁表能夠提早分配好,也能夠在用到的時候再分配。
仍是以 mov 0x80495b0, %eax 中的地址爲例分析一下線性地址轉物理地址的過程。
前面說到Linux中邏輯地址等於線性地址,那麼咱們要轉換的線性地址就是0x80495b0。轉換的過程是由CPU自動完成的,Linux所要作的就是準備好轉換所需的頁目錄表和頁表(假設已經準備好,給頁目錄表和頁表分配物理內存的過程很複雜,後文再分析)。
內核先將當前任務的頁目錄表的物理地址填入cr3寄存器。
線性地址 0x80495b0 轉換成二進制後是 0000 1000 0000 0100 1001 0101 1011 0000,最高10位0000 1000 00的十進制是32,CPU查看頁目錄表第32項,裏面存放的是頁表的物理地址。線性地址中間10位00 0100 1001 的十進制是73,頁表的第73項存儲的是最終物理頁的物理起始地址。物理頁基地址加上線性地址中最低12位的偏移量,CPU就找到了線性地址最終對應的物理內存單元。
咱們知道Linux中用戶進程線性地址能尋址的範圍是0 - 3G,那麼是否是須要提早先把這3G虛擬內存的頁表都創建好呢?通常狀況下,物理內存是遠遠小於3G的,加上同時有不少進程都在運行,根本沒法給每一個進程提早創建3G的線性地址頁表。Linux利用CPU的一個機制解決了這個問題。進程建立後咱們能夠給頁目錄表的表項值都填0,CPU在查找頁表時,若是表項的內容爲0,則會引起一個缺頁異常,進程暫停執行,Linux內核這時候能夠經過一系列複雜的算法給分配一個物理頁,並把物理頁的地址填入表項中,進程再恢復執行。固然進程在這個過程當中是被矇蔽的,它本身的感受仍是正常訪問到了物理內存。
怎樣防止進程訪問不屬於本身的線性地址(如內核空間)或無效的地址呢?內核裏記錄着每一個進程能訪問的線性地址範圍(進程的vm_area_struct 線性區鏈表和紅黑樹裏存放着),在引起缺頁異常的時候,若是內核檢查到引起缺頁的線性地址不在進程的線性地址範圍內,就發出SIGSEGV信號,進程結束,咱們將看到程序員最討厭看到的Segmentation fault。html
本貼涉及的硬件平臺是X86,若是是其它平臺,嘻嘻,不保證能一一對號入座,可是觸類旁通,我想是徹底可行的。
1、概念
物理地址(physical address)
用於內存芯片級的單元尋址,與處理器和CPU鏈接的地址總線相對應。
——這個概念應該是這幾個概念中最好理解的一個,可是值得一提的是,雖然能夠直接把物理地址理解成插在機器上那根內存自己,把內存當作一個從0字節一直到最大空量逐字節的編號的大數組,而後把這個數組叫作物理地址,可是事實上,這只是一個硬件提供給軟件的抽像,內存的尋址方式並非這樣。因此,說它是「與地址總線相對應」,是更貼切一些,不過拋開對物理內存尋址方式的考慮,直接把物理地址與物理的內存一一對應,也是能夠接受的。也許錯誤的理解更利於形而上的抽像。
虛擬內存(virtual memory)
這是對整個內存(不要與機器上插那條對上號)的抽像描述。它是相對於物理內存來說的,能夠直接理解成「不直實的」,「假的」內存,例如,一個0x08000000內存地址,它並不對就物理地址上那個大數組中0x08000000 - 1那個地址元素;
之因此是這樣,是由於現代操做系統都提供了一種內存管理的抽像,即虛擬內存(virtual memory)。進程使用虛擬內存中的地址,由操做系統協助相關硬件,把它「轉換」成真正的物理地址。這個「轉換」,是全部問題討論的關鍵。
有了這樣的抽像,一個程序,就可使用比真實物理地址大得多的地址空間。(拆東牆,補西牆,銀行也是這樣子作的),甚至多個進程可使用相同的地址。不奇怪,由於轉換後的物理地址並不是相同的。
——能夠把鏈接後的程序反編譯看一下,發現鏈接器已經爲程序分配了一個地址,例如,要調用某個函數A,代碼不是call A,而是call 0x0811111111 ,也就是說,函數A的地址已經被定下來了。沒有這樣的「轉換」,沒有虛擬地址的概念,這樣作是根本行不通的。
打住了,這個問題再說下去,就收不住了。
邏輯地址(logical address)
Intel爲了兼容,將遠古時代的段式內存管理方式保留了下來。邏輯地址指的是機器語言指令中,用來指定一個操做數或者是一條指令的地址。以上例,咱們說的鏈接器爲A分配的0x08111111這個地址就是邏輯地址。
——不過很差意思,這樣說,好像又違背了Intel中段式管理中,對邏輯地址要求,「一個邏輯地址,是由一個段標識符加上一個指定段內相對地址的偏移量,表示爲 [段標識符:段內偏移量],也就是說,上例中那個0x08111111,應該表示爲[A的代碼段標識符: 0x08111111],這樣,才完整一些」
線性地址(linear address)或也叫虛擬地址(virtual address)
跟邏輯地址相似,它也是一個不真實的地址,若是邏輯地址是對應的硬件平臺段式管理轉換前地址的話,那麼線性地址則對應了硬件頁式內存的轉換前地址。
-------------------------------------------------------------
CPU將一個虛擬內存空間中的地址轉換爲物理地址,須要進行兩步:首先將給定一個邏輯地址(實際上是段內偏移量,這個必定要理解!!!),CPU要利用其段式內存管理單元,先將爲個邏輯地址轉換成一個線程地址,再利用其頁式內存管理單元,轉換爲最終物理地址。
這樣作兩次轉換,的確是很是麻煩並且沒有必要的,由於直接能夠把線性地址抽像給進程。之因此這樣冗餘,Intel徹底是爲了兼容而已。
二、CPU段式內存管理,邏輯地址如何轉換爲線性地址
一個邏輯地址由兩部份組成,段標識符: 段內偏移量。段標識符是由一個16位長的字段組成,稱爲段選擇符。其中前13位是一個索引號。後面3位包含一些硬件細節,如圖:
最後兩位涉及權限檢查,本貼中不包含。
索引號,或者直接理解成數組下標——那它總要對應一個數組吧,它又是什麼東東的索引呢?這個東東就是「段描述符(segment descriptor)」,呵呵,段描述符具體地址描述了一個段(對於「段」這個字眼的理解,我是把它想像成,拿了一把刀,把虛擬內存,砍成若干的截——段)。這樣,不少個段描述符,就組了一個數組,叫「段描述符表」,這樣,能夠經過段標識符的前13位,直接在段描述符表中找到一個具體的段描述符,這個描述符就描述了一個段,我剛纔對段的抽像不太準確,由於看看描述符裏面究竟有什麼東東——也就是它到底是如何描述的,就理解段究竟有什麼東東了,每個段描述符由8個字節組成,以下圖:
這些東東很複雜,雖然能夠利用一個數據結構來定義它,不過,我這裏只關心同樣,就是Base字段,它描述了一個段的開始位置的線性地址。
Intel設計的本意是,一些全局的段描述符,就放在「全局段描述符表(GDT)」中,一些局部的,例如每一個進程本身的,就放在所謂的「局部段描述符表(LDT)」中。那究竟何時該用GDT,何時該用LDT呢?這是由段選擇符中的T1字段表示的,=0,表示用GDT,=1表示用LDT。
GDT在內存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT則在ldtr寄存器中。
好多概念,像繞口令同樣。這張圖看起來要直觀些:
首先,給定一個完整的邏輯地址[段選擇符:段內偏移地址],
一、看段選擇符的T1=0仍是1,知道當前要轉換是GDT中的段,仍是LDT中的段,再根據相應寄存器,獲得其地址和大小。咱們就有了一個數組了。
二、拿出段選擇符中前13位,能夠在這個數組中,查找到對應的段描述符,這樣,它了Base,即基地址就知道了。
三、把Base + offset,就是要轉換的線性地址了。
仍是挺簡單的,對於軟件來說,原則上就須要把硬件轉換所需的信息準備好,就可讓硬件來完成這個轉換了。OK,來看看Linux怎麼作的。
三、Linux的段式管理
Intel要求兩次轉換,這樣雖然說是兼容了,可是倒是很冗餘,呵呵,沒辦法,硬件要求這樣作了,軟件就只能照辦,怎麼着也得形式主義同樣。
另外一方面,其它某些硬件平臺,沒有二次轉換的概念,Linux也須要提供一個高層抽像,來提供一個統一的界面。因此,Linux的段式管理,事實上只是「哄騙」了一下硬件而已。
按照Intel的本意,全局的用GDT,每一個進程本身的用LDT——不過Linux則對全部的進程都使用了相同的段來對指令和數據尋址。即用戶數據段,用戶代碼段,對應的,內核中的是內核數據段和內核代碼段。這樣作沒有什麼奇怪的,原本就是走形式嘛,像咱們寫年終總結同樣。
include/asm-i386/segment.hlinux
把其中的宏替換成數值,則爲:程序員
方括號後是這四個段選擇符的16位二製表示,它們的索引號和T1字段值也能夠算出來了算法
T1均爲0,則表示都使用了GDT,再來看初始化GDT的內容中相應的12-15項(arch/i386/head.S):shell
按照前面段描述符表中的描述,能夠把它們展開,發現其16-31位全爲0,即四個段的基地址全爲0。
這樣,給定一個段內偏移地址,按照前面轉換公式,0 + 段內偏移,轉換爲線性地址,能夠得出重要的結論,「在Linux下,邏輯地址與線性地址老是一致(是一致,不是有些人說的相同)的,即邏輯地址的偏移量字段的值與線性地址的值老是相同的。!!!」
忽略了太多的細節,例如段的權限檢查。呵呵。
Linux中,絕大部份進程並不例用LDT,除非使用Wine ,仿真Windows程序的時候。
4.CPU的頁式內存管理
CPU的頁式內存管理單元,負責把一個線性地址,最終翻譯爲一個物理地址。從管理和效率的角度出發,線性地址被分爲以固定長度爲單位的組,稱爲頁(page),例如一個32位的機器,線性地址最大可爲4G,能夠用4KB爲一個頁來劃分,這頁,整個線性地址就被劃分爲一個tatol_page[2^20]的大數組,共有2的20個次方個頁。這個大數組咱們稱之爲頁目錄。目錄中的每個目錄項,就是一個地址——對應的頁的地址。
另外一類「頁」,咱們稱之爲物理頁,或者是頁框、頁楨的。是分頁單元把全部的物理內存也劃分爲固定長度的管理單位,它的長度通常與內存頁是一一對應的。
這裏注意到,這個total_page數組有2^20個成員,每一個成員是一個地址(32位機,一個地址也就是4字節),那麼要單單要表示這麼一個數組,就要佔去4MB的內存空間。爲了節省空間,引入了一個二級管理模式的機器來組織分頁單元。文字描述太累,看圖直觀一些:
如上圖,
一、分頁單元中,頁目錄是惟一的,它的地址放在CPU的cr3寄存器中,是進行地址轉換的開始點。萬里長征就今後長始了。
二、每個活動的進程,由於都有其獨立的對應的虛似內存(頁目錄也是惟一的),那麼它也對應了一個獨立的頁目錄地址。——運行一個進程,須要將它的頁目錄地址放到cr3寄存器中,將別個的保存下來。
三、每個32位的線性地址被劃分爲三部份,面目錄索引(10位):頁表索引(10位):偏移(12位)
依據如下步驟進行轉換:
一、從cr3中取出進程的頁目錄地址(操做系統負責在調度進程的時候,把這個地址裝入對應寄存器);
二、根據線性地址前十位,在數組中,找到對應的索引項,由於引入了二級管理模式,頁目錄中的項,再也不是頁的地址,而是一個頁表的地址。(又引入了一個數組),頁的地址被放到頁表中去了。
三、根據線性地址的中間十位,在頁表(也是數組)中找到頁的起始地址;
四、將頁的起始地址與線性地址中最後12位相加,獲得最終咱們想要的葫蘆;
這個轉換過程,應該說仍是很是簡單地。所有由硬件完成,雖然多了一道手續,可是節約了大量的內存,仍是值得的。那麼再簡單地驗證一下:
一、這樣的二級模式是否仍可以表示4G的地址;
頁目錄共有:2^10項,也就是說有這麼多個頁表
每一個目表對應了:2^10頁;
每一個頁中可尋址:2^12個字節。
仍是2^32 = 4GB
二、這樣的二級模式是否真的節約了空間;
也就是算一下頁目錄項和頁表項共佔空間 (2^10 * 4 + 2 ^10 *4) = 8KB。哎,……怎麼說呢!!!
紅色錯誤,標註一下,後文貼中有此討論。。。。。。
按<深刻理解計算機系統>中的解釋,二級模式空間的節約是從兩個方面實現的:
A、若是一級頁表中的一個頁表條目爲空,那麼那所指的二級頁表就根本不會存在。這表現出一種巨大的潛在節約,由於對於一個典型的程序,4GB虛擬地址空間的大部份都會是未分配的;
B、只有一級頁表才須要老是在主存中。虛擬存儲器系統能夠在須要時建立,並頁面調入或調出二級頁表,這就減小了主存的壓力。只有最常用的二級頁表才須要緩存在主存中。——不過Linux並無徹底享受這種福利,它的頁表目錄和與已分配頁面相關的頁表都是常駐內存的。
值得一提的是,雖然頁目錄和頁表中的項,都是4個字節,32位,可是它們都只用高20位,低12位屏蔽爲0——把頁表的低12屏蔽爲0,是很好理解的,由於這樣,它恰好和一個頁面大小對應起來,你們都成整數增長。計算起來就方便多了。可是,爲何同時也要把頁目錄低12位屏蔽掉呢?由於按一樣的道理,只要屏蔽其低10位就能夠了,不過我想,由於12>10,這樣,可讓頁目錄和頁表使用相同的數據結構,方便。
本貼只介紹通常性轉換的原理,擴展分頁、頁的保護機制、PAE模式的分頁這些麻煩點的東東就不囉嗦了……能夠參考其它專業書籍。
5.Linux的頁式內存管理
原理上來說,Linux只須要爲每一個進程分配好所需數據結構,放到內存中,而後在調度進程的時候,切換寄存器cr3,剩下的就交給硬件來完成了(呵呵,事實上要複雜得多,不過偶只分析最基本的流程)。
前面說了i386的二級頁管理架構,不過有些CPU,還有三級,甚至四級架構,Linux爲了在更高層次提供抽像,爲每一個CPU提供統一的界面。提供了一個四層頁管理架構,來兼容這些二級、三級、四級管理架構的CPU。這四級分別爲:
頁全局目錄PGD(對應剛纔的頁目錄)
頁上級目錄PUD(新引進的)
頁中間目錄PMD(也就新引進的)
頁表PT(對應剛纔的頁表)。
整個轉換依據硬件轉換原理,只是多了二次數組的索引罷了,以下圖:
那麼,對於使用二級管理架構32位的硬件,如今又是四級轉換了,它們怎麼可以協調地工做起來呢?嗯,來看這種狀況下,怎麼來劃分線性地址吧!
從硬件的角度,32位地址被分紅了三部份——也就是說,無論理軟件怎麼作,最終落實到硬件,也只認識這三位老大。
從軟件的角度,因爲多引入了兩部份,,也就是說,共有五部份。——要讓二層架構的硬件認識五部份也很容易,在地址劃分的時候,將頁上級目錄和頁中間目錄的長度設置爲0就能夠了。
這樣,操做系統見到的是五部份,硬件仍是按它死板的三部份劃分,也不會出錯,也就是說你們共建了和諧計算機系統。
這樣,雖然說是畫蛇添足,可是考慮到64位地址,使用四層轉換架構的CPU,咱們就再也不把中間兩個設爲0了,這樣,軟件與硬件再次和諧——抽像就是強大呀!!!
例如,一個邏輯地址已經被轉換成了線性地址,0x08147258,換成二制進,也就是:
0000100000 0101000111 001001011000
內核對這個地址進行劃分
PGD = 0000100000
PUD = 0
PMD = 0
PT = 0101000111
offset = 001001011000
如今來理解Linux針對硬件的花招,由於硬件根本看不到所謂PUD,PMD,因此,本質上要求PGD索引,直接就對應了PT的地址。而不是再到PUD和PMD中去查數組(雖然它們兩個在線性地址中,長度爲0,2^0 =1,也就是說,它們都是有一個數組元素的數組),那麼,內核如何合理安排地址呢?
從軟件的角度上來說,由於它的項只有一個,32位,恰好能夠存放與PGD中長度同樣的地址指針。那麼所謂先到PUD,到到PMD中作映射轉換,就變成了保持原值不變,一一轉手就能夠了。這樣,就實現了「邏輯上指向一個PUD,再指向一個PDM,但在物理上是直接指向相應的PT的這個抽像,由於硬件根本不知道有PUD、PMD這個東西」。
而後交給硬件,硬件對這個地址進行劃分,看到的是:
頁目錄 = 0000100000
PT = 0101000111
offset = 001001011000
嗯,先根據0000100000(32),在頁目錄數組中索引,找到其元素中的地址,取其高20位,找到頁表的地址,頁表的地址是由內核動態分配的,接着,再加一個offset,就是最終的物理地址了。編程
分析linux內存管理機制,離不了上述幾個概念,在介紹上述幾個概念以前,先從《深刻理解linux內核》這本書中摘抄幾段關於上述名詞的解釋:windows
邏輯地址(Logical Address)
數組
包含在機器語言指令中用來指定一個操做數或一條指令的地址(有點深奧)。這種尋址方式在80x86著名的分段結構中表現得尤其具體,它促使windows程序員把程序分紅若干段。每一個邏輯地址都由一個段和偏移量組成,偏移量指明瞭從段開始的地方到實際地址之間的距離。緩存
線性地址(linear address)(也稱虛擬地址 virtual address)數據結構
是一個32位無符號整數,能夠用來表示高達4GB的地址,線性地址一般用十六進制數字表示,值的範圍從0x00000000到0xffffffff。
物理地址(physical address)
用於內存芯片級內存單元尋址。它們與從微處理器的地址引腳按發送到內存總線上的電信號相對應。物理地址由32位或36位無符號整數表示。(其實這個最好理解,就是實實在在的地址)
(PS:在下面的解釋就能夠看到,有時也將邏輯地址看作虛擬地址,可是《深刻理解linux內核》中將線性地址看作虛擬地址)
首先說一句話:linux關於內存尋址能夠分爲幾個階段,首先由分段機制,而後有分頁機制。
分頁機制在段機制以後進行,以完成線性—物理地址的轉換過程。段機制把邏輯地址轉換爲線性址頁機制進一步把該線性地址再轉換爲物理地址
下面是我從網上查找資料瞭解到的,同時添加了本身的理解
邏輯地址(Logical Address)
是指由程序產生的與段相關的偏移地址部分。例如,你在進行C語言指針編程中,能夠讀取指針變量自己值(&操做),實際上這個值就是邏輯地址,它是相對於你當前進程數據段的地址,不和絕對物理地址相干。只有在Intel實模式下,邏輯地址才和物理地址相等(由於實模式沒有分段或分頁機制,Cpu不進行自動地址轉換);邏輯也就是在Intel保護模式下程序執行代碼段限長內的偏移地址(假定代碼段、數據段若是徹底同樣)。應用程序員僅需與邏輯地址打交道,而分段和分頁機制對您來講是徹底透明的,僅由系統編程人員涉及。應用程序員雖然本身能夠直接操做內存,那也只能在操做系統給你分配的內存段操做。(也就是說,我們應用程序中看到的地址都是邏輯地址。)
若是是程序員,那麼邏輯地址對你來講應該是垂手可得就能夠理解的。咱們在寫C代碼的時候常常說咱們定義的結構體首地址的偏移量,函數的入口偏移量,數組首地址等等。當咱們在考究這些概念的時候,實際上是相對於你這個程序而言的。並非對於整個操做系統而言的。也就是說,邏輯地址是相對於你所編譯運行的具體的程序(或者叫進程吧,事實上在運行時就是看成一個進程來執行的)而言。你的編譯好的程序的入口地址能夠看做是首地址,而邏輯地址咱們一般能夠認爲是在這個程序中,編譯器爲咱們分配好的相對於這個首地址的偏移,或者說以這個首地址爲起點的一個相對的地址值。(PS:這麼來看,邏輯地址就是一個段內偏移量,可是這麼說違背了邏輯地址的定義,在intel段是管理中,一個邏輯地址,是由一個段標識符加上一個指定段內相對地址的偏移量,表示爲 [段標識符:段內偏移量])
當咱們雙擊一個可執行程序時,就是給操做系統提供了這個程序運行的入口地址。以後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段結構的原理。Intel爲了兼容,將遠古時代的段式內存管理方式保留了下來,也就存在了邏輯地址
線性地址(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的可尋址範圍。
物理地址(Physical Address)
是指出如今CPU外部地址總線上的尋址物理內存的地址信號,是地址變換的最終結果地址。若是啓用了分頁機制,那麼線性地址會使用頁目錄和頁表中的項變換成物理地址。若是沒有啓用分頁機制,那麼線性地址就直接成爲物理地址了。
虛擬內存(Virtual Memory)
是指計算機呈現出要比實際擁有的內存大得多的內存量。所以它容許程序員編制並運行比實際系統擁有的內存大得多的程序。這使得許多大型項目也可以在具備有限內存資源的系統上實現。一個很恰當的比喻是:你不須要很長的軌道就可讓一列火車從上海開到北京。你只須要足夠長的鐵軌(好比說3千米)就能夠完成這個任務。採起的方法是把後面的鐵軌馬上鋪到火車的前面,只要你的操做足夠快並能知足要求,列車就能象在一條完整的軌道上運行。這也就是虛擬內存管理須要完成的任務。在Linux
0.11內核中,給每一個程序(進程)都劃分了總容量爲64MB的虛擬內存空間。所以程序的邏輯地址範圍是0x0000000到0x4000000。
有時咱們也把邏輯地址稱爲虛擬地址。由於與虛擬內存空間的概念相似,邏輯地址也是與實際物理內存容量無關的。(這一點和上面的解釋有一點區別,往下的解釋就按照這個繼續)
邏輯地址與物理地址的「差距」是0xC0000000,是因爲虛擬地址->線性地址->物理地址映射正好差這個值。這個值是由操做系統指定的。
虛擬地址到物理地址的轉化方法是與體系結構相關的。通常來講有分段、分頁兩種方式。以如今的x86 cpu爲例,分段分頁都是支持的。MemoryMangement Unit負責從邏輯地址到物理地址的轉化。邏輯地址是段標識+段內偏移量的形式,MMU經過查詢段表,能夠把邏輯地址轉化爲線性地址。若是cpu沒有開啓分頁功能,那麼線性地址就是物理地址;若是cpu開啓了分頁功能,MMU還須要查詢頁表來將線性地址轉化爲物理地址:
邏輯地址 ----(段表)---> 線性地址 — (頁表)—> 物理地址
不一樣的邏輯地址能夠映射到同一個線性地址上;不一樣的線性地址也能夠映射到同一個物理地址上;因此是多對一的關係。另外,同一個線性地址,在發生換頁之後,也可能被從新裝載到另一個物理地址上。因此這種多對一的映射關係也會隨時間發生變化。
程序(進程)的虛擬地址和邏輯地址
邏輯地址(logicaladdress)指程序產生的段內偏移地址。應用程序只與邏輯地址打交道,分段分頁對應用程序來講是透明的。也就是說C語言中的&,彙編語言中的符號地址,C中嵌入式彙編的」m」對應的都是邏輯地址。
邏輯地址是Intel爲了兼容,將遠古時代的段式內存管理方式保留了下來。邏輯地址指的是機器語言指令中,用來指定一個操做數或者是一條指令的地址。以上例,咱們說的鏈接器爲A分配的0x08111111這個地址就是邏輯地址。不過很差意思,這樣說,好像又違背了Intel中段式管理中,對邏輯地址要求,「一個邏輯地址,是由一個段標識符加上一個指定段內相對地址的偏移量,表示爲[段標識符:段內偏移量],也就是說,上例中那個0x08111111,應該表示爲[A的代碼段標識符: 0x08111111],這樣,才完整一些」
線性地址(linear address)或也叫虛擬地址(virtual address):跟邏輯地址相似,它也是一個不真實的地址,若是邏輯地址是對應的硬件平臺段式管理轉換前地址的話,那麼線性地址則對應了硬件頁式內存的轉換前地址。
實際物理內存地址
物理地址(physicaladdress)是CPU外部地址總線上的尋址信號,是地址變換的最終結果,一個物理地址始終對應實際內存中的一個存儲單元。對80386保護模式來講,若是開啓分頁機制,線性地址通過頁變換產生物理地址。若是沒有開啓分頁機制,線性地址直接對應物理地址。頁目錄表項、頁表項對應都是物理地址。
是指出如今CPU外部地址總線上的尋址物理內存的地址信號,是地址變換的最終結果地址。若是啓用了分頁機制,那麼線性地址會使用頁目錄和頁表中的項變換成物理地址。若是沒有啓用分頁機制,那麼線性地址就直接成爲物理地址了。
物理地址用於內存芯片級的單元尋址,與處理器和CPU鏈接的地址總線相對應。這個概念應該是這幾個概念中最好理解的一個,可是值得一提的是,雖然能夠直接把物理地址理解成插在機器上那根內存自己,把內存當作一個從0字節一直到最大空量逐字節的編號的大數組,而後把這個數組叫作物理地址,可是事實上,這只是一個硬件提供給軟件的抽像,內存的尋址方式並非這樣。因此,說它是「與地址總線相對應」,是更貼切一些,不過拋開對物理內存尋址方式的考慮,直接把物理地址與物理的內存一一對應,也是能夠接受的。也許錯誤的理解更利於形而上的抽像。
Linux0.11的內核數據段,內核代碼段基地址都是0,因此對內核來講,邏輯地址就是線性地址。又由於1個頁目錄表和4個頁表徹底映射16M物理內存,因此線性地址也就是物理地址。故對linux0.11內核來講,邏輯地址,線性地址,物理地址重合。
========================================================
虛擬地址是對整個內存(不要與機器上插那條對上號)的抽像描述。它是相對於物理內存來說的,能夠直接理解成「不真實的」,「假的」內存,例如,一個0x08000000內存地址,它並不對就物理地址上那個大數組中0x08000000 - 1那個地址元素;之因此是這樣,是由於現代操做系統都提供了一種內存管理的抽像,即虛擬內存(virtual memory)。進程使用虛擬內存中的地址,由操做系統協助相關硬件,把它「轉換」成真正的物理地址。這個「轉換」,是全部問題討論的關鍵。有了這樣的抽像,一個程序,就可使用比真實物理地址大得多的地址空間。(拆東牆,補西牆,銀行也是這樣子作的),甚至多個進程可使用相同的地址。不奇怪,由於轉換後的物理地址並不是相同的。能夠把鏈接後的程序反編譯看一下,發現鏈接器已經爲程序分配了一個地址,例如,要調用某個函數A,代碼不是call A,而是call 0x0811111111 ,也就是說,函數A的地址已經被定下來了。沒有這樣的「轉換」,沒有虛擬地址的概念,這樣作是根本行不通的。打住了,這個問題再說下去,就收不住了。
CPU將一個虛擬內存空間中的地址轉換爲物理地址,須要進行兩步:首先將給定一個邏輯地址(實際上是段內偏移量,這個必定要理解!!!),CPU要利用其段式內存管理單元,先將爲個邏輯地址轉換成一個線程地址,再利用其頁式內存管理單元,轉換爲最終物理地址。
線性地址:是CPU所能尋址的空間或者範圍。
物理地址:是機器中實際的內存地址。換言之,是機器中的內存容量範圍。
邏輯地址:是對程序而言的。通常以Seg:Offset來表示。(程序員本身看到的地址)
所以,若要確實比較三者的話,應有如下關係:線性地址大於等於物理地址(PS:但兩者的地址空間是同樣的),而邏輯地址大於線性地址。邏輯地址經過段表變換成線性地址,此時若是並未開啓分頁機制的狀況下,邏輯地址直接轉換成CPU所能尋址的空間。若已開啓則經過頁表完成線性地址到物理地址的變換。
所以,三者最準確的關係是:邏輯地址經過線性地址完成物理地址的映射,線性地址在三者之中徹底是充當"橋"的做用。
無論哪一種解釋,都差很少,只不過把虛擬地址歸屬於剩下三種的哪個的問題