2_指令集、體系架構、微架構

耶穌讚我萌發表於2019-04-11

指令集、體系架構、微架構 [轉]

轉載自《作業系統真相還原》

指令集是什麼?表面上看它是一套指令的集合。集合的意思顯而易見,那我們們說說什麼是指令。

在計算機中,CPU只能識別0、1這兩個數,甚至它都不知道數是什麼,它只知道要麼“是”,要麼“不是”,恰好用0、1來表示這兩種狀態而已。

人發明的東西逃不出人的思維,所以,先看看我們人類的語言是怎麼回事。

不同的語言對同一種事物有不同的名字,這個名字其實就是程式碼。比如說人類的好朋友:狗,我們們在中文裡稱之為狗,但在英文中它被稱為dog,雖然用了兩種語言,但其描述的都是這種會汪汪叫、對人類無比忠誠的動物。人是怎樣識別小狗的呢?識別資訊來自聽覺、視覺等,這是因為人天生具備處理聲音和影像的能力,能夠識別出各種不同的聲音和顏色不同的影像。可是計算機只能處理0、1這兩個數,所以讓計算機識別某個事物,只有用01這兩個數來定義。也就是說,要用0、1來為各種事物編碼。

為了更好地說明指令集,我們們這裡不再用現有的語言舉例子,當然也不是要自創指令集。下面舉個簡單的例子來演示指令集的模型。

我們們拿表示式A=B+C 為例。假設A 、B 、C 都是記憶體變數的值,它們的地址分別是Ox3000 、Ox3004 、Ox3008 。在此用Ra 表示暫存器A, Rb 表示暫存器B, Re 表示暫存器c.

完成這個加法的步驟是先將B 和C 載入到Ra 和Rb 暫存器中,再將兩個暫存器的值相加後送入暫存器Ra,之後再將暫存器Ra 的值寫入到地址為Ox3000 的記憶體中。

步驟有了,我們們再設計完成這些步驟的指令。

步驟1 :將記憶體中的資料載入到暫存器,我們們假設它的指令名為load 。 

步驟2:兩個暫存器的加法指令,假設指令名為add。

步驟3 :將暫存器中的內容儲存到記憶體,假設指令名為store。

以上指令名都是假設的,名字可以任意取,因為CPU 不識別指令名。指令名是編譯器用來給人看的,為的是方便人來程式設計, CPU 它只認編碼。目前CPU 中的指令,無論是哪種指令集,都由操作碼和運算元兩部分組成(有些指令即使指令格式中沒有列出運算元,也會有隱含的運算元)。我們們也採用這種操作碼+運算元的思路,分別為這兩部分編碼。

我們們先為操作碼設計編碼。

接下來為運算元編碼,運算元一般是立即數、暫存器、記憶體等,我們們這裡主要是為暫存器編碼。

好啦,操作碼和運算元都有了,其實指令集己經完成了。不過在一長串的二進位制01 中,哪些是操作碼,哪些是運算元呢?這就是指令格式的由來啦。我們人為規定個格式,規定操作碼和運算元的大小及位置,然後在CPU 硬體電路中寫死這些規則,讓CPU 在硬體一級上識別這些格式,從而能識別出操作碼和運算元。

假設我們的指令格式最大支援三個暫存器引數和一個立即數引數。其中操作碼和各暫存器運算元各佔1位元組,立即數部分佔4 位元組。各條指令並不是完全按照此格式填充,不同的指令有不同的引數,只有操作碼部分是固定的,其他運算元部分是可選的。當CPU 在譯碼階段識別出操作碼後,CPU自然知道該指令需要什麼樣的運算元,這是寫死在硬體電路中的,所以不同的指令其機器碼長度很可能不一致。

為了演示指令集模型,我們在上面假設了暫存器名、指令名、格式。按理說這對於指令集來說已經全了,不過,為方便我們們瞭解編譯器,不如我們們再假設個指令的語法吧,我們們這裡學習Intel 的語法格式:“指令目的運算元,源運算元飛目的運算元在左,源運算元在右,此賦值順序比較直觀。Intel 想表達的是a=b這種語序,如a=b ,便是mov a, b。

以上三個步驟的機器碼按照十六進位制表示為:

