Linux内存寻址
一.內存地址分類以及MMU介紹
對于程序員來說,可以簡單的把內存地址理解為一種訪問存儲單元的內容的一種方式。而對于80x86系列微處理器來說,我們需要區分三種地址:
(1)邏輯地址
這種地址通常使用在機器語言里用于指定操作數或機器指令的地址。該類地址在著名的80x86分段體系架構中得到了很好體現,因此DOS程序員和windows程序員都不得不把他們的程序分成一段一段的(如代碼段、數據段、堆棧段等等)。每一個邏輯地址都是由一個段(segment)和相對于段的實際起始地址的偏移地址(即offset 或 displacement)組成。
(2)線性地址(又稱虛擬地址)
一個32位的無符號整數就可用于描述4GB(2的32次方=4G)的內存地址空間,相當于4,294,967,296個內存單元。線性地址通常用16進制表示,大小范圍為:0x00000000 ~0xffffffff。
(3)物理地址
該類地址用于尋址/訪問內存芯片里的存儲單元。它們對應于微處理器引腳到內存總線之間的電信號。物理地址由一個32比特或38比特的無符號整數的16進制表示。
內存管理單元(MMU)可通過一個叫做段單元(segmentation unit)的硬件電路,將邏輯地址轉換成線性地址;接著,通過一個叫做頁單元(paging unit)的電路,再將線性地址轉換成物理地址。如下圖所示:
在有多個處理器的系統中,所有的CPU共享相同的內存。這就意味著,RAM芯片可能被相互獨立的CPU們并發的訪問。由于對RAM芯片的讀操作和寫操作都必須串行地執行,在總線和每個內存芯片之間添加了一個叫做內存仲裁器的硬件電路。這個電路的作用是:當RAM芯片處于空閑狀態(這里的空閑,當然是指沒有CPU在訪問它)時,授權給CPU訪問(俗稱“放行”);當RAM芯片正忙(已經有CPU在使用該芯片)時,延遲(暫時禁止,即暫時“閉門謝客”)其他CPU對其訪問。
甚至單處理器系統中,也會使用內存仲裁器。這是因為這些系統包括特殊的處理器------DMA控制器,它與CPU也存在并發操作的情況。當然,多處理器系統中的內存仲裁器電路更為復雜,因為它有更多的輸入端口。例如,雙核奔騰在每個芯片的入口維護了一個雙端口的仲裁器,并且要求兩個CPU在使用公用的總線時必須交換同步消息。從編程的角度看,仲裁器是隱而不見的,因為它是完全由硬件電路管理的。
?
二.邏輯地址到虛擬地址的轉換
1.段選擇符和分段寄存器
一個邏輯地址包括兩部分:段標識符 和 段內相對偏移地址。段標識符是一個被叫做段選擇符(selector)的16比特的域,而偏移地址是一個32比特的域。
為了方便快速檢索段選擇符,處理器提供了6個分段寄存器(segmentation register)來緩存段選擇符,它們是:cs,ss,ds,es,fs和gs. 雖然只有這6個寄存器,但程序可以復用同一個寄存器來實現不同的目的,只需要把該寄存器的內容保存到內存中,在隨后需要的時候可以恢復它的內容。需要注意的是,cs、ss、ds有專門的用途。請看下面介紹:
cs-----內存段寄存器,指向含有代碼指令的段;cs寄存器還有一個重要的功能:用于區分用戶模式和內核模式,它包含一個指定當前優先級別(CPL, current priviledge level)的的2比特的域。如果該域的值為0,表明優先級最高;如果該值為3,表明最低的優先級。 Linux只使用了0和3,用以區分內核模式和用戶模式。
ss-----堆棧段寄存器,指向包含當前程序棧的段;
ds-----數據段寄存器,指向包含靜態和全局數據的段。
其它三個,即es,fs和gs,都是通用分段寄存器,可以指向任意類型的段。
?
2.段描述符
每一個段都由一個8字節的段描述符來表示,它描述了段的特征。段描述符要么存儲在全局描述符表(GDT)里,要么存儲在本地描述符表(LDT)里.
通常只定義了一個GDT。然而,每個進程都允許有自己的LDT,如果進程需要額外創建除了GDT里描述的之外的段。主存里GDT的地址和大小都包含在gdtr控制寄存器中,而當前正在使用的LDT的地址和大小則包含在ldtr控制寄存器中。
BASE:段的第一個字節的線性地址。
G:如果為0,則段的大小用字節表示。
Limit:保存了段中最后一個存儲單元的偏移值,因此與segment的長度、大小是綁定在一起的。如果G為0,則LIMIT的大小范圍為1字節~1MB;反之, LIMIT大小范圍為4KB~4GB.
S:如果為0,表示為一個系統段(system segment);反之,為一個普通的數據段或代碼段。系統段里保存了關鍵的數據結構,如LDT.
Type:描述segment的類型。
DPL:描述符優先級別。主要用于對segment的訪問進行限制。
P:描述段是否在內存中存在的標記。
3.對段描述符的快速訪問的實現
我們知道,邏輯地址由一個16比特的段選擇符和一個32比特的偏移地址組成。同時,我們也知道,分段寄存器里只存儲了段選擇符。
我們先接著第一節繼續對段選擇符進行分析。它的格式如下所示:
Index:標記了GDT或LDT中段描述符的入口。由于段寄存器有8個字節長,它在GDT或LDT中的相對地址是這樣來計算的:13個bit之長(如上圖,比特3-15位)的index域值乘以8. 假設GDT位于0x00020000 (該值存放在gdtr控制寄存器中) 并且 段選擇符的index域值為2,那么相應的段描述符的地址是這么來計算的:
0x00020000 +(2 x8),即0x00020010.
TI: table indicator。TI=0,表示段描述符位于GDT中;TI=1,表示段描述符位于LDT中。
RPL:Requestor Previlige Level請求者優先級。
為了加快邏輯地址到線性地址的轉換過程,80x86增加了一個不可編程的寄存器。
每當一個段選擇符被加載到分段寄存器中時,相應的段描述符也被從內存里加載到那個匹配的不可編程的CPU寄存器中。這樣,邏輯地址的轉換就不再需要訪問主內存中的GDT和LDT,而只需要訪問那個包含段描述符的不可編程的寄存器。只有在分段寄存器內容改變時,才需要訪問LDT或GDT。
3.分段單元實現邏輯地址到線性地址的轉換
我們知道,內存管理單元(MMU)可通過一個叫做分段單元(segmentation unit)的硬件電路,將邏輯地址轉換成線性地址;接著,通過一個叫做分頁單元(paging unit)的電路,再將線性地址轉換成物理地址。如下圖所示:
那么,這個分段單元是按照什么樣的流程完成自己的職責所在呢?
首先,它會檢查段選擇符的TI域,進而知道是哪個描述表存放了相應的段描述符。如果段描述符位于GDT,則分段單元從gdtr寄存器中讀取GDT的線性基地址;否則,分段單元從ldtr讀取LDT的線性基地址。
其次,根據上一步得到的線性基地址和段選擇符的index域,計算出段描述符的地址。計算方法可參考上節。
通過以上兩步,我們就可以定位到我們需要的段描述符。
最后,把邏輯地址的偏移與前面定位到的段描述符的線性地址BASE域相加,得到線性地址。這樣,整個邏輯地址到線性地址的轉換過程就成了
地址轉換的過程如下圖所示:
注意,本文中的線性地址跟虛擬地址是一個概念。
?
Smith先生版權所有,出處:http://blog.csdn.net/acs713/article/details/7950414
總結
- 上一篇: C语言数据类型所占空间大小
- 下一篇: 内存地址转换与分段