指令集缕碎、體系架構(gòu)褥影、微架構(gòu) [轉(zhuǎn)]
轉(zhuǎn)載自《操作系統(tǒng)真相還原》
指令集是什么?表面上看它是一套指令的集合阎曹。集合的意思顯而易見伪阶,那咱們說說什么是指令。
在計算機中处嫌,CPU只能識別0激率、1這兩個數(shù)够话,甚至它都不知道數(shù)是什么,它只知道要么“是”,要么“不是”迅办,恰好用0、1來表示這兩種狀態(tài)而已季眷。
人發(fā)明的東西逃不出人的思維猫牡,所以,先看看我們?nèi)祟惖恼Z言是怎么回事捆昏。
不同的語言對同一種事物有不同的名字赚楚,這個名字其實就是代碼。比如說人類的好朋友:狗骗卜,咱們在中文里稱之為狗宠页,但在英文中它被稱為dog,雖然用了兩種語言寇仓,但其描述的都是這種會汪汪叫举户、對人類無比忠誠的動物。人是怎樣識別小狗的呢遍烦?識別信息來自聽覺俭嘁、視覺等,這是因為人天生具備處理聲音和圖像的能力服猪,能夠識別出各種不同的聲音和顏色不同的圖像供填。可是計算機只能處理0蔓姚、1這兩個數(shù)捕虽,所以讓計算機識別某個事物,只有用01這兩個數(shù)來定義坡脐。也就是說泄私,要用0、1來為各種事物編碼备闲。
為了更好地說明指令集晌端,咱們這里不再用現(xiàn)有的語言舉例子,當然也不是要自創(chuàng)指令集恬砂。下面舉個簡單的例子來演示指令集的模型咧纠。
咱們拿表達式A=B+C 為例。假設(shè)A 泻骤、B 漆羔、C 都是內(nèi)存變量的值梧奢,它們的地址分別是Ox3000 、Ox3004 演痒、Ox3008 亲轨。在此用Ra 表示寄存器A, Rb 表示寄存器B, Re 表示寄存器c.
完成這個加法的步驟是先將B 和C 載入到Ra 和Rb 寄存器中,再將兩個寄存器的值相加后送入寄存器Ra鸟顺,之后再將寄存器Ra 的值寫入到地址為Ox3000 的內(nèi)存中惦蚊。
步驟有了,咱們再設(shè)計完成這些步驟的指令讯嫂。
步驟1 :將內(nèi)存中的數(shù)據(jù)載入到寄存器蹦锋,咱們假設(shè)它的指令名為load 。
步驟2:兩個寄存器的加法指令欧芽,假設(shè)指令名為add莉掂。
步驟3 :將寄存器中的內(nèi)容存儲到內(nèi)存,假設(shè)指令名為store千扔。
以上指令名都是假設(shè)的巫湘,名字可以任意取,因為CPU 不識別指令名昏鹃。指令名是編譯器用來給人看的尚氛,為的是方便人來編程, CPU 它只認編碼洞渤。目前CPU 中的指令阅嘶,無論是哪種指令集,都由操作碼和操作數(shù)兩部分組成(有些指令即使指令格式中沒有列出操作數(shù)载迄,也會有隱含的操作數(shù))讯柔。咱們也采用這種操作碼+操作數(shù)的思路,分別為這兩部分編碼护昧。
咱們先為操作碼設(shè)計編碼魂迄。
接下來為操作數(shù)編碼,操作數(shù)一般是立即數(shù)惋耙、寄存器捣炬、內(nèi)存等,咱們這里主要是為寄存器編碼绽榛。
好啦湿酸,操作碼和操作數(shù)都有了,其實指令集己經(jīng)完成了灭美。不過在一長串的二進制01 中推溃,哪些是操作碼,哪些是操作數(shù)呢届腐?這就是指令格式的由來啦铁坎。我們?nèi)藶橐?guī)定個格式蜂奸,規(guī)定操作碼和操作數(shù)的大小及位置,然后在CPU 硬件電路中寫死這些規(guī)則硬萍,讓CPU 在硬件一級上識別這些格式窝撵,從而能識別出操作碼和操作數(shù)。
假設(shè)我們的指令格式最大支持三個寄存器參數(shù)和一個立即數(shù)參數(shù)襟铭。其中操作碼和各寄存器操作數(shù)各占1字節(jié),立即數(shù)部分占4 字節(jié)短曾。各條指令并不是完全按照此格式填充寒砖,不同的指令有不同的參數(shù),只有操作碼部分是固定的嫉拐,其他操作數(shù)部分是可選的哩都。當CPU 在譯碼階段識別出操作碼后,CPU自然知道該指令需要什么樣的操作數(shù)婉徘,這是寫死在硬件電路中的漠嵌,所以不同的指令其機器碼長度很可能不一致。
為了演示指令集模型盖呼,我們在上面假設(shè)了寄存器名儒鹿、指令名、格式几晤。按理說這對于指令集來說已經(jīng)全了约炎,不過,為方便咱們了解編譯器蟹瘾,不如咱們再假設(shè)個指令的語法吧圾浅,咱們這里學習Intel 的語法格式:“指令目的操作數(shù),源操作數(shù)飛目的操作數(shù)在左憾朴,源操作數(shù)在右狸捕,此賦值順序比較直觀。Intel 想表達的是a=b這種語序众雷,如a=b 灸拍,便是mov a, b。
以上三個步驟的機器碼按照十六進制表示為:
以上自定義的指令便是按照咱們假設(shè)的語法來生成的砾省。對于機器碼的大小株搔,由于指令不同,需要的操作數(shù)也不同纯蛾,所以機器碼大小也不同纤房。另外,機器碼中的立即數(shù)是按照x86 架構(gòu)的小端字節(jié)序?qū)懙姆撸@一點大家要注意炮姨。小端字節(jié)序是數(shù)值中的低位在低地址捌刮,高位在高地址,數(shù)位以字節(jié)為單位舒岸。
步驟2 的機器碼為01000110绅作。操作碼占1 字節(jié), CPU 識別出第1 字節(jié)的二進制01 是add 指令蛾派,知道此指令的操作數(shù)是3 個寄存器俄认,并且第1 個寄存器操作數(shù)是目的寄存器,另外兩個寄存器是源操作數(shù)(這都是我們假定的洪乍,并且是寫死在硬件中的規(guī)則眯杏,不同的指令有不同的規(guī)則,您也可以創(chuàng)造出內(nèi)存和寄存器混合作為操作數(shù)的加法指令)壳澳。于是到第2 宇節(jié)去讀取寄存器編碼岂贩,發(fā)現(xiàn)其值為二進制00 ,就是寄存器Ra 對應(yīng)的編碼巷波。接著到下一個字節(jié)處繼續(xù)讀出寄存器編碼萎津,發(fā)現(xiàn)是二進制01 ,也就是寄存器Rb, Re 同理抹镊。于是將寄存器Rb 和Re 的值相加后存入到寄存器b锉屈。
步驟3 中,機器碼為10 00 0c300000, CPU 讀取機器碼的第1 字節(jié)發(fā)現(xiàn)其為二進制10 垮耳,知道其為指令store部念,于是便確定了,目的操作數(shù)是個立即數(shù)形式的內(nèi)存地址氨菇,源操作數(shù)是個寄存器儡炼。接著到指令格式中的寄存器操作數(shù)1 的位置去讀取寄存器編碼,發(fā)現(xiàn)其值為00 查蓉,這就是寄存器Ra 的編碼乌询。機器碼中剩下的部分便作為立即數(shù),這樣便將寄存器Ra 的值寫入到內(nèi)存0x0000300c 中了豌研。
以上指令集的模型妹田,確實太過于簡單了,也許稱之為模型都非常勉強【楣玻現(xiàn)實中的指令格式要遠遠復雜得多鬼佣。下面我們看看目前世面上的指令集有哪些。
最早的指令集是CISC (Complex Instruction Set Computer )霜浴,意為復雜指令集計算機晶衷。從名字上看,這套指令集相當復雜,當初這套指令集問世的時候晌纫,它的研發(fā)者們都沒想過要給它起名税迷,只是因為后來出現(xiàn)了相對精簡高效的指令集,所以人們?yōu)榱思右詤^(qū)分锹漱,才將最初的這套相對復雜的指令集命名為CISC 箭养,而后來精簡高效的指令集稱為RISC (Reduced Instruction Set Computer )哥牍。
CISC 和RISC 并不是具體的指令集毕泌,而是兩種不同的指令體系,相當于指令集中的門派撼泛,是指令的設(shè)計思想。舉個例子译暂,就像中醫(yī)與西醫(yī)伯顶,中醫(yī)講究從整體上調(diào)理身體骆膝,西醫(yī)則更多的是偏向局部路克。這就是兩種不同的醫(yī)療思路精算,類似于CISC 和陽SC 這兩種指令體系疲吸。那什么是指令集呢?拿中醫(yī)舉例蹂喻,像華倫、張仲景這兩位醫(yī)圣驳概,他們雖然都是基于中醫(yī)的思想治病,但醫(yī)術(shù)各有特色稚照,水平也不盡相同,這就相當于不同的指令集斤彼。一會兒咱們會介紹具體的指令集去团。
為什么說CISC 復雜呢?
首先源哩,因為它是最早的指令集谓着,當初都是摸著石頭過河屉栓,肯定有一些瑕疵在里面句灌。其次资昧,當初的程序員都是用匯編語言開發(fā)程序叽唱,他們當然希望匯編語言強大啦,盡量多一些指令种樱,盡量一個指令能多干幾件事蒙袍,所以指令集中的指令越來越多,越來越復雜嫩挤。不過這樣的好處是程序員同學很爽害幅。最后,CISC是Intel使用的指令集岂昭,Intel公司在兼容性方面做得最好以现,指令集在發(fā)展的過程中,還要兼容過去有瑕疵的古董约啊,以至于最后的指令集變得有點“奇形怪狀”了邑遏。
作為后起之秀的RISC ,借鑒了前輩CISC 的經(jīng)驗恰矩,取其精華记盒,棄其糟柏,當然要更好更輕量啦外傅。它是怎么來的呢纪吮?
CISC 不是做得很全很強嗎,可是很多時候萎胰,程序員并不會用到那些復雜的指令和尋址方式碾盟,即使用到了,編譯器有時候為了優(yōu)化技竟,未必“全”將其編譯為復雜的形式冰肴。這就導致了CPU 中的復雜的指令和尋址方式無用武之地。根據(jù)二八定律榔组,指令集中20%的簡單指令占了程序的80% 熙尉,而指令集中80% 的復雜指令占了程序的20% 。根據(jù)這個特性搓扯,處理器及指令集被重新設(shè)計检痰,保留了那些基本常用的指令,減少了硬件電路的復雜性擅编。這樣攀细,大部分指令部能在一個時鐘周期內(nèi)完成箫踩,更有利于提升流水線的效率。而且谭贪,指令采用了定長編碼境钟,這樣譯碼工作更容易了。由于其太優(yōu)秀了俭识,后來的處理器慨削,如MIPS, ARM, Power都采用RISC 指令體系,做得最好的就是MIPS 處理器套媚,它嚴格遵守RISC 思想缚态,業(yè)界公認其優(yōu)雅。
我們常用的CPU 是Intel 和AMD 公司的產(chǎn)品堤瘤,它們用的指令集便是基于CISC 思想的x86.玫芦。 AMD 的x86指令架構(gòu)是Intel 授權(quán)給他們的,為區(qū)別于此本辐, Intel 在官方手冊上稱自己的指令集為IA32桥帆。
雖然AMD 采用的也是x86 指令集,但Intel 可沒把硬件實現(xiàn)方法也告訴AMD 慎皱,否則AMD 的CPU 和Intel 的CPU 不就完全一樣了嗎老虫,人家Intel 也不肯呢。指令集是一套約定茫多,里面規(guī)定的是有哪些指令祈匙、指令的二進制編碼、指令格式等天揖,如何實現(xiàn)這套約定夺欲,這是硬件自己的事。打個比方宝剖,這就像和朋友約好了在某餐廳吃飯洁闰,咱是坐車去歉甚,還是走著去万细,這是咱們的事,與吃飯是無關(guān)的纸泄。說白了赖钞,在Intel 的CPU 上運行的軟件也能夠在AMD 的CPU 上運行,原因就是它們共用了同用一套指令集聘裁,也就是對二進制編碼達成了共識雪营。它們面對相同的需求,可能采取了不同的行動衡便,但都完成了任務(wù)献起。比如機器碼是b80000, Intel的CPU 經(jīng)過譯碼洋访,知道這是將0 賦值給寄存器ax,相當于匯編語言mov ax, 0 谴餐。AMD 的CPU 在譯碼時姻政,也得將此機器碼認為是將0 賦值給寄存器ax。至于它們在物理上是怎么將0 傳入寄存器ax 中的岂嗓,這是它們各自實現(xiàn)的方式汁展,與指令集無關(guān)。它們各自實現(xiàn)的方式厌殉,就叫微架構(gòu)食绿。
總結(jié)一下,指令集是具體的一套指令編碼公罕,微架構(gòu)是指令集的物理實現(xiàn)方式器紧。
發(fā)展到后來, x86 指令集越來越復雜楼眷。它本屬于CISC 體系品洛,但由于效率低下,最終在其內(nèi)部實現(xiàn)上采取了RISC 內(nèi)核摩桶,即一條CISC 指令在譯碼時桥状,分解成多條RISC 指令,這樣其執(zhí)行效率便可與RISC 媲美啦硝清。
目前市面上常見的指令集有五種辅斟,除x86 是CISC 指令體系外, ARM芦拿、MIPS 士飒、Power、C6000 都是RISC 指令體系的指令集蔗崎。
CPU 與指令集是對應(yīng)的酵幕,一種CPU 只能識別一種指令集,所以很多CPU 都以其支持的指令集來稱呼缓苛。比如ARM 芳撒、MIPS,它們本身是CPU名稱未桥,又是指令集名稱笔刹。
ARM 主要用在手機中,作為手機的處理器冬耿。Power 是IBM 用于服務(wù)器上的處理器舌菜。C6000 是數(shù)字信號處理器,廣泛用于視頻處理亦镶。而MIPS 雖然本身很優(yōu)秀日月,但其在各領(lǐng)域起步都較晚袱瓮,并沒有廣泛應(yīng)用的領(lǐng)域。
由于MIPS 本身的優(yōu)越性爱咬,龍芯用的就是mips 指令集懂讯,有沒有人問,為什么咱們自主研發(fā)的CPU 還要用人家國外的指令集台颠?就不能也研發(fā)出一套指令集嗎褐望?能倒是能,不過語言不通用串前。就像我自己可以發(fā)明一門語言瘫里,語言本身沒什么問題,問題是我用自己發(fā)明的語言和別人交流荡碾,誰昕得懂呢谨读,誰又愿意去學這門語言呢?大家都很忙坛吁,不通用的東西沒人愿意花精力去學劳殖。如果龍芯也自立門戶創(chuàng)造新的指令集,那有誰愿意給它寫編譯器呢拨脉?即使有了編譯器哆姻,操作系統(tǒng)也要重新編譯發(fā)布,應(yīng)用程序也要重新編譯發(fā)布玫膀,指令集背后不僅是個計算機生態(tài)鏈矛缨,更重要的是全球經(jīng)濟鏈。
平時所說的編程語言帖旨,雖然其上層表現(xiàn)各異箕昭,歸根結(jié)底是要在具體的CPU 上運行的,所以必須由編譯器按照該CPU 的指奪集解阅,翻譯成符合該CPU 的指令落竹。說到這,不得不說一下交叉編譯货抄,本質(zhì)上交叉編譯就是用在A平臺上運行的編譯器述召,編譯出符合B 平臺CPU 指令集的程序,編譯出的程序直接能在B平臺上運行啦碉熄。這里的平臺指的就是CPU 指令體系結(jié)構(gòu)桨武。