內存映射與訪問機制

經過參考內存佈局及訪問機制的相關文章,本文試着整合一下相關知識點,但願能對有須要的朋友提供一點參考。但因爲所參考文章皆爲網友所做,相關知識並無造成系統的認識,因此有些知識點仍然不夠清楚,也不免有謬誤之處。若是你們發現錯誤,敬請指出,另外假若有關於「系統啓動及內存佈局」方面的可靠的資料或書籍,懇請大神留言告知。html

參考文章(只列出幾個重要的):http://www.cnblogs.com/clover-toeic/p/3754433.htmllinux

                   http://blog.chinaunix.net/uid-26126915-id-2981205.html程序員

                   http://blog.csdn.net/yeruby/article/details/39718119編程

                   http://www.cnblogs.com/qintangtao/p/3325985.html數組

                   http://www.cnblogs.com/wangccc/p/5342300.htmlruby

 

1、內存空間佈局數據結構

1.1 虛擬地址dom

  隨着圖形界面的興起和用戶需求的增大,內存空間變得容納不下程序了。所以人們將程序分割成許多片斷,並由操做系統將這些片斷調入內存運行,因而便產生一個虛擬地址的概念,其思想是:操做系統把程序當前使用的部分保留在內存中,而把其餘未被使用的部分保存在磁盤上,並在須要切換時將外設中的數據調入內存空間。而程序運行在虛擬地址空間中,並經過相關機制將虛擬地址映射到物理內存。所以內存空間就分爲物理存儲空間和虛擬存儲空間。函數

  引入虛擬地址的好處在於:佈局

  一、程序可使用一系列相鄰的虛擬地址來訪問物理內存中不相鄰的大內存緩衝區。

  二、程序可使用一系列虛擬地址來訪問大於可用物理內存的內存緩衝區。當物理內存的供應量變小時,內存管理器會將物理內存頁(一般大小爲 4 KB)保存到磁盤文件。數據或代碼頁會根據須要在物理內存與磁盤之間移動。

  三、不一樣進程使用的物理地址由系統進行映射管理,從而避免直接操做內存,防止惡意程序或bug破壞其它內存空間。

1.2 物理空間佈局

         物理存儲空間佈局與處理器相關,詳細狀況能夠從處理器用戶手冊的存儲空間分佈表(memory map)相關章節中查到。

1.3 虛擬空間佈局

         在多任務操做系統中,每一個進程都運行在虛擬地址空間(Virtual Address Space),在32位模式下它是一個4GB的內存地址塊。在Linux系統中, 內核進程和用戶進程所佔的虛擬內存比例是1:3,而Windows系統爲2:2(經過設置Large-Address-Aware Executables標誌也可爲1:3)。

         虛擬地址經過頁表(Page Table)映射到物理內存,頁表由操做系統維護並被處理器引用。內核空間在頁表中擁有較高特權級,所以用戶態程序試圖訪問這些頁時會致使一個頁錯誤(page fault)。在Linux中,內核空間是持續存在的,而且在全部進程中都映射到一樣的物理內存。內核代碼和數據老是可尋址,隨時準備處理中斷和系統調用。與此相反,用戶模式地址空間的映射隨進程切換的發生而不斷變化。

 Linux進程在虛擬內存中的標準內存段佈局以下圖所示:

 

  其中,用戶地址空間中的藍色條帶對應於映射到物理內存的不一樣內存段,灰白區域表示未映射的部分。這些段只是簡單的內存地址範圍,與Intel處理器的段沒有關係。

  上圖中Random stack offset和Random mmap offset等隨機值意在防止惡意程序。Linux經過對棧、內存映射段、堆的起始地址加上隨機偏移量來打亂佈局,以避免惡意程序經過計算訪問棧、庫函數等 地址。execve(2)負責爲進程代碼段和數據段創建映射,真正將代碼段和數據段的內容讀入內存是由系統的缺頁異常處理程序按需完成的。另外,execve(2)還會將BSS段清零。

 

2、內存訪問機制

         在80X86CPU的發展過程當中,存儲器的管理機制發生了較大的變化。8086/8088CPU對存儲器的管理採用分段的實方式;80286CPU除了可在實方式下工做外,還能夠在保護模式下工做;而80386CPU以後的處理器則具備三種工做方式:實方式、保護方式和虛擬8086方式。

