《操作系统真象还原》——0.25 指令集、体系结构、微架构、编程语言
本節書摘來自異步社區《操作系統真象還原》一書中的第0章,第0.25節,作者:鄭鋼著,更多章節內容可以訪問云棲社區“異步社區”公眾號查看
0.25 指令集、體系結構、微架構、編程語言
指令集是什么?表面上看它是一套指令的集合。集合的意思顯而易見,那咱們說說什么是指令。
在計算機中,CPU只能識別0、1這兩個數,甚至它都不知道數是什么,它只知道要么“是”,要么“不是”,恰好用0、1來表示這兩種狀態而已。
人發明的東西逃不出人的思維,所以,先看看我們人類的語言是怎么回事。
不同的語言對同一種事物有不同的名字,這個名字其實就是代碼。比如說人類的好朋友:狗,咱們在中文里稱之為狗,但在英文中它被稱為dog,雖然用了兩種語言,但其描述的都是這種會汪汪叫、對人類無比忠誠的動物。人是怎樣識別小狗的呢?識別信息來自聽覺、視覺等,這是因為人天生具備處理聲音和圖像的能力,能夠識別出各種不同的聲音和顏色不同的圖像。可是計算機只能處理0、1這兩個數,所以讓計算機識別某個事物,只有用01這兩個數來定義。也就是說,要用0、1來為各種事物編碼。
為了更好地說明指令集,咱們這里不再用現有的語言舉例子,當然也不是要自創指令集。下面舉個簡單的例子來演示指令集的模型。
咱們拿表達式A=B+C為例。假設A、B、C都是內存變量的值,它們的地址分別是0x3000、0x3004、0x3008。在此用Ra表示寄存器A,Rb表示寄存器B,Rc表示寄存器C。
完成這個加法的步驟是先將B和C載入到Ra和Rb寄存器中,再將兩個寄存器的值相加后送入寄存器Ra,之后再將寄存器Ra的值寫入到地址為0x3000的內存中。
步驟有了,咱們再設計完成這些步驟的指令。
步驟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的機器碼為01 00 01 10。操作碼占1字節,CPU識別出第1字節的二進制01是add指令,知道此指令的操作數是3個寄存器,并且第1個寄存器操作數是目的寄存器,另外兩個寄存器是源操作數(這都是我們假定的,并且是寫死在硬件中的規則,不同的指令有不同的規則,您也可以創造出內存和寄存器混合作為操作數的加法指令)。于是到第2字節去讀取寄存器編碼,發現其值為二進制00,就是寄存器Ra對應的編碼。接著到下一個字節處繼續讀出寄存器編碼,發現是二進制01,也就是寄存器Rb,Rc同理。于是將寄存器Rb和Rc的值相加后存入到寄存器Ra。
步驟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和RISC這兩種指令體系。那什么是指令集呢?拿中醫舉例,像華佗、張仲景這兩位醫圣,他們雖然都是基于中醫的思想治病,但醫術各有特色,水平也不盡相同,這就相當于不同的指令集。一會兒咱們會介紹具體的指令集。
為什么說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指令體系結構。
總結
以上是生活随笔為你收集整理的《操作系统真象还原》——0.25 指令集、体系结构、微架构、编程语言的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot 就这一篇全搞定
- 下一篇: Android Studio 3.5.2