Linux后端程序成長(zhǎng)關(guān)鍵技術(shù)之底層體系結(jié)構(gòu)
計(jì)算機(jī)程序的Bug千奇百怪,要想能順利的解決疑難雜癥,必須對(duì)計(jì)算機(jī)的底層原理非常熟悉。比如在實(shí)際生產(chǎn)中不光我們的應(yīng)用會(huì)出問題,操作系統(tǒng)也可能有Bug,硬件也可能有Bug。因此,只有更加深入的理解了原理,才能更加方便我們解決問題。
本文對(duì)計(jì)算機(jī)的體系結(jié)構(gòu)底層原理進(jìn)行簡(jiǎn)要的介紹。這些知識(shí)對(duì)于幫助我們解決疑難問題會(huì)有很大的幫助。做程序開發(fā)應(yīng)該深入原理,不僅要知其然,還要知其所以然。
計(jì)算機(jī)的工作模式

- 對(duì)于一個(gè)計(jì)算機(jī)來(lái)說(shuō),最核心的是CPU,CPU是計(jì)算機(jī)的大腦,所有設(shè)備都圍繞其展開
- CPU通過(guò)總線(Bus)與其他設(shè)備連接,在這些設(shè)備中,最為重要的是內(nèi)存(Memory)
- 單靠CPU是無(wú)法完成計(jì)算任務(wù)的,很多復(fù)雜的計(jì)算任務(wù)都需要將中間結(jié)果保存下來(lái),然后基于中間結(jié)果進(jìn)行下一步的計(jì)算
- CPU和內(nèi)存是完成計(jì)算的核心組件
CPU本身無(wú)法保存這么多的中間結(jié)果,因此需要依賴于內(nèi)存
CPU
- CPU包含三部分:運(yùn)算單元、數(shù)據(jù)單元和控制單元
- 運(yùn)算單元只管計(jì)算,但它不知道應(yīng)該算哪些數(shù)據(jù),運(yùn)算結(jié)果應(yīng)該放在哪里
- 運(yùn)算單元計(jì)算的數(shù)據(jù)如果每次都要經(jīng)過(guò)總線,直接到內(nèi)存里面現(xiàn)拿,速度會(huì)很慢,因此出現(xiàn)了數(shù)據(jù)單元
- 數(shù)據(jù)單元包括CPU內(nèi)部的緩存和寄存器組,空間很小,但速度很快
- 控制單元是一個(gè)統(tǒng)一的指揮中心,可以獲得下一條指令,然后執(zhí)行這條指令
這個(gè)指令會(huì)指導(dǎo)運(yùn)算單元取出數(shù)據(jù)單元中的某幾個(gè)數(shù)據(jù),計(jì)算出結(jié)果,然后放在數(shù)據(jù)單元的某個(gè)地方
計(jì)算過(guò)程
1. 每個(gè)進(jìn)程都有一個(gè)程序放在硬盤上,是二進(jìn)制的,在里面存儲(chǔ)的是一行一行的指令,這些指令會(huì)操作一些數(shù)據(jù)
2. 進(jìn)程開始運(yùn)行,會(huì)有獨(dú)立的內(nèi)存空間,相互隔離但不連續(xù) - 程序會(huì)分別加載到進(jìn)程A和進(jìn)程B的內(nèi)存空間里面,形成各自的代碼段
3. 程序在運(yùn)行過(guò)程中要操作的數(shù)據(jù)和產(chǎn)生的計(jì)算結(jié)果,都會(huì)放在數(shù)據(jù)段(內(nèi)存)里

4. 在CPU的控制單元里面,有一個(gè)指令指針寄存器,記錄的是下一條指令在內(nèi)存中的地址 - 控制單元會(huì)不停地將代碼段的指令拿進(jìn)來(lái),先放入指令寄存器
5. 指令的組成部分:做什么操作 + 操作哪些數(shù)據(jù) - 要執(zhí)行指令,需要將***部分交給運(yùn)算單元,將第二部分交給數(shù)據(jù)單元
6. 數(shù)據(jù)單元根據(jù)數(shù)據(jù)的地址,從數(shù)據(jù)段里讀取數(shù)據(jù)到數(shù)據(jù)寄存器,最終會(huì)有指令將數(shù)據(jù)寫回到內(nèi)存中的數(shù)據(jù)段
7. CPU里有兩個(gè)寄存器,專門保存當(dāng)前處理進(jìn)程的代碼段起始地址和數(shù)據(jù)段起始地址,圖中的當(dāng)前進(jìn)程為進(jìn)程A
8. CPU和內(nèi)存通過(guò)總線傳輸數(shù)據(jù),總線上有兩類數(shù)據(jù) - 地址總線(Address Bus):地址數(shù)據(jù),位數(shù)決定了能訪問的地址有多廣 - 數(shù)據(jù)總線(Data Bus):真正的數(shù)據(jù),位數(shù)決定了一次性能拿多少數(shù)據(jù)
x86架構(gòu)
型號(hào)

8086的原理