2.1 分段機制

         段的引入:8086爲了用16位寄存器實現1MB(20位)的尋址內存空間,引入了段的概念。在沒有采用分頁管理時,邏輯地址計算而得的線性地址是直接映射物理地址(Physical Address)的,因而能夠直接用線性地址訪問內存;不然,還要經過X86的分頁轉換,將線性地址轉換爲物理地址。

         2.1.1 實模式

         在Real Mode下,咱們對一個內存地址的訪問是經過Segment:Offset的方式來進行的,其中Segment是一個段的Base Address,一個Segment的最大長度是64 KB,這是16-bit系統所能表示的最大長度。而Offset則是相對於此Segment Base Address的偏移量。Base Address+Offset就是一個內存絕對地址。在實際編程的時候,使用16-bit段寄存器CS(Code Segment),DS(Data Segment),SS(Stack Segment)來指定Segment,CPU將段寄存器中的數值向左偏移4-bit,放到20-bit的地址線上就成爲20-bit的Base Address。

         2.1.2 保護模式

         到了Protected Mode,內存的管理模式分爲兩種,段模式和頁模式。

         因爲 Protected Mode運行在32-bit系統上,那麼Segment的兩個因素:Base Address和Limit也都是32位的。IA-32容許將一個段的Base Address設爲32-bit所能表示的任何值(Limit則能夠被設爲32-bit所能表示的,以2^12爲倍數的任何指),而不像Real Mode下,一個段的Base Address只能是16的倍數(由於其低4-bit是經過左移運算得來的,只能爲0,從而達到使用16-bit段寄存器表示20-bit Base Address的目的),而一個段的Limit只能爲固定值64 KB。另外,Protected Mode,顧名思義,又爲段模式提供了保護機制,也就說一個段的描述符須要規定對自身的訪問權限(Access)。因此,在Protected Mode下,對一個段的描述則包括3方面因素:【Base Address, Limit, Access】,它們加在一塊兒被放在一個64-bit長的數據結構中,被稱爲段描述符。

  (1)GDT

  若是咱們直接經過一個64-bit段描述符來引用一個段的時候,就必須使用一個64-bit長的段寄存器裝入這個段描述符。但Intel爲了保持向後兼容,將段寄存器仍然規定爲16-bit(儘管每一個段寄存器事實上有一個64-bit長的不可見部分,但對於程序員來講,段寄存器就是16-bit的),那麼很明顯,咱們沒法經過16-bit長度的段寄存器來直接引用64-bit的段描述符。解決的方法就是把這些長度爲64-bit的段描述符放入一個數組中,而將段寄存器中的值做爲下標索引來間接引用(事實上,是將段寄存器中的高13 -bit的內容做爲索引)。這個全局的數組就是GDT。事實上,在GDT中存放的不只僅是段描述符,還有其它描述符,它們都是64-bit長。GDT是Protected Mode所必須的數據結構,也是惟一的。

  全局描述符表GDT含有每個任務均可能或能夠訪問的段的描述符,一般包含操做系統所使用的代碼段,數據段和堆棧段的描述符,也包含多種特殊數據段的描述符,如各個LDT的描述符等.每一個GDT最多含有8192個描述符.注意,GDT的第0個描述符總不被處理,一般它置成全0.

GDT結構圖以下:

 

說明以下:

G:

(1)、G=0時,段限長的20位爲實際段限長,最大限長爲2^20=1MB

(2)、G=1時,則實際段限長爲20位段限長乘以2^12=4KB,最大限長達到4GB

D/B:

當描述符指向的是可執行代碼段時,這一位叫作D位,D=1使用32位地址和32/8位操做數,D=0使用16位地址和16/8位操做數。若是指向的是向下擴展的數據段,這一位叫作B位,B=1時段的上界爲4GB,B=0時段的上界爲64KB。若是指向的是堆棧段,這一位叫作B位,B=1使用32位操做數,堆棧指針用ESP,B=0時使用16位操做數,堆棧指針用SP。

DPL:特權級,0爲最高特權級,3爲最低,表示訪問該段時CPU所需處於的最低特權級

