Linux核心筆記001 - Intel X86 CPU 系列的定址方式

jmpcall發表於2020-05-12


1. 段暫存器

  Linux核心筆記001 - Intel X86 CPU 系列的定址方式
    上圖是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內部的一些元件,在真實模式和保護模式下,作用可能也不相同。
  Linux核心筆記001 - Intel X86 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. 保護模式下段暫存器含義

    Linux核心筆記001 - Intel X86 CPU 系列的定址方式
    類似於"非攻"中元件,在不同形態中可以設計為完全不同的作用,段暫存器在保護模式中,也已經不再跟真實模式一樣純粹指向一個段基址了,而是設計成上圖中的結構:
  • 高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位的索引,就可以指定一個描述符。
    描述符的結構為:
    Linux核心筆記001 - Intel X86 CPU 系列的定址方式
  • 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:
Linux核心筆記001 - Intel X86 CPU 系列的定址方式
  • 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記憶體管理的基本框架"時,自然就能體會到了。

總結

    本篇筆記,記錄了段式定址過程,過程本身很簡單,但是涉及到的一些設計思維很抽象。 我剛接觸計算機時,覺得它很神祕,像人一樣,能"播放"音樂給我聽,能"回答"我搜尋的問題,導致我差點都不敢去探索它,不過我想,天才都已經把它從無到有製造出來了,我只是去學習一個已經存在的東西,還要畏懼嗎?開啟開關,燈就亮了,踩下自行車,輪子就轉了,這些簡單的物理反應,我習以為常,點選"播放",電腦發出聲音,難道不就是一個更復雜的物理反應而已嘛。

相關文章