1. 段暫存器
上圖是Intel X86系列的CPU,在Pentium之前的發展過程:
- 4004:8位CPU
- 8086~80286:16位CPU,即邏輯運算單元(ALU)的位數為16,但地址匯流排為20位,另外80286開始支援保護模式,但只支援真實模式到保護模式的單向切換
- 80386:32位CPU,
ALU為32位,地址匯流排也設計成32位了
我有個自我認識,就是在學習的時候,比較喜歡鑽牛角尖,比如我就為這些問題糾結過:
- 8086為什麼要把地址匯流排設計成20位?
- 地址匯流排既然可以設計成20位,為什麼不設計成32位?
- 地址匯流排可以設計成20位,為什麼不把ALU也設計成20位?
- 等等..
導致本來的目的是學習核心,結果差點被這些不相關的問題嚇跑了。不過應該會有人比我更有這種性格:在第一章即使被不相關的問題卡住,也絕不繼續學第二章!確實,自學一樣很難的東西時,需要有這種性格,有時候只有逼著自己更加深入的去體會,才能推翻之前的錯覺,然後很多東西都也隨之豁然開朗了;但如果過於執著,就變成鑽牛角尖了,核心中凝聚了非常多抽象和複雜的設計,很多時候不是按照順序學習一遍,就能明白ABC,而是要一知半解的重複著ABC、ACB、BCA、..,這樣去學習。
特別是有些明顯偏離學習目標的問題,要懂得放下,或者猜個差不多的答案,安撫下自己的牛脾氣就可以了。比如以上問題我猜測的原因是:
- 地址匯流排設計成16位,只能支援64KB的記憶體,已經不滿足當時計算機使用者的需求了
- 定址能力大了,主機板上實際用於儲存的電晶體也要相應多才行,但當時的CPU製作工藝水平還有限
- ALU涉及到的電路複雜,不能像地址匯流排一樣擴充套件成20位
以上是我學習核心的一點感受,接下來介紹正題:
1.1. 段暫存器誕生的原因
地址值經常要參與運算,比如指令跳轉時,要加偏移值,而8086 CPU中,ALU是16位,沒辦法一次性運算20位的地址值,所以設計了段暫存器,20位地址由兩個16位的儲存單元儲存,並且通過"段暫存器*16+段偏移",可以計算得到20位的實際地址。
1.2. 段暫存器為什麼不只設計成4位
4位只能指定2^4個段基址,1MB空間只能被劃分為0-64KB、64KB-128KB、..、960KB-1MB這樣的16個段,每個段64KB,而16位可以指定2^16個段基址,從離每個段開始很近的地方,就可以指定為另外一個段,可以為每個程式安排自己獨立的段,記憶體佈局整體上會更靈活和更清晰。
其實,以上所有亂七八糟的問題和理解都不重要,重要的只有一個:段暫存器本質上是為了解決ALU和地址匯流排位數不一致的問題。
2. 80386 CPU
2.1. 真實模式、保護模式
真實模式和保護模式,指的是CPU硬體的不同狀態,就像《秦時明月》中天明拿的"非攻"一樣,它是墨家打造的一件多功能兵器,可以變換成不同的形狀,同樣一塊黑鐵,在不同形狀下,用途也不同。同樣的,CPU內部的一些元件,在真實模式和保護模式下,作用可能也不相同。
2.2. 80386的選擇
80386的ALU和地址匯流排都是32位,按道理段暫存器在80386中就沒有存在的意義了,但是為了相容之前的CPU,必須保留段暫存器,否則可以在之前的CPU執行的程式,到80386上就執行不了了,因為它連段暫存器都沒有。
80386除了考慮相容,還要擴充套件一個重要的特性,就是支援保護模式。還是拿"非攻"舉例,
"非攻"中的所有器材,在各種形態下,都會發揮作用,這叫物盡其用,特別對於CPU來說,如果切換到保護模式,段暫存器就空閒在一旁不起任何作用,一方面從製作工藝的角度不允許,因為CPU中每一寸空間都很寶貴,另一方面從生產成本上也不允許,資產家希望花了成本做出來的東西,要竭其所能的工作,不能閒著。
結論:Intel工程師不能在80386中丟棄段暫存器,同時對保護模式的設計思路,還得想辦法把段暫存器用上。
3. 保護模式下的段式管理
3.1. 特權指令
Linux作業系統以及系統中執行的一些管理平臺程式,都有使用者管理的功能,並且有root使用者、普通使用者之分,這些都是通過純軟體的設計實現的。特權指令、非特權指令針對的是硬體層面,特權指令就是指,在保護模式下,標誌暫存器中的某些位是1,才能執行,否則就讓執行這條指令的程式異常退出。
比如我記得很小的時候,有些自行車剛買回來,就自帶一把鎖,同樣是"騎"這個動作,如果鎖是開的,就能騎走,如果鎖是關的,就騎不走,這就是通過硬體的設計,可以導致同一條指令執行結果不同的道理。
為了新增保護模式,80386相應的新增了一些暫存器和特權指令,由於是新增的,以及"權"的資訊怎麼存、怎麼使用,都不用考慮對低型號CPU的相容性問題。
3.2. 保護模式下段暫存器含義
類似於"非攻"中元件,在不同形態中可以設計為完全不同的作用,段暫存器在保護模式中,也已經不再跟真實模式一樣純粹指向一個段基址了,而是設計成上圖中的結構:
- 高13位:描述符表索引/陣列下標(詳見筆記3.4節)
- TI位:標識使用GDTR指向的描述符表,還是LDTR指向的描述符表(詳見筆記3.4節)
- 最低2位:RPL(Request Privilege Level)
,相關概念還有DPL(Descriptor Privilege Level)
、CPL(Current Privilege Level),含義都由硬體設計定義,用於實現特權指令,軟體設計要遵守硬體的規範(詳見筆記3.4節)
3.3. 段選擇子
GDTR和LDTR是80386新增的暫存器,分別用於指向全域性描述符表和區域性描述符表,這樣,段暫存器的TI位用於表明使用哪個表,再配合高13位的索引,就可以指定一個描述符。
描述符的結構為:
-
B31~B24 + B23~B16 + B15~B0:段基址,共32位
- L19~L16、L15~L0:段長度,共20位
- G:0=段長度單位為1,1=段長度單位為4KB
- D:存取記憶體預設大小,0=16位,1=32位(暫時不用理解,opcode 66H字首可以切換指令存取記憶體的預設大小)
- A:是否可供系統軟體使用(暫時不用理解)
- P + DPL + S + type:
- P:是否在記憶體中(暫時不用理解,用於實現"交換分割槽"功能)
- DPL:本段特權(詳見筆記3.4節)
- S:0=系統描述符(比如指向任務狀態段TTS),1=程式碼或資料段描述符(暫時不用理解)
- E:0=資料段,1=程式碼段(暫時不用理解)
- D(E=0):0=堆區,1=棧區;C(E=1):0=忽視特權級,1=遵守特權級(暫時不用理解)
- W(E=0):0=不可寫,1=可寫;R(E=1):0=不可讀,1=可讀(暫時不用理解)
- A:CPU任何一次訪問該描述符指向的段,都會通過硬體層的邏輯,將A設定為1(暫時不用理解,用於選擇換出到交換分割槽的段)
這個結構中的大部分成員,隨著後面的學習,都會逐個接觸到,那麼除了暫時不用理解的,就只剩下段基址、段長度了。很多人都會有疑問,為什麼它們的位都不連續?那是因為Intel最初設計的80386只支援24位定址(B23~B16 + B15~B0),後來很快又不滿足需求了,又將定址位數擴充套件到了32位。
3.4. 段式定址過程
CPU內部是非常複雜的電路邏輯,如果按照軟體的思維,把段式定址這套電路邏輯理解成一個介面,那麼段暫存器、GDTR/LDTR暫存器就是引數。
當軟體設定好這些引數後,CPU的硬體執行過程是這樣:
① 對比CPL和段暫存器中的"RPL",判斷是否越權;
② 根據段暫存器中的TI位,載入全域性或區域性描述符表;
③ 根據段暫存器中的描述符表索引,載入指定的描述符;
④ 對比描述符中的"段長度"和指令中的"邏輯地址",判斷是否越界;
⑤ 對比描述符中的"DPL"和段暫存器中的"RPL",判斷是否越權;
⑥ 訪問描述符中"段基址"+指令中"邏輯地址"得到的實體地址。
3.5. 平坦(Flat)地址
其實,80386後來又支援了另外一種更先進的記憶體管理:頁式記憶體管理。而且類似於不能丟棄段暫存器,還要基於段暫存器設計保護模式,它的頁式記憶體管理,又是基於段式記憶體管理設計的,從而導致很多學習這款CPU的人很討厭它,但是如果完整的瞭解過Intel CPU的發展過程後,自然就認為情有可原了。
頁式記憶體管理,和段式記憶體管理,本來是兩個相互獨立的管理方式,但是Intel新增頁式記憶體管理的時候,80386已經發布過一段時間了,仍然是考慮到相容性等問題,使得它必須先執行完段式定址,才能進行頁式定址,目前,學習還只是剛開始,只瞭解了80386定址相關的硬體特性,可能還理解不了這個特點意味著什麼,後期學習到書本的第2.1節"Linux記憶體管理的基本框架"時,自然就能體會到了。
總結
本篇筆記,記錄了段式定址過程,過程本身很簡單,但是涉及到的一些設計思維很抽象。 我剛接觸計算機時,覺得它很神祕,像人一樣,能"播放"音樂給我聽,能"回答"我搜尋的問題,導致我差點都不敢去探索它,不過我想,天才都已經把它從無到有製造出來了,我只是去學習一個已經存在的東西,還要畏懼嗎?開啟開關,燈就亮了,踩下自行車,輪子就轉了,這些簡單的物理反應,我習以為常,點選"播放",電腦發出聲音,難道不就是一個更復雜的物理反應而已嘛。