1. 程式人生 > >記憶體定址一(分段)

記憶體定址一(分段)

記憶體地址

邏輯地址(logical address)

包含在機器語言指令中用來指定一個運算元或一條指令的地址。這種定址方式在80x86著名的分段結構表現的尤為具體。每個邏輯地址都有一個段(segment)和偏移量(offset)組成,偏移量指明瞭從段開始的地方到實際地址之間的距離。

線性地址(linear address)(也稱虛擬地址virtual address)

是一個32位無符號整數,可以用來表示高達4G的地址。線性地址通常用十六進位制數字表示,值的範圍從0x00000000到0xffffffff。

實體地址(physical address)

用於記憶體晶片級記憶體單元定址。它們與微處理器的地址引腳傳送到記憶體總線上的電訊號相對應。實體地址由32位或36位無符號整數表示。

記憶體管理單元(MMU)通過一種稱為分段單元(segmentation unit)的硬體電路把一個邏輯地址轉換成線性地址;接著,第二個稱為分頁單元(paging unit)的硬體電路把線性地址轉換為一個實體地址。


當Intel處理器工作在保護模式下,分段機制是必須使用的,而分頁是可選的。當分頁沒有使用時,線性地址空間就直接對映到實體地址空間。實體地址空間是指處理器在地址匯流排產生的地址空間範圍,如處理器為32位但地址匯流排是36位,這實體地址空間就是0~2^36

硬體中的分段

一個邏輯地址由兩部分組成:一個段識別符號和一個指定段內相對地址的偏移量。段識別符號是一個16位長的欄位,稱為段選擇符

段選擇符(segment selector)


Index 指定了放在GDT或者LDT中的相應段描述符的入口。從存放在GDT或者LDT中的8192個描述符中選擇段描述符,由於一個段描述符是8個位元組長(下面 會講道),因此它在GDT或者LDT內的相對地址是由段選擇符的最高13位的值乘以8得到的。例如:如果GDT在0x00020000(這個值儲存在gdtr暫存器中),且由段選擇符所指定的索引號為2,那麼相應的段描述符地址是0x00020000 + 2*8

TI    TI(Table Indicator)標誌;指明段描述符是在GDT中(TI=0)或在LDT中(TI=1)

RPL   請求特權等級(Requested Privilege Level):當相應的段選擇符裝入到CS暫存器中時指示出CPU當前的特權等級。優先順序範圍從0到3,0代表最高優先順序,而3代表最低優先順序。Linux只用到0級和3級,分別稱之為核心態和使用者態

為了快速方面地找到段選擇符,處理器提供段暫存器,段暫存器的唯一目的是存放段選擇符。這些暫存器稱為cs,ss,ds,es,fs和gs。

6個暫存器中有3個有專門用途:

cs  程式碼段暫存器,指向包含程式指令的段。

ss  棧段暫存器,指向包含當前程式的段。

ds  資料段暫存器,指向包含靜態資料或者全域性資料段

對intel處理器,有兩種型別的指令用於載入段暫存器

一類是顯式引用,如MOV, POP, LDS, LES, LSS, LGS, and LFS

另一類是隱式載入,如CALL,JMP,RET,SYSENTER,SYSEXIT,IRET,INTn等這些指令都會附帶修改CS或者其他段暫存器內容

段描述符表(Segment Descriptor Tables)

段描述符表是一個段描述符的陣列,一個描述符表的大小最多為8192(2^13)8byte的描述符

有兩種型別的描述符表

全域性描述符表(Global Descriptor Table,GDT)

區域性描述符表(Local Descriptor Table,LDT)


通常只定義一個GDT,而每個程序除了存放在GDT中的段之外還需要建立附加的段,就可以有自己的LDT。GDT在主存中的地址和大小存放在gdtr控制暫存器中,當前正被使用的LDT地址和大小存放在ldtr控制暫存器中。


段描述符(Segment Descriptor)


Base  包含段的首位元組的線性地址,處理器把3個base address域形成一個32bit值,段基地址應該是要16位元組邊界對齊的但不是必須的,因為16位元組對齊可以使程式效能最大化通過程式碼與資料16位元組邊界對齊

G     粒度(granularity)標誌:如果該位清0,則段大小以位元組為單位,否則以4096位元組的倍數計

D/B   稱為D或B的標誌,取決於是程式碼段還是資料段。D或者B的含義情況在兩種情況下稍有區別,但是如果段偏移量的地址是32位長,就是基本上把它置為       1,如果這個偏移量是16位長,它被清0