通用寄存器
- 為了暫存數(shù)據(jù),8086處理器內(nèi)部有8個(gè)16位的通用寄存器,屬于CPU內(nèi)部的數(shù)據(jù)單元
- 分別是AX、BX、CX、DX、SP、BP、SI和DI
- 其中AX、BX、CX和DX可以分成兩個(gè)8位的寄存器來(lái)使用,其中H就是High,L就是Low
- 這樣,比較長(zhǎng)的數(shù)據(jù)也能暫存,比較短的數(shù)據(jù)也能暫存
控制單元
- IP寄存器(Instruction Pointer Register)即指令指針寄存器
- 指向代碼段中下一條指令的位置
- CPU會(huì)根據(jù)IP寄存器不斷地將指令從內(nèi)存的代碼段中,加載到CPU的指令隊(duì)列中,然后交給運(yùn)算單元去執(zhí)行
- 切換進(jìn)程
- 每個(gè)進(jìn)程都分為代碼段和數(shù)據(jù)段
- 為了指向不同進(jìn)程的地址空間,有4個(gè)16位的段寄存器,分別是CS、DS、SS和ES
- CS(Code Segment Register)是代碼段寄存器,通過(guò)它可以找到代碼在內(nèi)存中的位置
- DS(Data Segment Register)是數(shù)據(jù)段寄存器,通過(guò)它可以找到數(shù)據(jù)在內(nèi)存中的位置
- SS(Stack Segment Register)是棧寄存器,但凡與函數(shù)調(diào)用相關(guān)的操作,都與棧緊密相關(guān)
- A調(diào)用B,B調(diào)用C
- 當(dāng)A調(diào)用B的時(shí)候,要執(zhí)行B函數(shù)的邏輯,因而A運(yùn)行的相關(guān)信息會(huì)被push到棧里
- 當(dāng)B調(diào)用C的時(shí)候,同理,B運(yùn)行的相關(guān)信息會(huì)被push到棧里,然后才運(yùn)行C函數(shù)的邏輯
- 當(dāng)C運(yùn)行完畢后,先pop出來(lái)的是B,B接著調(diào)用C函數(shù)之后的指令運(yùn)行下去
- B運(yùn)行完畢后,再pop出來(lái)的是A,A接著運(yùn)行,直至結(jié)束

加載內(nèi)存數(shù)據(jù)
- 如果需要加載內(nèi)存中的數(shù)據(jù),可以通過(guò)DS找到內(nèi)存中的數(shù)據(jù),加載到通用寄存器
- 對(duì)于一個(gè)段,有一個(gè)起始地址,而段內(nèi)的具體位置,稱為偏移量
- CS和DS都存放著一個(gè)段的起始地址
- 代碼段的偏移量放在IP寄存器
- 數(shù)據(jù)段的偏移量放在通用寄存器
- CS和DS都是16位的(起始地址),IP寄存器和通用寄存器也都是16位的(偏移量),但8086的地址總線是20位的
- 湊20位:起始地址 << 4 + 偏移量
- 無(wú)論真正的內(nèi)存有多大,對(duì)于只有20位地址總線的8086來(lái)說(shuō),能夠區(qū)分的地址也就2^20=1M(尋址單位為Byte)
- 如果想訪問1M+X的地方,在總線上超過(guò)20位的部分根本發(fā)不出去,***訪問的還是1M內(nèi)的X位置
- 偏移量只有16位的,所以一個(gè)段的***大小為2^16=64K
- 因此對(duì)于8086的CPU來(lái)說(shuō),最多只能訪問1M的內(nèi)存空間,還要分成多個(gè)段,每個(gè)段***為64K
32位處理器
- 在32位的CPU中,有32根地址總線,可以訪問2^32=4G的內(nèi)存
- x86架構(gòu)是開放的,因此32位的CPU需要兼容原來(lái)的架構(gòu)
兼容

1. 通用寄存器 - 將8個(gè)16位的通用寄存器擴(kuò)展到8個(gè)32位的通用寄存器,但依然保留16位和8位的使用方式 - 高16位不能分成兩個(gè)8位使用,因?yàn)檫@是不兼容的
2. IP寄存器 - 指向下一條指令的指令指針寄存器IP,會(huì)擴(kuò)展成32位的,同樣兼容16位
3. 段寄存器(Segment Register) - CS、DS、SS和ES仍然是16位,但不再是段的起始地址,段的起始地址放在內(nèi)存的某個(gè)地方(表格)
- 表格中的一項(xiàng)是段描述符(Segment Descriptor),里面才是段真正的起始地址 - 而段寄存器里面保存的是這個(gè)表格中的某一項(xiàng),稱為選擇子(Selector)
- 獲取段起始地址的流程:先間接地從段寄存器中找到表格中的一項(xiàng),再?gòu)谋砀裰械囊豁?xiàng)拿到段真正的起始地址
- 為了快速拿到段的起始地址,段寄存器會(huì)從內(nèi)存中拿到CPU的描述符高速緩存器中
- 這種模式與8086的模式不兼容,但非常靈活,可以保持未來(lái)的兼容性
實(shí)模式 VS 保護(hù)模式
- 在32位的架構(gòu)下,將前一種模式稱為實(shí)模式(Real Pattern),后一種模式稱為保護(hù)模式(Protected Pattern)
- 系統(tǒng)剛剛啟動(dòng)的時(shí)候,CPU處于實(shí)模式,此時(shí)和原來(lái)的模式是兼容的。即32位的CPU,也支持在原來(lái)的模式下運(yùn)行,速度會(huì)快一點(diǎn)
- 當(dāng)需要更多內(nèi)存時(shí),可以遵循一定的規(guī)則,進(jìn)行一系列的操作,然后切換到保護(hù)模式,就能夠用到32位CPU更強(qiáng)大的能力
- 如果不能無(wú)縫兼容,但通過(guò)切換模式兼容,也是可以接受的
系統(tǒng)交互

常用匯編指令
mov, call, jmp, int, ret, add, or, xor, shl, shr, push, pop, inc, dec, sub, cmp