type : 類型

(1)、type<8時:數據段

 

(2)、type>=8時:代碼段

 

         GDT能夠被放在內存的任何位置,那麼當程序員經過段寄存器來引用一個段描述符時,CPU必須知道GDT的入口,也就是基地址放在哪裏,因此Intel的設計者門提供了一個寄存器GDTR(GDTR是一個48位的全局描述符寄存器,高32位存放GDT的基址,低16位存放GDT限長。)用來存放GDT的入口地址,程序員將GDT設定在內存中某個位置以後,能夠經過LGDT指令將GDT的入口地址裝入此寄存器,今後之後,CPU就根據此寄存器中的內容做爲GDT的入口來訪問GDT了。

         (2)LDT

         除了GDT以外,IA-32還容許程序員構建與GDT相似的數據結構,它們被稱做LDT(Local Descriptor Table,局部描述符表),但與GDT不一樣的是,LDT在系統中能夠存在多個,而且從LDT的名字能夠得知,LDT不是全局可見的,它們只對引用它們的任務可見,保護模式支持多任務,每一個任務都有本身的局部描述符表LDT,且每一個任務最多隻有一個LDT,每一個任務的LDT含有該任務本身的代碼段,數據段和堆棧段的描述符。另外,每個LDT自身做爲一個段存在,它們的段描述符被放在GDT中。每一個LDT最多含有8192個描述符。

         IA-32爲LDT的入口地址也提供了一個寄存器LDTR(LDTR是一個16位的局部描述符寄存器,高13位存放LDT在GDT中的索引值。),由於在任什麼時候刻只能有一個任務在運行,因此LDT寄存器全局也只須要有一個。若是一個任務擁有自身的LDT,那麼當它須要引用自身的LDT時,它須要經過lldt指令將其LDT的段描述符裝入此寄存器。lldt指令與lgdt指令不一樣的時,lgdt指令的操做數是一個32-bit的內存地址,這個內存地址處存放的是一個32-bit GDT的入口地址,以及16-bit的GDT Limit。而lldt指令的操做數是一個16-bit的選擇子,這個選擇子主要內容是:被裝入的LDT的段描述符在GDT中的索引值。

         因此咱們能夠這樣理解GDT和LDT:GDT爲一級描述符表,LDT爲二級描述符表。如圖:

 

         其中,selector選擇器(一個16位的數據結構)被裝入段寄存器,它的高13位做爲被引用的段描述符在GDT/LDT中的下標索引,bit 2用來指定被引用段描述符被放在GDT中仍是到LDT中,bit 0和bit 1是RPL——請求特權等級,被用來作保護目的。如圖所示:

 

         (3)IDT

         中斷描述符表(Interrupt Descriptor Table,IDT)將每一個異常或中斷向量分別與它們的處理過程聯繫起來。與GDT和LDT表相似,IDT也是由8字節長描述符組成的一個數組。

         IDT表能夠駐留在線性地址空間的任何地方,處理器使用IDTR寄存器來定位IDT表的位置。這個寄存器中含有IDT表32位的基地址和16位的長度(限長)值。

         在實地址模式中,CPU把內存中從0開始的1K字節做爲一箇中斷向量表。表中的每一個表項佔四個字節,由兩個字節的段地址和兩個字節的偏移量組成,這樣構成的地址即是相應中斷處理程序的入口地址。可是,在保護模式下,由四字節的表項構成的中斷向量表顯然知足不了要求。這是由於,除了兩個字節的段描述符,偏移量必用四字節來表示;要有反映模式切換的信息。在保護模式下,中斷向量表中的表項由8個字節組成,中斷向量表也改叫作中斷描述符表IDT(InterruptDescriptor Table)。其中的每一個表項叫作一個門描述符(gate descriptor),「門」的含義是當中斷髮生時必須先經過這些門,而後才能進入相應的處理程序。

主要門描述符是:

· 中斷門(Interrupt gate)

其類型碼爲110,中斷門包含了一箇中斷或異常處理程序所在段的選擇符和段內偏移量。當控制權經過中斷門進入中斷處理程序時,處理器清IF標誌,即關中斷,以免嵌套中斷的發生。中斷門中的DPL(Descriptor Privilege Level)爲0,所以,用戶態的進程不能訪問Intel的中斷門。全部的中斷處理程序都由中斷門激活,並所有限制在內核態。