Limit 存放段中最後一個記憶體單元的偏移量,從而決定段的長度。如果G被設定為0,則一個段的大小在1個位元組到1MB之間變化;否則,將在4KB到4GB之間       變化    

P     Segment-Present標誌:等於0表示段當前不在主存中。Linux總是把這個標誌設定為1,因為它從來不把整個段交換到磁碟上去。

DPL   描述符特權級(Descriptor Privilege Level)欄位:用於限制對這個段的存取。它表示為訪問這個段而要求的CPU最小的優先順序。因此,DPL設       為0的段只能當CPL為0時(即在核心態)才可以訪問的,而DPL設為3的段對任何CPL值都是可以訪問的

S     系統標誌:如果它被清零,則這是一個系統段,儲存諸如LDT這種關鍵的資料結構,否則它是一個普通的程式碼段或資料段

Type  描述了段的型別特徵和它的存取許可權

有幾種不同型別的段以及和它們對應的段描述符。下面列出了Linux中被廣泛採用的型別:

程式碼段描述符

表示這個段描述符代表一個程式碼段,它可以放在GDT或者LDT中。該描述符置S標誌位1

資料段描述符

表示這個段描述符代表一個數據段,它可以放在GDT或者LDT中。該描述符置S標誌位1。棧段是通過一般資料段實現的

任務狀態段描述符(TSSD)

表示這個段描述符代表一任務狀態段(Task State Segment,TSS),也就是說這個段用於儲存處理器暫存器的內容。它只能出現在GDT中。根據相應的程序是否正在CPU上執行,其Type欄位的值分別為11或9.這個描述符的S標誌置為0

區域性描述符表描述符(LDTD)

表示這個段描述代表一個包含LDT的段,它只能出現在GDT中。相應的Type欄位的值為12,S標誌位0。

總結一下分段單元是如何把邏輯地址轉換成線性地址的

a、先檢查段選擇符的TI欄位,以決定段描述符儲存在哪個描述符表。

b、從段選擇符的Index欄位計算段描述符的地址,Index欄位的值乘以8,這個結果與gdtr或者ldtr暫存器的內容相加

c、把邏輯地址的偏移量與描述符Base欄位的值相加就得到了線性地址


Linux中的分段

Linux以非常有限的方式使用分段。與分段相比,Linux更喜歡使用分頁方式,因為:

a、當所有程序使用相同的段暫存器值時,記憶體管理變得更簡單,也就是說它們能共享同樣的一組線性地址

b、Linux設計目標之一是可以把它移植到絕大多數流行的處理器平臺上。然而,RISC體系結構對分段的支援有限

2.6版本的Linux只有在80x86結構下才需要使用分段。

執行在使用者態的所有Linux程序都使用一對相同的段來對指令和資料定址。這兩個段就是所謂的使用者程式碼段和使用者資料段。類似地,執行在核心態的所有Linux程序都使用一對相同的段對指令和資料定址:它們分別叫做核心態程式碼段和核心資料段。

四個主要的Linux段的段描述符欄位的值

段         Base       G  Limit    S  Type  DPL  D/B  P

使用者程式碼段  0x00000000  1  0xfffff  1  10    3    1    1

使用者資料段  0x00000000  1  0xfffff  1  2     3    1    1

核心程式碼段  0x00000000  1  0xfffff  1  10    0    1    1

核心資料段  0x00000000  1  0xfffff  1  2     0    1    1

相應的段選擇符由巨集__USER_CS、__USER_DS、__KERNEL_DS、__KERNEL_CS。例如,為了對核心程式碼段定址,核心只需要把__KERNEL_CS巨集產生的值裝進cs段暫存器即可

注意,與段相關的線性地址從0開始,達到2^32-1的定址限長。這意味著在使用者態或者核心態下的所有程序可以使用相同的邏輯地址。

所有段都從0x00000000開始,這可以得出另一個重要結論,那就是在Linux下邏輯地址與線性地址是一致的,即邏輯地址的偏移量欄位的值與相應的線性地址的值總是一致的

摘自linux-2.6.32/arch/x86/include/asm/segment.h

#define GDT_ENTRY_DEFAULT_USER_CS	14
#define GDT_ENTRY_DEFAULT_USER_DS	15
#define GDT_ENTRY_KERNEL_BASE	12
#define GDT_ENTRY_KERNEL_CS		(GDT_ENTRY_KERNEL_BASE + 0)
#define GDT_ENTRY_KERNEL_DS		(GDT_ENTRY_KERNEL_BASE + 1)
#define __KERNEL_CS	(GDT_ENTRY_KERNEL_CS * 8)
#define __KERNEL_DS	(GDT_ENTRY_KERNEL_DS * 8)
#define __USER_DS     (GDT_ENTRY_DEFAULT_USER_DS* 8 + 3)
#define __USER_CS     (GDT_ENTRY_DEFAULT_USER_CS* 8 + 3)

