内存寻址一(分段)
內存地址
邏輯地址(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共大多數進程共享。
轉載于:https://www.cnblogs.com/brucemengbm/p/6753685.html
總結
- 上一篇: JMeter 压力测试使用CSV参数
- 下一篇: NOIP 2015 提高组 Day2