· 陷阱門(Trap gate)

其類型碼爲111,與中斷門相似,其惟一的區別是,控制權經過陷阱門進入處理程序時維持IF標誌位不變,也就是說,不關中斷。

· 系統門(System gate)

這是Linux內核特別設置的,用來讓用戶態的進程訪問Intel的陷阱門,所以,門描述符的DPL爲3。經過系統門來激活4個Linux異常處理程序,它們的向量是三、四、5及128,也就是說,在用戶態下,可使用int三、into、bound 及int0x80四條彙編指令。

最後,在保護模式下,中斷描述符表在內存的位置再也不限於從地址0開始的地方,而是能夠放在內存的任何地方。爲此,CPU中增設了一箇中斷描述符表寄存器IDTR,用來存放中斷描述符表在內存的起始地址。中斷描述符表寄存器IDTR是一個48位的寄存器,其低16位保存中斷描述符表的大小,高32位保存IDT的基址.

2.2 分頁機制

         2.2.1 MMU

         在沒有使用虛擬存儲器的機器上,虛擬地址被直接送到內存總線上,使具備相同地址的物理存儲器被讀寫。而在使用了虛擬存儲器的狀況下,虛擬地址不是被直接送到內存地址總線上,而是送到內存管理單元——MMU。他由一個或一組芯片組成,通常存在於協處理器中,其功能是把虛擬地址映射爲物理地址,這是MMU的基本做用之一,除了硬件的支持外,軟件上實際就是維護一張表,表中的內容是VA到PA的轉換法則;另外一做用是能夠實現不一樣的訪問權限。

         2.2.2 地址映射

         分頁的最大做用就在於:使得進程的物理地址空間能夠是非連續的。在分段的方法中,每次程序運行時老是把程序所有裝入內存,而分頁的方法按照程序運行的局部性原理只將部分程序裝入內存。

         當建立一個進程時,操做系統會爲該進程分配一個4GB(32位系統中)大小的虛擬進程地址空間。建立4GB虛擬地址空間其實並非要真的建立空間,只是要建立那種映射機制所須要的數據結構而已,這種數據結構就是頁目和頁表。

         一個頁表的大小爲4K字節,放在一個物理頁中。由1024個4字節的頁表項組成。頁表項的大小爲4個字節(32bit),因此一個頁表中有1024 個頁表項。頁表中的每一項的內容(每項4個字節,32bit)高20bit用來放一個物理頁的物理地址,低12bit放着一些標誌。

頁目錄,一個頁目錄大小爲4K字節,放在一個物理頁中。由1024個4字節的頁目錄項組成。頁目錄項的大小爲4個字節(32bit),因此一個頁目錄中有 1024個頁目錄項。頁目錄中的每一項的內容(每項4個字節)高20bit用來放一個頁表(頁表放在一個物理頁中)的物理地址,低12bit放着一些標誌。

對於x86系統,頁目錄的物理地址放在CPU的CR3寄存器中。

頁表中包含物理頁面基地址和頁的屬性。對於頁表有兩級頁表和三級頁表之分。linux爲了保證可移植行,採用了三級分頁機制,固然其在某些狀況下能夠返回到二級分頁。

 

3、系統啓動過程的內存變化

         參考http://blog.csdn.net/huangzhipeng/article/details/6159169

4、小結

         一、在實模式邏輯地址是直接映射到物理地址的,因此能夠直接使用邏輯地址訪問物理存儲單元;在分段模式下,邏輯地址對應線性地址(虛擬地址),須要轉換爲對應的物理地址;在分頁模式下,內存被劃分爲一系列較段小的頁,從而減小了內存交換的開銷,提升了內存使用效率,其使用的虛擬地址與物理地址並不是一一對應,因此須要進行轉換。

         二、實模式下經過段寄存器:偏移量肯定邏輯地址;段模式下經過GDT(R)、LDT(R)和selector(就是段寄存器)肯定虛擬地址;分頁模式下經過MMU和多級頁表肯定存儲單元,而CPU使用的是虛擬地址。

相關文章
相關標籤/搜索