以上自定義的指令便是按照我們們假設的語法來生成的。對於機器碼的大小,由於指令不同,需要的運算元也不同,所以機器碼大小也不同。另外,機器碼中的立即數是按照x86 架構的小端位元組序寫的,這一點大家要注意。小端位元組序是數值中的低位在低地址,高位在高地址,數位以位元組為單位。

步驟2 的機器碼為01000110。操作碼佔1 位元組, CPU 識別出第1 位元組的二進位制01 是add 指令,知道此指令的運算元是3 個暫存器,並且第1 個暫存器運算元是目的暫存器,另外兩個暫存器是源運算元(這都是我們假定的,並且是寫死在硬體中的規則,不同的指令有不同的規則,您也可以創造出記憶體和暫存器混合作為運算元的加法指令)。於是到第2 宇節去讀取暫存器編碼,發現其值為二進位制00 ,就是暫存器Ra 對應的編碼。接著到下一個位元組處繼續讀出暫存器編碼,發現是二進位制01 ,也就是暫存器Rb, Re 同理。於是將暫存器Rb 和Re 的值相加後存入到暫存器b。

步驟3 中,機器碼為10 00 0c300000, CPU 讀取機器碼的第1 位元組發現其為二進位制10 ,知道其為指令store,於是便確定了,目的運算元是個立即數形式的記憶體地址,源運算元是個暫存器。接著到指令格式中的暫存器運算元1 的位置去讀取暫存器編碼,發現其值為00 ,這就是暫存器Ra 的編碼。機器碼中剩下的部分便作為立即數,這樣便將暫存器Ra 的值寫入到記憶體0x0000300c 中了。

以上指令集的模型,確實太過於簡單了,也許稱之為模型都非常勉強。現實中的指令格式要遠遠複雜得多。下面我們看看目前世面上的指令集有哪些。

最早的指令集是CISC (Complex Instruction Set Computer ),意為複雜指令集計算機。從名字上看,這套指令集相當複雜,當初這套指令集問世的時候,它的研發者們都沒想過要給它起名,只是因為後來出現了相對精簡高效的指令集,所以人們為了加以區分,才將最初的這套相對複雜的指令集命名為CISC ,而後來精簡高效的指令集稱為RISC (Reduced Instruction Set Computer )。

CISC 和RISC 並不是具體的指令集,而是兩種不同的指令體系,相當於指令集中的門派,是指令的設計思想。舉個例子,就像中醫與西醫,中醫講究從整體上調理身體,西醫則更多的是偏向區域性。這就是兩種不同的醫療思路,類似於CISC 和陽SC 這兩種指令體系。那什麼是指令集呢?拿中醫舉例,像華倫、張仲景這兩位醫聖,他們雖然都是基於中醫的思想治病,但醫術各有特色,水平也不盡相同,這就相當於不同的指令集。一會兒我們們會介紹具體的指令集。

為什麼說CISC 複雜呢?

首先,因為它是最早的指令集,當初都是摸著石頭過河,肯定有一些瑕疵在裡面。其次,當初的程式設計師都是用匯編語言開發程式,他們當然希望組合語言強大啦,儘量多一些指令,儘量一個指令能多幹幾件事,所以指令集中的指令越來越多,越來越複雜。不過這樣的好處是程式設計師同學很爽。最後,CISC是Intel使用的指令集,Intel公司在相容性方面做得最好,指令集在發展的過程中,還要相容過去有瑕疵的古董,以至於最後的指令集變得有點“奇形怪狀”了。

作為後起之秀的RISC ,借鑑了前輩CISC 的經驗,取其精華,棄其糟柏,當然要更好更輕量啦。它是怎麼來的呢?

CISC 不是做得很全很強嗎,可是很多時候,程式設計師並不會用到那些複雜的指令和定址方式,即使用到了,編譯器有時候為了優化,未必“全”將其編譯為複雜的形式。這就導致了CPU 中的複雜的指令和定址方式無用武之地。根據二八定律,指令集中20%的簡單指令佔了程式的80% ,而指令集中80% 的複雜指令佔了程式的20% 。根據這個特性,處理器及指令集被重新設計,保留了那些基本常用的指令,減少了硬體電路的複雜性。這樣,大部分指令部能在一個時鐘週期內完成,更有利於提升流水線的效率。而且,指令採用了定長編碼,這樣譯碼工作更容易了。由於其太優秀了,後來的處理器,如MIPS, ARM, Power都採用RISC 指令體系,做得最好的就是MIPS 處理器,它嚴格遵守RISC 思想,業界公認其優雅。