Linux GDT

在單處理器系統中只有一個GDT,而在多處理器系統中每個CPU對應一個GDT。所有的GDT都存放在cpu_gdt_table陣列中。

每一個GDT中包含的18個段描述符指向下列的段:

a、使用者態和核心態下的程式碼段和資料段共4個

b、任務狀態段(TSS),每個處理器有1個。每個TSS相應的線性地址空間都是核心資料段相應線性地址空間的一個子集。

c、一個包括預設區域性描述符表的段,這個段通常是被所有程序共享的段

d、3個區域性執行緒儲存段(Thread-Local Storage,TLS):這種機制允許多執行緒應用程式使用最多3個區域性於執行緒的資料段。系統呼叫set_thread_area()和get_thread_area()分別為正在執行的程序建立和撤銷一個TLS段

Linux LDT

大多數使用者態下的Linux程式不使用區域性描述符表,這樣核心就定義了一個預設的LDT共大多數程序共享。

相關推薦

記憶體(分段)

記憶體地址 邏輯地址(logical address) 包含在機器語言指令中用來指定一個運算元或一條指令的地址。這種定址方式在80x86著名的分段結構表現的尤為具體。每個邏輯地址都有一個段(segme

Linux 記憶體分段機制

前言 最近在學習Linux核心,讀到《深入理解Linux核心》的記憶體定址一章。原本以為自己對分段分頁機制已經理解了,結果發現其實是一知半解。於是,查找了很多資料,最終理順了記憶體定址的知識。現在把我的理解記錄下來,希望對核心學習者有一定幫助,也希望大家指出錯誤之處。分段到

【深入理解Linux核心】記憶體

  1. 邏輯地址:包含在機器語言指令中用來指定一個運算元或一條指令的地址。每一個邏輯地址都由一個段和偏移量組成。偏移量指明瞭從段開始的地方到實際地址之間的距離。 2. 線性地址:又稱虛擬地址,是一個32位無符號整數,也用來表示4GB的地址,範圍從0x00000000到0xffff

保護模式下的分段記憶體

段選擇符(段暫存器中的值) 32位彙編中16位段暫存器(CS、DS、ES、SS、FS、GS)中不再存放段基址,而是段描述符在段描述符表中的索引值,D3-D15位是索引值,D0-D1位是請求特權級(RPL)用於特權檢查,D2位是描述符表引用指示位TI,TI=0指 示從全域性描

8086的記憶體分段機制 記憶體

如圖,8086內部有8個16位暫存器,AX BX CX DX SI DI BP SP 為了加快指令執行速度,8086內部有一個6位元組的指令預取佇列,當處理器執行不需要訪問記憶體的指令時,指令預取部件訪問記憶體預取指令。 8086有4個段暫存器 C

linux 記憶體 學習筆記(

ARM:RISC,精簡指令系統集 X86:CISC,複雜指令系統集 CISC:犧牲處理器本身的複雜度,換取高效能 RISC:將複雜度交給了編譯器,犧牲了程式大小和指令頻寬,換取了簡單和低功耗的硬體實現 CPL:current privilege leve

Linux的記憶體——淺談分段和分頁機制

本文會以80x86架構,linux2.6為例,簡單介紹記憶體的分段和分頁機制。1. 三種記憶體地址關於記憶體地址,首先要了解它有三種,分別是邏輯地址、線性地址和實體地址。把邏輯地址轉換為線性地址是由一個叫做分段單元的硬體電路完成的。同樣地,還有一個叫做分頁單元的硬體電路負責把

深入理解計算機系統-之-記憶體(四)--linux中分段機制的實現方式

linux中的分段機制 前面說了那麼多關於分段機制的實現,其實,Linux以非常有限的方式使用分段。因為,Linux基本不使用分段的機制(注:並不是不使用,使用分段方式還是必須的,會簡化程式的編寫和執行方式),或者說,Linux中的分段機制只是為了相容IA

《程式設計機制探析》第四章 執行棧與記憶體

《程式設計機制探析》第四章 執行棧與記憶體定址計算機啟動之後,作業系統程式首先從硬碟進入記憶體條,成為最先執行起來的一批程序。這一批作業系統程序可了不得,它們規定了CPU工作的總流程。CPU工作的時候,必須嚴格遵守作業系統程序定義的工作流程。為了滿足人類使用者的需求,現代的作

程式設計機制探析之執行棧與記憶體

《程式設計機制探析》第四章 執行棧與記憶體定址 計算機啟動之後,作業系統程式首先從硬碟進入記憶體條,成為最先執行起來的一批程序。這一批作業系統程序可了不得,它們規定了CPU工作的總流程。CPU工作的時候,必須嚴格遵守作業系統程序定義的工作流程。 為了滿足人類使用者的需求

記憶體的三種模型

 1. 地址的種類 首先明確一下邏輯地址和線性地址這兩個概念: 1. 邏輯地址 2. 線性地址 3. 實體地址 1.1 邏輯地址: 邏輯地址是編譯器生成的,我們使用在linux環境下,使用C語言指標時,指標的值就是邏輯地址。對於每個程序而言,他們都有一樣的程序地址空

記憶體之分頁機制

寫在前面: 分頁機制完成線性地址到實體地址的轉換 80x86 規定分頁機制是可選的。分段和分頁沒有什麼必然聯絡,分段可以說是 Intel 的 CPU 一直保持著的一種機制,而分頁只是保護模式下的一種記憶體管理策略。想開啟分頁機制,CPU必須工作在保護模式,而工

第一講 記憶體

引子 一段程式碼: #include <stdio.h> int foo; void main() { foo = 100; printf("%d\n",foo); } 問題:變數foo存放在記憶體的什麼位置,prin

匠心 x64 結構體系下的記憶體

在閱讀NewBluePill原始碼的時候,看記憶體的那一塊簡直頭疼,全是x64下的定址,之前根本就沒有接觸過x64的記憶體定址上的內容,看的暈頭轉向,決定先把x64下的定址給弄明白了再回過頭來看NewBluePill的原始碼,然後在網上一頓找,居然沒有找到關於x64定址的部落格或者文章,簡直痛苦啊,終於把

80386的記憶體機制複習

題外話: “不試試怎麼知道不行。”金句啊金句。 80x86的記憶體定址機制複習2011.06.03 80386處理器的工作模式: 80386處理器有三種工作模式:真實模式、保護模式 和 虛擬86模式。 模式的切換:真實模式和保護模式之間可以相互轉換,保護模式和虛擬86模式之間可以相互轉換,而真實模式和虛擬8

深入理解Linux核心day01--記憶體

記憶體定址 記憶體地址:     邏輯地址: 段+偏移量 組成     線性地址: 可用來表達4GB的地址 (也稱虛擬地址)     實體地址: 用於記憶體晶片級記憶體單元定址。他們與微處理器地址引腳傳送到記憶體總線上的電訊號相對應     記憶體控制單元(MMU) 通過一

linux記憶體解析

1.記憶體地址 1.邏輯地址:每一個邏輯地址都有一個段和偏移量組成。 2.線性地址:也叫虛擬地址,是一個32位無符號整數,可以用來表示高達4GB的地址,值得範圍從0x00000000到0xffffffff。 3.實體地址:用於記憶體晶片級記憶體單元定址 記憶體控制單元(MMU)將邏輯地址(通過段對映)轉化成

彙編學習筆記之真實模式/保護模式記憶體

真實模式下的記憶體地址     2的10次方是1K,20次方就是1M,30次方就是1G。     Intel 8086是16位CPU,它只有16位暫存器、16位資料匯流排和20位地址匯流排,它只能執行在真實模式。在真實模式,實體地址=段值*16+偏移,段值和偏移都是16位的

Linux記憶體管理解析() : 分段與分頁機制

背景 : 在此文章裡會從分頁分段機制去解析Linux記憶體管理系統如何工作的,由於Linux記憶體管理過於複雜而本人能力有限。會盡量將自己總結歸納的部分寫清晰。 從真實模式到保護模式的定址方式的不同 :    16位CPU的定址方式 : 在 8086 CPU 中,提供了兩類暫存器來進行定址,分別為段

演算法導論 第十章:散列表 筆記(直接表、散列表、通過連結法解決碰撞、雜湊函式、開放法、完全雜湊)

前面討論的各種資料結構中,記錄在各種結構中的相對位置是隨機的,和在記錄的關鍵字之間不存在有確定的關係,因此在查詢記錄是需要進行一系列和關鍵字的比較。而理想的情況是不希望進行任何的比較,一次存取便能得到所查記錄。那就必須在記錄的儲存位置和它的關鍵字之間建立一種確定的關係f,使每個關鍵字和結構中有一