天天看點

x86從實模式到保護模式 pdf_Linux核心淺析-X86體系結構

預想了解Linux核心的一些原理,必先了解X86體系結構。x86體系基本等同于cpu的使用說明書,Linux的底層就是cpu,如果不了解底層提供的功能點,很多實作方式都搞不懂。就好像我們做一個需求,不知道底層有哪些接口可用,那需求做起來就太困難了。

x86架構起源于intel8086處理器,是intel公司70年代生産的16位處理器,推出前幾年都不溫不火,直到找到自己的業務方:ibm pc。ibm pc的研發沒有交給最牛的華生實驗室,而是交給另外一個團隊,且要求一年交貨,由于工期緊張,他們直接采用intel8086作為cpu,微軟ms-dos作為os。由于ibm pc的大賣,hp、康柏等公司開始做相容機,也是采用8086處理器,是以intel成為pc行業的标準,也就是x86。

1、體系結構總覽

x86從實模式到保護模式 pdf_Linux核心淺析-X86體系結構

cpu的核心邏輯是輸入資料 + 指令 = 輸出結果,那就要解決這麼幾個問題:1)get指令。2)get輸入資料。3)運算。4)store輸出結果。其中第3步運算邏輯基本在cpu的alu中完成,不需要關注。其中1、2、4主要包含get、store兩種操作,其實解決了資料的存儲、尋址,get和store也就ok了。

1.1 指令和資料的存儲

一個程序的能夠看到的記憶體空間是邏輯位址,最後操作時會映射到實體位址,一個程序的記憶體邏輯位址又分為以下幾個segment。

1)Text Segment:存放程序運作的指令,程序建立時,會将代碼編譯後的指令放到這個位置,也是靜态的,運作時不會改變。

2)Data Segment:代碼中已初始化值的變量。Bss是未初始化值的靜态變量。

3)heap:堆,用于進行記憶體的動态配置設定。

4)stack:函數運作的棧。

1.2 指令和資料的尋址

X86的尋址,在cpu的視角看,是

段基址 + 段偏移

位址(至于為什麼用這麼挫的方式,在下一節段位址中描述)。以下是段基址寄存器:

1)CS:存儲代碼段的基址。

2)DS:存儲資料段的基址。

3)SS:存儲運作棧的基址。

段偏移位址寄存器:

1)IP:指令指針寄存器,根據CS + IP擷取到的指令存儲EIP(指令寄存器)中,供ALU使用。

2)EBP:棧基指針寄存器。

3)ESP:棧頂指針寄存器。

4)資料段的偏移位址一般存儲在通用寄存器中,擷取的資料一般也存到通用寄存器中,供ALU使用。

有了尋址方式,CPU直接将位址發送到位址總線上,即可以從資料總線收到資料。

2、分段記憶體管理

8086是16位的cpu,最大支援尋址空間為64K,但如何支援20位的位址總線呢?即支援1M的尋址空間(估計是ibm提的需求)。那怎麼辦呢?16位撐死就64K。那就兩個位址,段基址和段偏移,湊成20位位址。

邏輯位址 = 段基址 * 16(左移四位) + 段偏移位址
           

當32位cpu出現時,這種設計就a round peg in a square hole,但考慮到原有架構上已經有很多硬體和軟體,不得不做相容。但現在好不容易更新為32位,再用原有模式也無法支援4G的空間。于是IA32(32位處理器架構簡稱)和作業系統産生商量後達成一緻,IA32推出的标準:

1)尋址方式仍然為

線性位址

->

邏輯位址(分段模式) -> 實體位址

,保留分段模式。

2)在記憶體中建構兩個存儲段基址的table,

GDT(Global Descriptor Table,全局描述表)

,存儲核心程式的段基址,由寄存器GDTR儲存GDT入口位址,

LDT(Local Descriptor Table,本地描述符表)

,由寄存器LRTR指向LRT的入口,存儲各個使用者程序的段基址。table中的item存儲

段描述符

,其中包括段基址、段界限和權限相關資訊。

3)CS、DS等段基址寄存器仍然為16位(相容以前),但改為存儲

段選擇子

,即table的index,這樣段基址就存儲于記憶體中,為以後擴充也打下基礎。于是段基址存儲位址時叫“

實模式

”,存儲段選擇子叫“

保護模式

”,系統啟動時都是先處于實模式,當你需要更多記憶體時,切換到保護模式。通過切換模式做到無縫相容。

以上是硬體商給的解決方案,但Linux為了相容spark、arm等體系架構(這些體系都沒有分段位址模式),也是為了少一次位址映射,是以将所有段基址都設定為0,是以段偏移位址 = 線性位址(記憶體尋址的原理後面會細講),也就是所有程序的任一段基址都為0,是以Linux沒必要使用LDT,僅用了GDT。

3、函數調用棧

代碼都用函數來承載指令,通過函數調用來承載指令的跳轉。而 函數的運作是基于棧的,一個函數就是一個棧幀。

int bar(int c, int d)
{
    int e = c + d;
    return e;
}
int foo(int a, int b)
{
    return bar(a, b);
}
int main(void)
{
    foo(2, 3);
    return 0;
}
           
x86從實模式到保護模式 pdf_Linux核心淺析-X86體系結構

1)stack segment處于線性位址的高位址,且向下擴充,是以esp棧頂位址是向低位址擴充,esp棧基位址反而處于高位址。

2)當A->B函數時,會進行如下操作:

1、push eip:儲存目前的EIP(指向A函數的某條指令),然後通過jmp或call指令将EIP設

置為B函數的起始指令。這樣當B函數傳回時,再将該EIP的值pop 到EIP寄存器中,使得A

函數可以繼續運作以下的指令。

2、push ebp:将A目前的ebp壓棧,有兩個作用:1)友善B函數擷取其入參。2)當B傳回

時,将ebp指向A的棧基址。

3、mov esp ebp:将目前的esp複制給ebp,相當于ebp從指向A的棧基址指向B的棧基

址,開始運作B函數的指令。

3)目前函數的入參,是在上一個調用函數的棧幀中,通過本棧幀中儲存的調用者的ebp和參

數的size進行計算入參的位址。

4. 狀态寄存器

eflags是狀态寄存器,控制單元會根據其值變更執行邏輯。比如IF就是中斷标志,當陷入中斷時,會通關關閉eflags.if标志來關中斷,此處先提一下,後面中斷會具體講到。

x86從實模式到保護模式 pdf_Linux核心淺析-X86體系結構

在看X86體系架構時,有些感觸:

1、業務很重要。對于intel,沒有保住ibm pc的大腿,現在可能也沒有intel,也沒有x86。

2、業務提的需求,很多時候條件都不具備,需要工程師是做trade off。比如16位cpu支援20位的尋址,分段模式不得不說是一個臨時方案,但為其赢得了市場。為了技術潔癖去做完美的方案是沒有出路的。對于架構師,當你知道了業務的不美好和代碼的龌蹉後,還能依然想把這個系統做好。

3、如果要做開放體系,相容是必須的,哪怕是原來做個的臨時方案。對于開放體系,一旦釋出方案,就意味着必須相容到底。好比java的泛型,現在還為了相容早期非泛型版本遺留着許多不完美。