我們常用的CPU 是Intel 和AMD 公司的產品,它們用的指令集便是基於CISC 思想的x86.。 AMD 的x86指令架構是Intel 授權給他們的,為區別於此, Intel 在官方手冊上稱自己的指令集為IA32。

雖然AMD 採用的也是x86 指令集,但Intel 可沒把硬體實現方法也告訴AMD ,否則AMD 的CPU 和Intel 的CPU 不就完全一樣了嗎,人家Intel 也不肯呢。指令集是一套約定,裡面規定的是有哪些指令、指令的二進位制編碼、指令格式等,如何實現這套約定,這是硬體自己的事。打個比方,這就像和朋友約好了在某餐廳吃飯,我們是坐車去,還是走著去,這是我們們的事,與吃飯是無關的。說白了,在Intel 的CPU 上執行的軟體也能夠在AMD 的CPU 上執行,原因就是它們共用了同用一套指令集,也就是對二進位制編碼達成了共識。它們面對相同的需求,可能採取了不同的行動,但都完成了任務。比如機器碼是b80000, Intel的CPU 經過譯碼,知道這是將0 賦值給暫存器ax,相當於組合語言mov ax, 0 。AMD 的CPU 在譯碼時,也得將此機器碼認為是將0 賦值給暫存器ax。至於它們在物理上是怎麼將0 傳入暫存器ax 中的,這是它們各自實現的方式,與指令集無關。它們各自實現的方式,就叫微架構

總結一下,指令集是具體的一套指令編碼,微架構是指令集的物理實現方式。

發展到後來, x86 指令集越來越複雜。它本屬於CISC 體系,但由於效率低下,最終在其內部實現上採取了RISC 核心,即一條CISC 指令在譯碼時,分解成多條RISC 指令,這樣其執行效率便可與RISC 媲美啦。

目前市面上常見的指令集有五種,除x86 是CISC 指令體系外, ARM、MIPS 、Power、C6000 都是RISC 指令體系的指令集。

CPU 與指令集是對應的,一種CPU 只能識別一種指令集,所以很多CPU 都以其支援的指令集來稱呼。比如ARM 、MIPS,它們本身是CPU名稱,又是指令集名稱。

ARM 主要用在手機中,作為手機的處理器。Power 是IBM 用於伺服器上的處理器。C6000 是數字訊號處理器,廣泛用於視訊處理。而MIPS 雖然本身很優秀,但其在各領域起步都較晚,並沒有廣泛應用的領域。

由於MIPS 本身的優越性,龍芯用的就是mips 指令集,有沒有人問,為什麼我們們自主研發的CPU 還要用人家國外的指令集?就不能也研發出一套指令集嗎?能倒是能,不過語言不通用。就像我自己可以發明一門語言,語言本身沒什麼問題,問題是我用自己發明的語言和別人交流,誰昕得懂呢,誰又願意去學這門語言呢?大家都很忙,不通用的東西沒人願意花精力去學。如果龍芯也自立門戶創造新的指令集,那有誰願意給它寫編譯器呢?即使有了編譯器,作業系統也要重新編譯釋出,應用程式也要重新編譯釋出,指令集背後不僅是個計算機生態鏈,更重要的是全球經濟鏈。

平時所說的程式語言,雖然其上層表現各異,歸根結底是要在具體的CPU 上執行的,所以必須由編譯器按照該CPU 的指奪集,翻譯成符合該CPU 的指令。說到這,不得不說一下交叉編譯,本質上交叉編譯就是用在A平臺上執行的編譯器,編譯出符合B 平臺CPU 指令集的程式,編譯出的程式直接能在B平臺上執行啦。這裡的平臺指的就是CPU 指令體系結構。


連結:https://www.jianshu.com/p/39325938a73a
來源:簡書

相關文章