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

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

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

8086的原理

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

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

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

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