本文主要內(nèi)容:
1.基本概念:物理內(nèi)存、虛擬內(nèi)存;物理地址、虛擬地址、邏輯地址;頁目錄,頁表
2.Windows內(nèi)存管理
3.CPU段式內(nèi)存管理
4.CPU頁式內(nèi)存管理
一、基本概念
1. 兩個(gè)內(nèi)存概念
物理內(nèi)存:人盡皆知,就是插在主板上的內(nèi)存條。他是固定的,內(nèi)存條的容量多大,物理內(nèi)存就有多大(集成顯卡系統(tǒng)除外)。但是如果程序運(yùn)行很多或者程序本身很大的話,就會(huì)導(dǎo)致大量的物理內(nèi)存占用,甚至導(dǎo)致物理內(nèi)存消耗殆盡。
虛擬內(nèi)存:簡(jiǎn)明的說,虛擬內(nèi)存就是在硬盤上劃分一塊頁面文件,充當(dāng)內(nèi)存。當(dāng)程序在運(yùn)行時(shí),有一部分資源還沒有用上或者同時(shí)打開幾個(gè)程序卻只操作其中一個(gè)程序時(shí),系統(tǒng)沒必要將程序所有的資源都塞在物理內(nèi)存中,于是,系統(tǒng)將這些暫時(shí)不用的資源放在虛擬內(nèi)存上,等到需要時(shí)在調(diào)出來用。
2.三個(gè)地址概念
物理地址(physical address):用于內(nèi)存芯片級(jí)的單元尋址,與處理器和CPU連接的地址總線相對(duì)應(yīng)。
——這個(gè)概念應(yīng)該是這幾個(gè)概念中最好理解的一個(gè),但是值得一提的是,雖然可以直接把物理地址理解成插在機(jī)器上那根內(nèi)存本身,把內(nèi)存看成一個(gè)從0字節(jié)一直到 最大空量逐字節(jié)的編號(hào)的大數(shù)組,然后把這個(gè)數(shù)組叫做物理地址,但是事實(shí)上,這只是一個(gè)硬件提供給軟件的抽像,內(nèi)存的尋址方式并不是這樣。所以,說它是“與 地址總線相對(duì)應(yīng)”,是更貼切一些,不過拋開對(duì)物理內(nèi)存尋址方式的考慮,直接把物理地址與物理的內(nèi)存一一對(duì)應(yīng),也是可以接受的。也許錯(cuò)誤的理解更利于形而上的抽像。
邏輯地址(logical address):是指由程序產(chǎn)生的與段相關(guān)的偏移地址部分。例如,你在進(jìn)行C語言指針編程中,可以讀取指針變量本身值(&操作),實(shí)際上這個(gè)值就是邏輯地址,它是相對(duì)于你當(dāng)前進(jìn)程數(shù)據(jù)段的地址,不和絕對(duì)物理地址相干。只有在Intel實(shí)模式下,邏輯地址才和物理地址相等(因?yàn)閷?shí)模式?jīng)]有分段或分頁機(jī)制,Cpu不進(jìn)行自動(dòng)地址轉(zhuǎn)換);邏輯也就是在Intel 保護(hù)模式下程序執(zhí)行代碼段限長(zhǎng)內(nèi)的偏移地址(假定代碼段、數(shù)據(jù)段如果完全一樣)。應(yīng)用程序員僅需與邏輯地址打交道,而分段和分頁機(jī)制對(duì)您來說是完全透明的,僅由系統(tǒng)編程人員涉及。應(yīng)用程序員雖然自己可以直接操作內(nèi)存,那也只能在操作系統(tǒng)給你分配的內(nèi)存段操作。
Intel為了兼容,將遠(yuǎn)古時(shí)代的段式內(nèi)存管理方式保留了下來。邏輯地址指的是機(jī)器語言指令中,用來指定一個(gè)操作數(shù)或者是一條指令的地址。以上例,我們說的連接器為A分配的0x08111111這個(gè)地址就是邏輯地址。
——不過不好意思,這樣說,好像又違背了Intel中段式管理中,對(duì)邏輯地址要求,“一個(gè)邏輯地址,是由一個(gè)段標(biāo)識(shí)符加上一個(gè)指定段內(nèi)相對(duì)地址的偏移量, 表示為 [段標(biāo)識(shí)符:段內(nèi)偏移量],也就是說,上例中那個(gè)0x08111111,應(yīng)該表示為[A的代碼段標(biāo)識(shí)符: 0x08111111],這樣,才完整一些”
線性地址(linear address)或也叫虛擬地址(virtual address)
跟邏輯地址類似,它也是一個(gè)不真實(shí)的地址,如果邏輯地址是對(duì)應(yīng)的硬件平臺(tái)段式管理轉(zhuǎn)換前地址的話,那么線性地址則對(duì)應(yīng)了硬件頁式內(nèi)存的轉(zhuǎn)換前地址。
-------------------------------------------------------------
每個(gè)進(jìn)程都有4GB的虛擬地址空間
這4GB分3部分
(1)一部分映射物理內(nèi)存
(2)一部分映射硬盤上的交換文件
(3)一部分什么也不做
程序中都是使用4GB的虛擬地址,訪問物理內(nèi)存需要使用物理地址,物理地址是放在尋址總線上的地址,以字節(jié)(8位)為單位。
-------------------------------------------------------------
CPU將一個(gè)虛擬內(nèi)存空間中的地址轉(zhuǎn)換為物理地址,需要進(jìn)行兩步:首先將給定一個(gè)邏輯地址(其實(shí)是段內(nèi)偏移量,這個(gè)一定要理解?。。。?,CPU要利用其段式內(nèi)存管理單元,先將為個(gè)邏輯地址轉(zhuǎn)換成一個(gè)線程地址,再利用其頁式內(nèi)存管理單元,轉(zhuǎn)換為最終物理地址。
這樣做兩次轉(zhuǎn)換,的確是非常麻煩而且沒有必要的,因?yàn)橹苯涌梢园丫€性地址抽像給進(jìn)程。之所以這樣冗余,Intel完全是為了兼容而已。
3.頁表、頁目錄概念
使用了分頁機(jī)制之后,4G的地址空間被分成了固定大小的頁,每一頁或者被映射到物理內(nèi)存,或者被映射到硬盤上的交換文件中,或者沒有映射任何東西。對(duì)于一般程序來說,4G的地址空間,只有一小部分映射了物理內(nèi)存,大片大片的部分是沒有映射任何東西。物理內(nèi)存也被分頁,來映射地址空間。對(duì)于32bit的 Win2k,頁的大小是4K字節(jié)。CPU用來把虛擬地址轉(zhuǎn)換成物理地址的信息存放在叫做頁目錄和頁表的結(jié)構(gòu)里。
物理內(nèi)存分頁,一個(gè)物理頁的大小為4K字節(jié),第0個(gè)物理頁從物理地址 0x00000000 處開始。由于頁的大小為4KB,就是0x1000字節(jié),所以第1頁從物理地址 0x00001000處開始。第2頁從物理地址0x00002000處開始??梢钥吹接捎陧摰拇笮∈?KB,所以只需要32bit的地址中高20bit來尋址物理頁。
頁目錄: 一個(gè)頁目錄大小為4K字節(jié),放在一個(gè)物理頁中。由1024個(gè)4字節(jié)的頁目錄項(xiàng)組成。頁目錄項(xiàng)的大小為4 個(gè)字節(jié)(32bit),所以一個(gè)頁目錄中有1024個(gè)頁目錄項(xiàng)。頁目錄中的每一項(xiàng)的內(nèi)容(每項(xiàng)4個(gè)字節(jié))高20bit用來放一個(gè)頁表(頁表放在一個(gè)物理頁中)的物理地址,低12bit放著一些標(biāo)志。
頁表: 一個(gè)頁表的大小為4K字節(jié),放在一個(gè)物理頁中。由1024個(gè)4字節(jié)的頁表項(xiàng)組成。頁表項(xiàng)的大小為4個(gè)字節(jié) (32bit),所以一個(gè)頁表中有1024個(gè)頁表項(xiàng)。頁表中的每一項(xiàng)的內(nèi)容(每項(xiàng)4個(gè)字節(jié),32bit)高20bit用來放一個(gè)物理頁的物理地址,低 12bit放著一些標(biāo)志。
對(duì)于x86系統(tǒng),頁目錄的物理地址放在CPU的CR3寄存器中。
4. 虛擬地址轉(zhuǎn)換成物理地址
一個(gè)虛擬地址大小為4字節(jié),其中包含找到物理地址的信息
虛擬地址分3部分
(1)31-22位(10位)是頁目錄中的索引
(2)21-12位(10位)是頁表中的索引
(2)11-0位(12位)是頁內(nèi)偏移
轉(zhuǎn)換過程:
首先通過CR3找到頁目錄所在物理頁-》根據(jù)虛擬地址中的31-22找到該頁目錄項(xiàng)-》通過該頁目錄項(xiàng)找到該虛擬地址對(duì)應(yīng)的頁表地址-》根據(jù)虛擬地址21-12找到物理頁的物理地址-》更具虛擬地址的11-0位作為偏移加上該物理頁的地址就找到了 該虛擬地址對(duì)應(yīng)的物理地址
CPU把虛擬地址轉(zhuǎn)換成物理地址:一個(gè)虛擬地址,大小4個(gè)字節(jié)(32bit),包含著找到物理地址的信息,分為3個(gè)部分:第22位到第31位這10位(最高10位)是頁目錄中的索引,第 12位到第21位這10位是頁表中的索引,第0位到第11位這12位(低12位)是頁內(nèi)偏移。對(duì)于一個(gè)要轉(zhuǎn)換成物理地址的虛擬地址,CPU首先根據(jù)CR3 中的值,找到頁目錄所在的物理頁。然后根據(jù)虛擬地址的第22位到第31位這10位(最高的10bit)的值作為索引,找到相應(yīng)的頁目錄項(xiàng)(PDE, page directory entry),頁目錄項(xiàng)中有這個(gè)虛擬地址所對(duì)應(yīng)頁表的物理地址。有了頁表的物理地址,根據(jù)虛擬地址的第12位到第21位這10位的值作為索引,找到該頁表中相應(yīng)的頁表項(xiàng)(PTE,page table entry),頁表項(xiàng)中就有這個(gè)虛擬地址所對(duì)應(yīng)物理頁的物理地址。最后用虛擬地址的最低12位,也就是頁內(nèi)偏移,加上這個(gè)物理頁的物理地址,就得到了該虛擬地址所對(duì)應(yīng)的物理地址。
-------------------------------------------------------------
一個(gè)頁目錄有1024項(xiàng),虛擬地址最高的10bit剛好可以索引1024項(xiàng)(2的10次方等于1024)。一個(gè)頁表也有1024項(xiàng),虛擬地址中間部分的 10bit,剛好索引1024項(xiàng)。虛擬地址最低的12bit(2的12次方等于4096),作為頁內(nèi)偏移,剛好可以索引4KB,也就是一個(gè)物理頁中的每個(gè)字節(jié)。
-------------------------------------------------------------
一個(gè)虛擬地址轉(zhuǎn)換成物理地址的計(jì)算過程就是,處理器通過CR3找到當(dāng)前頁目錄所在物理頁,取虛擬地址的高10bit,然后把這10bit右移2bit(因?yàn)槊總€(gè)頁目錄項(xiàng)4個(gè)字節(jié)長(zhǎng),右移2bit相當(dāng)于乘4)得到在該頁中的地址,取出該地址處PDE(4個(gè)字節(jié)),就找到了該虛擬地址對(duì)應(yīng)頁表所在物理頁,取虛擬地址第12位到第21位這10位,然后把這10bit右移2bit(因?yàn)槊總€(gè)頁表項(xiàng)4個(gè)字節(jié)長(zhǎng),右移2bit相當(dāng)于乘4)得到在該頁中的地址,取出該地址處的PTE(4個(gè)字節(jié)),就找到了該虛擬地址對(duì)應(yīng)物理頁的地址,最后加上12bit的頁內(nèi)偏移得到了物理地址。
-------------------------------------------------------------
32bit的一個(gè)指針,可以尋址范圍0x00000000-0xFFFFFFFF,4GB大小。也就是說一個(gè)32bit的指針可以尋址整個(gè)4GB地址空間的每一個(gè)字節(jié)。一個(gè)頁表項(xiàng)負(fù)責(zé)4K的地址空間和物理內(nèi)存的映射,一個(gè)頁表1024項(xiàng),也就是負(fù)責(zé)1024*4k=4M的地址空間的映射。一個(gè)頁目錄項(xiàng),對(duì)應(yīng)一個(gè)頁表。一個(gè)頁目錄有1024項(xiàng),也就對(duì)應(yīng)著1024個(gè)頁表,每個(gè)頁表負(fù)責(zé)4M地址空間的映射。1024個(gè)頁表負(fù)責(zé)1024*4M=4G的地址空間映射。一個(gè)進(jìn)程有一個(gè)頁目錄。所以以頁為單位,頁目錄和頁表可以保證4G的地址空間中的每頁和物理內(nèi)存的映射。
-------------------------------------------------------------
每個(gè)進(jìn)程都有自己的4G地址空間,從0x00000000-0xFFFFFFFF。通過每個(gè)進(jìn)程自己的一套頁目錄和頁表來實(shí)現(xiàn)。由于每個(gè)進(jìn)程有自己的頁目錄和頁表,所以每個(gè)進(jìn)程的地址空間映射的物理內(nèi)存是不一樣的。兩個(gè)進(jìn)程的同一個(gè)虛擬地址處(如果都有物理內(nèi)存映射)的值一般是不同的,因?yàn)樗麄兺鶎?duì)應(yīng)不同的物理頁。
4G地址空間中低2G,0x00000000-0x7FFFFFFF是用戶地址空間,4G地址空間中高2G,即0x80000000-0xFFFFFFFF 是系統(tǒng)地址空間。訪問系統(tǒng)地址空間需要程序有ring0的權(quán)限。
二. windows內(nèi)存原理
主要的內(nèi)容如下:
1.概述
2.虛擬內(nèi)存
3.物理內(nèi)存
4.映射
1.概述:
windows中 我們一般編程時(shí)接觸的都是線性地址 也就是我們所說的虛擬地址,然而很不幸在我不斷成長(zhǎng)的過程中發(fā)現(xiàn)原來線性地址是操作系統(tǒng)自己意淫出來的,根本就不是我們數(shù)據(jù)真實(shí)存在的地方.換句話說我們?cè)?x80000000(虛擬地址)的地方寫入了"UESTC"這個(gè)字符串,但是我們這個(gè)字符串并不真實(shí)存在于物理地址的0x80000000這里.再說了真實(shí)的物理地址是利用一段N長(zhǎng)的數(shù)組來定位的(額~看不懂這句話沒關(guān)系,一會(huì)看到物理地址那你就明白了).但是為什么windows乃至linux都需要采取這種方式來尋址呢?原因很簡(jiǎn)單 為了安全.聽說過保護(hù)模式吧?顧名思義就是這個(gè)模式下加入了保護(hù)系統(tǒng)安全的措施,同樣采用線性地址也是所謂的安全措施之一.
我們假設(shè)下如果沒有使用線性地址,那么我們可以直接訪問物理地址,但是這樣的話當(dāng)我們往內(nèi)存寫東西的時(shí)候操作系統(tǒng)無法檢查這塊內(nèi)存是否可寫,換句話說操作系統(tǒng)無法實(shí)現(xiàn)對(duì)頁面訪問控制.這點(diǎn)是很可怕的事情,就如win9x那樣沒事在應(yīng)用態(tài)往內(nèi)核地址寫東西,還有沒有天理了~~
由于操作系統(tǒng)的安全需要,催生了虛擬地址的應(yīng)用.在CPU中有個(gè)叫MMU(應(yīng)該是Memory Manage Unit 內(nèi)存管理單元)的東西,專門負(fù)責(zé)線性地址和物理地址之間的轉(zhuǎn)化.我們每次讀寫內(nèi)存,從CPU的結(jié)構(gòu)看來不是都要經(jīng)過ALU么,ALU拿到虛擬地址后扔給MMU轉(zhuǎn)化成物理地址后再把數(shù)據(jù)讀入寄存器中.過程就是如此.
2.虛擬內(nèi)存
我們編程時(shí)所面對(duì)的都是虛擬地址,對(duì)于每個(gè)進(jìn)程來說都擁有4G的虛擬內(nèi)存(小補(bǔ)充: 4G虛擬內(nèi)存中,高2G內(nèi)存屬于內(nèi)核部分,是所有進(jìn)程共有的,低2G內(nèi)存數(shù)據(jù)是進(jìn)程獨(dú)有的,每個(gè)進(jìn)程低2G內(nèi)存都不一樣),但注意的是虛擬地址是操作系統(tǒng)自己意淫出來的,打個(gè)比方就是說想法還未付諸實(shí)踐,所以不構(gòu)成任何資源損失.比如我們要在0x80000000的地方寫個(gè)"UESTC"的時(shí)候,操作系統(tǒng)就會(huì)將這個(gè)虛擬地址映射到一塊物理地址A中,你寫這塊虛擬地址就相當(dāng)于寫物理地址A.但是加入我們只申請(qǐng)了一段1KB的虛擬內(nèi)存空間,并未讀寫,系統(tǒng)是不會(huì)分配任何物理內(nèi)存的,只有當(dāng)虛擬內(nèi)存要使用時(shí)系統(tǒng)才會(huì)分配相應(yīng)的物理空間.
下面要說些細(xì)節(jié)點(diǎn)的了,其實(shí)說白了虛擬內(nèi)存的管理是由一堆數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)的,下面給出這些數(shù)據(jù)結(jié)構(gòu):
(懶得打那么多 就只打出重要的部分~~)
在EPROCESS中有個(gè)數(shù)據(jù)結(jié)構(gòu)如下:
typedef struct _MADDRESS_SPACE
{
PMEMORY_AREA MemoryAreaRoot ; //這個(gè)指針指向一顆二叉排序樹,想必學(xué)過數(shù)據(jù)結(jié)構(gòu)的朋友都曉得吧~~嘿嘿~~ 主要是這個(gè)情況下采用二叉排序樹能加快內(nèi)存的搜索速度
...
...
...
}MADDRESS_SPACE , *PMADDRESS_SPACE ;
然而這顆二叉排序樹的節(jié)點(diǎn)結(jié)構(gòu)結(jié)構(gòu)是這樣的:
typedef struct _MEMORY_AREA
{
PVOID StartingAddress ; //虛擬內(nèi)存段的起始地址
PVOID EndingAddress ; //虛擬內(nèi)存段的結(jié)束地址
struct _MEMORY_AREA *Parent ; //該節(jié)點(diǎn)的老爹
struct _MEMORY_AREA *LeftChild ; //該節(jié)點(diǎn)的左兒子
struct _MEMORY_AREA *RrightChild ; //該節(jié)點(diǎn)的左兒子
...
...
...
}MEMORY_AREA , *PMEMORY_AREA ;
這個(gè)節(jié)點(diǎn)內(nèi)主要記錄了已分配的虛擬內(nèi)存空間,如果要申請(qǐng)?zhí)摂M內(nèi)存空間就跑來這里創(chuàng)建個(gè)節(jié)點(diǎn)就好了,如果要?jiǎng)h除空間同樣把對(duì)應(yīng)節(jié)點(diǎn)刪除就好了.不過說來簡(jiǎn)單,其實(shí)還會(huì)涉及到很多操作,比如要平衡這棵樹什么的.
那么我們?cè)诜峙涮摂M內(nèi)存空間時(shí)系統(tǒng)會(huì)跑去找到這顆樹,然后通過一定算法遍歷這顆樹,找到符合條件的內(nèi)存空隙(未分配的內(nèi)存空間),創(chuàng)建個(gè)節(jié)點(diǎn)掛到這顆樹上,返回起始地址就完成了.
3.物理內(nèi)存
接下來就到物理內(nèi)存的東東了,其實(shí)呢 物理內(nèi)存的管理是基于一個(gè)數(shù)組來管理的,聽說過分頁機(jī)制吧,下面說下分頁.windows下分頁是4kb一頁 那么假設(shè)我們物理內(nèi)存有4GB 那么windows會(huì)將這4GB空間分頁,分成4GB/4KB = 1M頁 那么每一頁(就是4KB)的物理空間都由一個(gè)叫PHYSICAL_PAGE的數(shù)據(jù)結(jié)構(gòu)管理,這個(gè)數(shù)據(jù)結(jié)構(gòu)就不寫啦....一來我寫的手酸 二來看的人也累~~說說思路就好了.
接著以上假設(shè) 4GB的內(nèi)存 操作系統(tǒng)就會(huì)相應(yīng)產(chǎn)生一個(gè)PHYSICAL_PAGE數(shù)組,這個(gè)數(shù)組有多少個(gè)元素呢?有1M個(gè) 正好覆蓋了4GB的物理地址.這就是所謂的分頁機(jī)制的原型.說白了就是把內(nèi)存按4k劃分來管理.那么物理地址就好定位了,所謂的物理內(nèi)存地址就可以直接以數(shù)組下標(biāo)來表示,其實(shí)這里的物理內(nèi)存地址指的是物理內(nèi)存地址的頁面號(hào)... 具體地址還是要根據(jù)虛擬內(nèi)存地址的低12位和物理內(nèi)存的頁面號(hào)一起確定的
再說下物理內(nèi)存的管理吧,在內(nèi)核下有3個(gè)隊(duì)列 這些隊(duì)列內(nèi)的元素就是上邊所說的PHYSICAL_PAGE結(jié)構(gòu)
它們分別是:
A.已分配的內(nèi)存隊(duì)列 :存放正被使用的內(nèi)存
B.待清理的內(nèi)存隊(duì)列 :存放已被釋放的內(nèi)存,但是這些內(nèi)存未被清理(清零)
C.空閑隊(duì)列 :存放可使用的空閑內(nèi)存
系統(tǒng)管理流程如下:
1).每隔一段時(shí)間,系統(tǒng)會(huì)自動(dòng)從B隊(duì)列中提取隊(duì)列元素進(jìn)行清理,然后放入空閑隊(duì)列中.
2).每當(dāng)釋放物理內(nèi)存時(shí),系統(tǒng)會(huì)將要釋放的內(nèi)存從A隊(duì)列提取出來,放入B隊(duì)列中.
3).申請(qǐng)內(nèi)存時(shí),系統(tǒng)將要分配的內(nèi)存從C隊(duì)列中提取出來,放入A隊(duì)列中
4.映射
說到映射得先從虛擬內(nèi)存的32位地址說起.在CPU中存在個(gè)CR3寄存器,里面存放著每個(gè)進(jìn)程的頁目錄地址
我們可以把轉(zhuǎn)換過程分成幾步看
1.根據(jù)CR3(注:CR3中的值是物理地址)的值我們可以定位到當(dāng)前進(jìn)程的頁目錄基址,然后通過虛擬地址的高10位做偏移量來獲得指定的PDE(Page Directory Entry),PDE內(nèi)容有4字節(jié),高20位部分用來做頁表基址,剩下的比特位用來實(shí)現(xiàn)權(quán)限控制之類的東西了.系統(tǒng)只要檢測(cè)相應(yīng)的比特位就可以實(shí)現(xiàn)內(nèi)存的權(quán)限控制了.
2.通過PDE提供的基址加上虛擬內(nèi)存的中10位(21-12)做偏移量就找到了頁表PTE(Page Table Entry)地址,然后PTE的高20位就是物理內(nèi)存的基址了(其實(shí)就是那個(gè)PHYSICAL_PAGE數(shù)組的下標(biāo)號(hào)....),剩下的比特位同樣用于訪問控制之類的.
3.通過虛擬內(nèi)存的低12位加上PTE中高20位做基址就可以確定唯一的物理內(nèi)存了.
三. CPU段式內(nèi)存管理,邏輯地址如何轉(zhuǎn)換為線性地址
一個(gè)邏輯地址由兩部份組成,段標(biāo)識(shí)符: 段內(nèi)偏移量。段標(biāo)識(shí)符是由一個(gè)16位長(zhǎng)的字段組成,稱為段選擇符。其中前13位是一個(gè)索引號(hào)。后面3位包含一些硬件細(xì)節(jié),如圖:
最后兩位涉及權(quán)限檢查,本貼中不包含。
索引號(hào),或者直接理解成數(shù)組下標(biāo)——那它總要對(duì)應(yīng)一個(gè)數(shù)組吧,它又是什么東東的索引呢?這個(gè)東東就是“段描述符(segment descriptor)”,呵呵,段描述符具體地址描述了一個(gè)段(對(duì)于“段”這個(gè)字眼的理解,我是把它想像成,拿了一把刀,把虛擬內(nèi)存,砍成若干的截—— 段)。這樣,很多個(gè)段描述符,就組了一個(gè)數(shù)組,叫“段描述符表”,這樣,可以通過段標(biāo)識(shí)符的前13位,直接在段描述符表中找到一個(gè)具體的段描述符,這個(gè)描 述符就描述了一個(gè)段,我剛才對(duì)段的抽像不太準(zhǔn)確,因?yàn)榭纯疵枋龇锩婢烤褂惺裁礀|東——也就是它究竟是如何描述的,就理解段究竟有什么東東了,每一個(gè)段描 述符由8個(gè)字節(jié)組成,如下圖:
這些東東很復(fù)雜,雖然可以利用一個(gè)數(shù)據(jù)結(jié)構(gòu)來定義它,不過,我這里只關(guān)心一樣,就是Base字段,它描述了一個(gè)段的開始位置的線性地址。
Intel設(shè)計(jì)的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每個(gè)進(jìn)程自己的,就放在所謂的“局部段描述符表 (LDT)”中。那究竟什么時(shí)候該用GDT,什么時(shí)候該用LDT呢?這是由段選擇符中的T1字段表示的,=0,表示用GDT,=1表示用LDT。
GDT在內(nèi)存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT則在ldtr寄存器中。
好多概念,像繞口令一樣。這張圖看起來要直觀些:
首先,給定一個(gè)完整的邏輯地址[段選擇符:段內(nèi)偏移地址],
1、看段選擇符的T1=0還是1,知道當(dāng)前要轉(zhuǎn)換是GDT中的段,還是LDT中的段,再根據(jù)相應(yīng)寄存器,得到其地址和大小。我們就有了一個(gè)數(shù)組了。
2、拿出段選擇符中前13位,可以在這個(gè)數(shù)組中,查找到對(duì)應(yīng)的段描述符,這樣,它了Base,即基地址就知道了。
3、把Base + offset,就是要轉(zhuǎn)換的線性地址了。
還是挺簡(jiǎn)單的,對(duì)于軟件來講,原則上就需要把硬件轉(zhuǎn)換所需的信息準(zhǔn)備好,就可以讓硬件來完成這個(gè)轉(zhuǎn)換了。OK,來看看Linux怎么做的。
Linux的段式管理
Intel要求兩次轉(zhuǎn)換,這樣雖說是兼容了,但是卻是很冗余,呵呵,沒辦法,硬件要求這樣做了,軟件就只能照辦,怎么著也得形式主義一樣。
另一方面,其它某些硬件平臺(tái),沒有二次轉(zhuǎn)換的概念,Linux也需要提供一個(gè)高層抽像,來提供一個(gè)統(tǒng)一的界面。所以,Linux的段式管理,事實(shí)上只是“哄騙”了一下硬件而已。
按照Intel的本意,全局的用GDT,每個(gè)進(jìn)程自己的用LDT——不過Linux則對(duì)所有的進(jìn)程都使用了相同的段來對(duì)指令和數(shù)據(jù)尋址。即用戶數(shù)據(jù)段,用 戶代碼段,對(duì)應(yīng)的,內(nèi)核中的是內(nèi)核數(shù)據(jù)段和內(nèi)核代碼段。這樣做沒有什么奇怪的,本來就是走形式嘛,像我們寫年終總結(jié)一樣。
[Copy to clipboard] [ - ]
CODE:
#define GDT_ENTRY_DEFAULT_USER_CS 14
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
#define GDT_ENTRY_DEFAULT_USER_DS 15
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
#define GDT_ENTRY_KERNEL_BASE 12
#define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
#define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE + 1)
#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)
把其中的宏替換成數(shù)值,則為:
[Copy to clipboard] [ - ]
CODE:
#define __USER_CS 115 [00000000 1110 0 11]
#define __USER_DS 123 [00000000 1111 0 11]
#define __KERNEL_CS 96 [00000000 1100 0 00]
#define __KERNEL_DS 104 [00000000 1101 0 00]
方括號(hào)后是這四個(gè)段選擇符的16位二制表示,它們的索引號(hào)和T1字段值也可以算出來了
[Copy to clipboard] [ - ]
CODE:
__USER_CS index= 14 T1=0
__USER_DS index= 15 T1=0
__KERNEL_CS index= 12 T1=0
__KERNEL_DS index= 13 T1=0
T1均為0,則表示都使用了GDT,再來看初始化GDT的內(nèi)容中相應(yīng)的12-15項(xiàng)(arch/i386/head.S):
[Copy to clipboard] [ - ]
CODE:
.quad 0x00cf9a000000ffff /* 0x60 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff /* 0x68 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff /* 0x73 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff /* 0x7b user 4GB data at 0x00000000 */
按照前面段描述符表中的描述,可以把它們展開,發(fā)現(xiàn)其16-31位全為0,即四個(gè)段的基地址全為0。
這樣,給定一個(gè)段內(nèi)偏移地址,按照前面轉(zhuǎn)換公式,0 + 段內(nèi)偏移,轉(zhuǎn)換為線性地址,可以得出重要的結(jié)論,“在Linux下,邏輯地址與線性地址總是一致(是一致,不是有些人說的相同)的,即邏輯地址的偏移量字段的值與線性地址的值總是相同的。?。?!”
忽略了太多的細(xì)節(jié),例如段的權(quán)限檢查。呵呵。
Linux中,絕大部份進(jìn)程并不例用LDT,除非使用Wine ,仿真Windows程序的時(shí)候。
四.CPU頁式內(nèi)存管理
CPU的頁式內(nèi)存管理單元,負(fù)責(zé)把一個(gè)線性地址,最終翻譯為一個(gè)物理地址。從管理和效率的角度出發(fā),線性地址被分為以固定長(zhǎng)度為單位的組,稱為頁 (page),例如一個(gè)32位的機(jī)器,線性地址最大可為4G,可以用4KB為一個(gè)頁來劃分,這頁,整個(gè)線性地址就被劃分為一個(gè)tatol_page [2^20]的大數(shù)組,共有2的20個(gè)次方個(gè)頁。這個(gè)大數(shù)組我們稱之為頁目錄。目錄中的每一個(gè)目錄項(xiàng),就是一個(gè)地址——對(duì)應(yīng)的頁的地址。
另一類“頁”,我們稱之為物理頁,或者是頁框、頁楨的。是分頁單元把所有的物理內(nèi)存也劃分為固定長(zhǎng)度的管理單位,它的長(zhǎng)度一般與內(nèi)存頁是一一對(duì)應(yīng)的。
這里注意到,這個(gè)total_page數(shù)組有2^20個(gè)成員,每個(gè)成員是一個(gè)地址(32位機(jī),一個(gè)地址也就是4字節(jié)),那么要單單要表示這么一個(gè)數(shù)組,就要占去4MB的內(nèi)存空間。為了節(jié)省空間,引入了一個(gè)二級(jí)管理模式的機(jī)器來組織分頁單元。文字描述太累,看圖直觀一些:
如上圖,
1、分頁單元中,頁目錄是唯一的,它的地址放在CPU的cr3寄存器中,是進(jìn)行地址轉(zhuǎn)換的開始點(diǎn)。萬里長(zhǎng)征就從此長(zhǎng)始了。
2、每一個(gè)活動(dòng)的進(jìn)程,因?yàn)槎加衅洫?dú)立的對(duì)應(yīng)的虛似內(nèi)存(頁目錄也是唯一的),那么它也對(duì)應(yīng)了一個(gè)獨(dú)立的頁目錄地址?!\(yùn)行一個(gè)進(jìn)程,需要將它的頁目錄地址放到cr3寄存器中,將別個(gè)的保存下來。
3、每一個(gè)32位的線性地址被劃分為三部份,面目錄索引(10位):頁表索引(10位):偏移(12位)
依據(jù)以下步驟進(jìn)行轉(zhuǎn)換:
1、從cr3中取出進(jìn)程的頁目錄地址(操作系統(tǒng)負(fù)責(zé)在調(diào)度進(jìn)程的時(shí)候,把這個(gè)地址裝入對(duì)應(yīng)寄存器);
2、根據(jù)線性地址前十位,在數(shù)組中,找到對(duì)應(yīng)的索引項(xiàng),因?yàn)橐肓硕?jí)管理模式,頁目錄中的項(xiàng),不再是頁的地址,而是一個(gè)頁表的地址。(又引入了一個(gè)數(shù)組),頁的地址被放到頁表中去了。
3、根據(jù)線性地址的中間十位,在頁表(也是數(shù)組)中找到頁的起始地址;
4、將頁的起始地址與線性地址中最后12位相加,得到最終我們想要的葫蘆;
這個(gè)轉(zhuǎn)換過程,應(yīng)該說還是非常簡(jiǎn)單地。全部由硬件完成,雖然多了一道手續(xù),但是節(jié)約了大量的內(nèi)存,還是值得的。那么再簡(jiǎn)單地驗(yàn)證一下:
1、這樣的二級(jí)模式是否仍能夠表示4G的地址;
頁目錄共有:2^10項(xiàng),也就是說有這么多個(gè)頁表
每個(gè)目表對(duì)應(yīng)了:2^10頁;
每個(gè)頁中可尋址:2^12個(gè)字節(jié)。
還是2^32 = 4GB
2、這樣的二級(jí)模式是否真的節(jié)約了空間;
也就是算一下頁目錄項(xiàng)和頁表項(xiàng)共占空間 (2^10 * 4 + 2 ^10 *4) = 8KB。哎,……怎么說呢?。。。ㄕ娴臏p少了嗎?因該是增加了吧,(4 + 2^10 * 4 + 2 ^10 * 2 ^10 *4) = 4100KB+4Byte)
值得一提的是,雖然頁目錄和頁表中的項(xiàng),都是4個(gè)字節(jié),32位,但是它們都只用高20位,低12位屏蔽為0——把頁表的低12屏蔽為0,是很好理解的,因 為這樣,它剛好和一個(gè)頁面大小對(duì)應(yīng)起來,大家都成整數(shù)增加。計(jì)算起來就方便多了。但是,為什么同時(shí)也要把頁目錄低12位屏蔽掉呢?因?yàn)榘赐瑯拥牡览?,只?屏蔽其低10位就可以了,不過我想,因?yàn)?2>10,這樣,可以讓頁目錄和頁表使用相同的數(shù)據(jù)結(jié)構(gòu),方便。
本貼只介紹一般性轉(zhuǎn)換的原理,擴(kuò)展分頁、頁的保護(hù)機(jī)制、PAE模式的分頁這些麻煩點(diǎn)的東東就不啰嗦了……可以參考其它專業(yè)書籍。
Win32通過一個(gè)兩層的表結(jié)構(gòu)來實(shí)現(xiàn)地址映射,因?yàn)槊總€(gè)進(jìn)程都擁有私有的4G的虛擬內(nèi)存空間,相應(yīng)的,每個(gè)進(jìn)程都有自己的層次表結(jié)構(gòu)來實(shí)現(xiàn)其地址映射。
第一層稱為頁目錄,實(shí)際就是一個(gè)內(nèi)存頁,Win32的內(nèi)存頁有4KB大小,這個(gè)內(nèi)存頁以4個(gè)字節(jié)分為1024項(xiàng),每一項(xiàng)稱為“頁目錄項(xiàng)”(PDE);
第二層稱為頁表,這一層共有1024個(gè)頁表,頁表結(jié)構(gòu)與頁目錄相似,每個(gè)頁表也都是一個(gè)內(nèi)存頁,這個(gè)內(nèi)存頁以4KB的大小被分為1024項(xiàng),頁表的每一項(xiàng)被稱為頁表項(xiàng)(PTE),易知共有1024×1024個(gè)頁表項(xiàng)。每一個(gè)頁表項(xiàng)對(duì)應(yīng)一個(gè)物理內(nèi)存中的某一個(gè)“內(nèi)存頁”,即共有1024×1024個(gè)物理內(nèi)存頁,每個(gè)物理內(nèi)存頁為4KB,這樣就可以索引到4G大小的虛擬物理內(nèi)存。
如下圖所示(注下圖中的頁目錄項(xiàng)和頁表項(xiàng)的大小應(yīng)該是4個(gè)字節(jié),而不是4kB):
Win32提供了4GB大小的虛擬地址空間。因此每個(gè)虛擬地址都是一個(gè)32位的整數(shù)值,也就是我們平時(shí)所說的指針,即指針的大小為4B。它由三部分組成,如下圖:
這三個(gè)部分的第一部分,即前10位為頁目錄下標(biāo),用來尋址頁目錄項(xiàng),頁目錄項(xiàng)剛好1024個(gè)。找到頁目錄項(xiàng)后,找對(duì)頁目錄項(xiàng)對(duì)應(yīng)的的頁表。第二部分則是用來在頁表內(nèi)尋址,用來找到頁表項(xiàng),共有1024個(gè)頁表項(xiàng),通過頁表項(xiàng)找到物理內(nèi)存頁。第三部分用來在物理內(nèi)存頁中找到對(duì)應(yīng)的字節(jié),一個(gè)頁的大小是4KB,12位剛好可以滿足尋址要求。
具體的例子:
假設(shè)一個(gè)線程正在訪問一個(gè)指針(Win32的指針指的就是虛擬地址)指向的數(shù)據(jù),此指針指為0x2A8E317F,下圖表示了這一個(gè)過程:
0x2A8E317F的二進(jìn)制寫法為0010101010_0011100011_000101111111,為了方便我們把它分為三個(gè)部分。
首先按照0010101010尋址,找到頁目錄項(xiàng)。因?yàn)橐粋€(gè)頁目錄項(xiàng)為4KB,那么先將0010101010左移兩位,001010101000(0x2A8),用此下標(biāo)找到頁目錄項(xiàng),然后根據(jù)此頁目錄項(xiàng)定位到下一層的某個(gè)頁表。
然后按照0011100011尋址,在上一步找到頁表中尋找頁表項(xiàng)。尋址方法與上述方法類似。找到頁表項(xiàng)后,就可以找到對(duì)應(yīng)的物理內(nèi)存頁。
最后按照000101111111尋址,尋找頁內(nèi)偏移。
上面的假設(shè)的是此數(shù)據(jù)已在物理內(nèi)存中,其實(shí)判斷訪問的數(shù)據(jù)是否在內(nèi)存中也是在地址映射過程中完成的。Win32系統(tǒng)總是假設(shè)數(shù)據(jù)已在物理內(nèi)存中,并進(jìn)行地址映射。頁表項(xiàng)中有一位標(biāo)志位,用來標(biāo)識(shí)包含此數(shù)據(jù)的頁是否在物理內(nèi)存中,如果在的話,就直接做地址映射,否則,拋出缺頁中斷,此時(shí)頁表項(xiàng)也可標(biāo)識(shí)包含此數(shù)據(jù)的頁是否在調(diào)頁文件中(外存),如果不在則訪問違例,程序?qū)?huì)退出,如果在,頁表項(xiàng)會(huì)查出此數(shù)據(jù)頁在哪個(gè)調(diào)頁文件中,然后將此數(shù)據(jù)頁調(diào)入物理內(nèi)存,再繼續(xù)進(jìn)行地址映射。為了實(shí)現(xiàn)每個(gè)進(jìn)程擁有私有4G的虛擬地址空間,也就是說每個(gè)進(jìn)程都擁有自己的頁目錄和頁表結(jié)構(gòu),對(duì)不同進(jìn)程而言,即使是相同的指針(虛擬地址)被不同的進(jìn)程映射到的物理地址也是不同的,這也意味著在進(jìn)程之間傳遞指針是沒有意義的。
前面說了i386的二級(jí)頁管理架構(gòu),不過有些CPU,還有三級(jí),甚至四級(jí)架構(gòu),Linux為了在更高層次提供抽像,為每個(gè)CPU提供統(tǒng)一的界面。提供了一個(gè)四層頁管理架構(gòu),來兼容這些二級(jí)、三級(jí)、四級(jí)管理架構(gòu)的CPU。這四級(jí)分別為:
頁全局目錄PGD(對(duì)應(yīng)剛才的頁目錄)
頁上級(jí)目錄PUD(新引進(jìn)的)
頁中間目錄PMD(也就新引進(jìn)的)
頁表PT(對(duì)應(yīng)剛才的頁表)。
整個(gè)轉(zhuǎn)換依據(jù)硬件轉(zhuǎn)換原理,只是多了二次數(shù)組的索引罷了,如下圖:
那么,對(duì)于使用二級(jí)管理架構(gòu)32位的硬件,現(xiàn)在又是四級(jí)轉(zhuǎn)換了,它們?cè)趺茨軌騾f(xié)調(diào)地工作起來呢?嗯,來看這種情況下,怎么來劃分線性地址吧!
從硬件的角度,32位地址被分成了三部份——也就是說,不管理軟件怎么做,最終落實(shí)到硬件,也只認(rèn)識(shí)這三位老大。
從軟件的角度,由于多引入了兩部份,,也就是說,共有五部份?!尪蛹軜?gòu)的硬件認(rèn)識(shí)五部份也很容易,在地址劃分的時(shí)候,將頁上級(jí)目錄和頁中間目錄的長(zhǎng)度設(shè)置為0就可以了。
這樣,操作系統(tǒng)見到的是五部份,硬件還是按它死板的三部份劃分,也不會(huì)出錯(cuò),也就是說大家共建了和諧計(jì)算機(jī)系統(tǒng)。
這樣,雖說是多此一舉,但是考慮到64位地址,使用四層轉(zhuǎn)換架構(gòu)的CPU,我們就不再把中間兩個(gè)設(shè)為0了,這樣,軟件與硬件再次和諧——抽像就是強(qiáng)大呀?。?!
例如,一個(gè)邏輯地址已經(jīng)被轉(zhuǎn)換成了線性地址,0x08147258,換成二制進(jìn),也就是:
0000100000 0101000111 001001011000
內(nèi)核對(duì)這個(gè)地址進(jìn)行劃分
PGD = 0000100000
PUD = 0
PMD = 0
PT = 0101000111
offset = 001001011000
現(xiàn)在來理解Linux針對(duì)硬件的花招,因?yàn)橛布究床坏剿^PUD,PMD,所以,本質(zhì)上要求PGD索引,直接就對(duì)應(yīng)了PT的地址。而不是再到PUD和 PMD中去查數(shù)組(雖然它們兩個(gè)在線性地址中,長(zhǎng)度為0,2^0 =1,也就是說,它們都是有一個(gè)數(shù)組元素的數(shù)組),那么,內(nèi)核如何合理安排地址呢?
從軟件的角度上來講,因?yàn)樗捻?xiàng)只有一個(gè),32位,剛好可以存放與PGD中長(zhǎng)度一樣的地址指針。那么所謂先到PUD,到到PMD中做映射轉(zhuǎn)換,就變成了保 持原值不變,一一轉(zhuǎn)手就可以了。這樣,就實(shí)現(xiàn)了“邏輯上指向一個(gè)PUD,再指向一個(gè)PDM,但在物理上是直接指向相應(yīng)的PT的這個(gè)抽像,因?yàn)橛布静恢?道有PUD、PMD這個(gè)東西”。
然后交給硬件,硬件對(duì)這個(gè)地址進(jìn)行劃分,看到的是:
頁目錄 = 0000100000
PT = 0101000111
offset = 001001011000
嗯,先根據(jù)0000100000(32),在頁目錄數(shù)組中索引,找到其元素中的地址,取其高20位,找到頁表的地址,頁表的地址是由內(nèi)核動(dòng)態(tài)分配的,接著,再加一個(gè)offset,就是最終的物理地址了。
五. 存儲(chǔ)方式
保護(hù)模式現(xiàn)代操作系統(tǒng)的基礎(chǔ),理解他是我們要翻越的第一座山。保護(hù)模式是相對(duì)實(shí)模式而言的,他們是處理器的兩種工作方式。很久以前大家使用的dos就是運(yùn)行在實(shí)模式下,而現(xiàn)在的windows操作系統(tǒng)則是運(yùn)行在保護(hù)模式下。兩種運(yùn)行模式有著較大的不同,
實(shí)模式由于是由8086/8088發(fā)展而來因此他更像是一個(gè)運(yùn)行單片機(jī)的簡(jiǎn)單模式,計(jì)算機(jī)啟動(dòng)后首先進(jìn)入的就是實(shí)模式,通過8086/8088只有20根地址線所以它的尋址范圍只有2的20次冪,即1M。內(nèi)存的訪問方式就是我們熟悉的seg:offset邏輯地址方式,例如我們給出地址邏輯地址它將在cpu內(nèi)轉(zhuǎn)換為20的物理地址,即將seg左移4位再加上offset值。例如地址1000h:5678h,則物理地址為10000h+5678h=15678h。實(shí)模式在后續(xù)的cpu中被保留了下來,但實(shí)模式的局限性是很明顯的,由于使用seg:offset邏輯地址只能訪問1M多一點(diǎn)的內(nèi)存空間,在擁有32根地址線的cpu中訪問1M以上的空間則變得很困難。而且隨著計(jì)算機(jī)的不斷發(fā)展實(shí)模式的工作方式越來越不能滿足計(jì)算機(jī)對(duì)資源(存儲(chǔ)資源和cpu資源等等)的管理,由此產(chǎn)生了新的管理方式——保護(hù)模式。
80386及以上的處理器功能要大大超過其先前的處理器,但只有在保護(hù)模式下,處理器才能發(fā)揮作用。在保護(hù)模式下,全部32根地址線有效,可尋址4G的物理地址空間;擴(kuò)充的存儲(chǔ)分段機(jī)制和可選的存儲(chǔ)器分頁機(jī)制,不僅為存儲(chǔ)器共享和保護(hù)提供了硬件支持,而且為實(shí)現(xiàn)虛擬存儲(chǔ)器提供了硬件支持;支持多任務(wù);4個(gè)特權(quán)級(jí)和完善的特權(quán)級(jí)檢查機(jī)制,實(shí)現(xiàn)了數(shù)據(jù)的安全和保密。計(jì)算機(jī)啟動(dòng)后首先進(jìn)入的就是實(shí)模式,通過設(shè)置相應(yīng)的寄存器才能進(jìn)入保護(hù)模式(以后介紹)。保護(hù)模式是一個(gè)整體的工作方式,但分步討論由淺入深更利于學(xué)習(xí)。
存儲(chǔ)方式主要體現(xiàn)在內(nèi)存訪問方式上,由于兼容和IA32框架的限制,保護(hù)模式在內(nèi)存訪問上延用了實(shí)模式下的seg:offset的形式(即:邏輯地址),其實(shí)seg:offset的形式在保護(hù)模式下只是一個(gè)軀殼,內(nèi)部的存儲(chǔ)方式與實(shí)模式截然不同。在保護(hù)模式下邏輯地址并不是直接轉(zhuǎn)換為物理地址,而是將邏輯地址首先轉(zhuǎn)換為線性地址,再將線性地址轉(zhuǎn)換為物理地址。
線性地址是個(gè)新概念,但大家不要把它想的過于復(fù)雜,簡(jiǎn)單的說他就是0000000h~ffffffffh(即0~4G)的線性結(jié)構(gòu),是32個(gè)bite位能表示的一段連續(xù)的地址,但他是一個(gè)概念上的地址,是個(gè)抽象的地址,并不存在在現(xiàn)實(shí)之中。線性地址地址主要是為分頁機(jī)制而產(chǎn)生的。處理器在得到邏輯地址后首先通過分段機(jī)制轉(zhuǎn)換為線性地址,線性地址再通過分頁機(jī)制轉(zhuǎn)換為物理地址最后讀取數(shù)據(jù)。
分段機(jī)制是必須的,分頁機(jī)制是可選的,當(dāng)不使用分頁的時(shí)候線性地址將直接映射為物理地址,設(shè)立分頁機(jī)制的目的主要是為了實(shí)現(xiàn)虛擬存儲(chǔ)(分頁機(jī)制在后面介紹)。先來介紹一下分段機(jī)制,以下文字是介紹如何由邏輯地址轉(zhuǎn)換為線性地址。
分段機(jī)制在保護(hù)模式中是不能被繞過得,回到我們的seg:offset地址結(jié)構(gòu),在保護(hù)模式中seg有個(gè)新名字叫做“段選擇子”(seg..selector)。段選擇子、GDT、LDT構(gòu)成了保護(hù)模式的存儲(chǔ)結(jié)構(gòu),GDT、LDT分別叫做全局描述符表和局部描述符表,描述符表是一個(gè)線性表(數(shù)組),表中存放的是描述符。
“描述符”是保護(hù)模式中的一個(gè)新概念,它是一個(gè)8字節(jié)的數(shù)據(jù)結(jié)構(gòu),它的作用主要是描述一個(gè)段(還有其他作用以后再說),用描述表中記錄的段基址加上邏輯地址(sel:offset)的offset轉(zhuǎn)換成線性地址。描述符主要包括三部分:段基址(Base)、段限制(Limit)、段屬性(Attr)。一個(gè)任務(wù)會(huì)涉及多個(gè)段,每個(gè)段需要一個(gè)描述符來描述,為了便于組織管理,80386及以后處理器把描述符組織成表,即描述符表。在保護(hù)模式中存在三種描述符表 “全局描述符表”(GDT)、“局部描述符表”(LDT)和中斷描述符表(IDT)(IDT在以后討論)。
(1)全局描述符表GDT(Global Descriptor Table)在整個(gè)系統(tǒng)中,全局描述符表GDT只有一張,GDT可以被放在內(nèi)存的任何位置,但CPU必須知道GDT的入口,也就是基地址放在哪里,Intel的設(shè)計(jì)者門提供了一個(gè)寄存器GDTR用來存放GDT的入口地址,程序員將GDT設(shè)定在內(nèi)存中某個(gè)位置之后,可以通過LGDT指令將GDT的入口地址裝入此積存器,從此以后,CPU就根據(jù)此寄存器中的內(nèi)容作為GDT的入口來訪問GDT了。GDTR中存放的是GDT在內(nèi)存中的基地址和其表長(zhǎng)界限。
(2)段選擇子(Selector)由GDTR訪問全局描述符表是通過“段選擇子”(實(shí)模式下的段寄存器)來完成的,如圖三①步。段選擇子是一個(gè)16位的寄存器(同實(shí)模式下的段寄存器相同)
段選擇子包括三部分:描述符索引(index)、TI、請(qǐng)求特權(quán)級(jí)(RPL)。他的index(描述符索引)部分表示所需要的段的描述符在描述符表的位置,由這個(gè)位置再根據(jù)在GDTR中存儲(chǔ)的描述符表基址就可以找到相應(yīng)的描述符(如圖三①步)。然后用描述符表中的段基址加上邏輯地址(SEL:OFFSET)的OFFSET就可以轉(zhuǎn)換成線性地址(如圖三②步),段選擇子中的TI值只有一位0或1,0代表選擇子是在GDT選擇,1代表選擇子是在LDT選擇。請(qǐng)求特權(quán)級(jí)(RPL)則代表選擇子的特權(quán)級(jí),共有4個(gè)特權(quán)級(jí)(0級(jí)、1級(jí)、2級(jí)、3級(jí))。例如給出邏輯地址:21h:12345678h轉(zhuǎn)換為線性地址
a. 選擇子SEL=21h=0000000000100 0 01b 他代表的意思是:選擇子的index=4即100b選擇GDT中的第4個(gè)描述符;TI=0代表選擇子是在GDT選擇;左后的01b代表特權(quán)級(jí)RPL=1
b. OFFSET=12345678h若此時(shí)GDT第四個(gè)描述符中描述的段基址(Base)為11111111h,則線性地址=11111111h+12345678h=23456789h
(3)局部描述符表LDT(Local Descriptor Table)局部描述符表可以有若干張,每個(gè)任務(wù)可以有一張。我們可以這樣理解GDT和LDT:GDT為一級(jí)描述符表,LDT為二級(jí)描述符表。如圖五
LDT和GDT從本質(zhì)上說是相同的,只是LDT嵌套在GDT之中。LDTR記錄局部描述符表的起始位置,與GDTR不同LDTR的內(nèi)容是一個(gè)段選擇子。由于LDT本身同樣是一段內(nèi)存,也是一個(gè)段,所以它也有個(gè)描述符描述它,這個(gè)描述符就存儲(chǔ)在GDT中,對(duì)應(yīng)這個(gè)表述符也會(huì)有一個(gè)選擇子,LDTR裝載的就是這樣一個(gè)選擇子。LDTR可以在程序中隨時(shí)改變,通過使用lldt指令。如圖五,如果裝載的是Selector 2則LDTR指向的是表LDT2。舉個(gè)例子:如果我們想在表LDT2中選擇第三個(gè)描述符所描述的段的地址12345678h。
1. 首先需要裝載LDTR使它指向LDT2 使用指令lldt將Select2裝載到LDTR
2. 通過邏輯地址(SEL:OFFSET)訪問時(shí)SEL的index=3代表選擇第三個(gè)描述符;TI=1代表選擇子是在LDT選擇,此時(shí)LDTR指向的是LDT2,所以是在LDT2中選擇,此時(shí)的SEL值為1Ch(二進(jìn)制為11 1 00b)。OFFSET=12345678h。邏輯地址為1C:12345678h
3. 由SEL選擇出描述符,由描述符中的基址(Base)加上OFFSET可得到線性地址,例如基址是11111111h,則線性地址=11111111h+12345678h=23456789h
4. 此時(shí)若再想訪問LDT1中的第三個(gè)描述符,只要使用lldt指令將選擇子Selector 1裝入再執(zhí)行2、3兩步就可以了(因?yàn)榇藭r(shí)LDTR又指向了LDT1)
由于每個(gè)進(jìn)程都有自己的一套程序段、數(shù)據(jù)段、堆棧段,有了局部描述符表則可以將每個(gè)進(jìn)程的程序段、數(shù)據(jù)段、堆棧段封裝在一起,只要改變LDTR就可以實(shí)現(xiàn)對(duì)不同進(jìn)程的段進(jìn)行訪問。
存儲(chǔ)方式是保護(hù)模式的基礎(chǔ),學(xué)習(xí)他主要注意與實(shí)模式下的存儲(chǔ)模式的對(duì)比,總的思想就是首先通過段選擇子在描述符表中找到相應(yīng)段的描述符,根據(jù)描述符中的段基址首先確定段的位置,再通過OFFSET加上段基址計(jì)算出線性地址.
聯(lián)系客服