不以規矩.不能成方圓。--《孟子·離婁上》
說到指令集以及CPU架構體系,大家就會想到計算機專業課程裡面的計算機體系結構的方面的內容。既然課程中已經有了的內容我就不想那麼枯燥的去複述一遍,而是先看一個類的定義:
//定義暫存器編號
typedef enum : int {
Reg0,
Reg1,
Reg2,
Reg3
} RegNum;
//定義系統呼叫編號
typedef enum : int {
Int3 //裝置輸出,將暫存器Reg0中的內容輸出到螢幕
} Interrupt;
//定義指令索引
typedef int Instruct;
/**
虛擬CPU類,模擬CPU所提供的指令。
虛擬CPU由4個暫存器和運算部件組成。四個暫存器的編號分別定義在RegNum中;運算部件提供了賦值、加減、比較、跳轉9個指令。
*/
@interface VCPU : NSObject
//將一個常量值賦值給編號為reg的暫存器中。
-(void)moveFromConst:(int)val toReg:(RegNum)reg;
//將編號為reg1的暫存器中的值賦值給編號為reg2的暫存器中。
-(void)moveFromReg:(RegNum)reg1 toReg:(RegNum)reg2;
//將編號為reg的暫存器中的值賦值給地址為addr的記憶體中。
-(void)moveFromReg:(RegNum)reg toAddr:(Addr)addr;
//將地址為addr的記憶體中的值賦值給編號為reg的暫存器中。
-(void)moveFromAddr:(Addr)addr toReg:(RegNum)reg;
//將編號為reg1的暫存器中的值加上編號為reg2的暫存器中的值並將結果儲存到編號為reg2的暫存器中。
-(void)addFromReg:(RegNum)reg1 toReg:(RegNum)reg2;
//將編號為reg1的暫存器中的值減去編號為reg2的暫存器中的值並將結果儲存到編號為reg2的暫存器中。
-(void)subFromReg:(RegNum)reg1 toReg:(RegNum)reg2;
//如果兩個暫存器內容相等則執行instruct所指定的指令,否則什麼也不做。
-(void)isEqualReg:(RegNum)reg1 withReg:(RegNum)reg2 thenGoto:(Instruct)instruct;
//跳轉到instruct所指定的指令中去。
-(void)jumpTo:(Instruct)instruct;
//系統返回
-(void)ret;
//系統呼叫,目前只支援螢幕輸出呼叫Int3,表示將暫存器編號為0中的值輸出到螢幕。
-(void)sys:(Interrupt)interrupt;
@end
複製程式碼
上面是一個叫VCPU的類的定義部分,它是一個用OC語言實現的用來模擬CPU功能的類。我們再來看一個使用這個類的程式碼片段:
-(void)main:(VCPU*)cpu memory:(VMemory*)memory
{
VINSTRUCT_BEGIN
VINSTRUCT(0, [cpu moveFromConst:10 toReg:Reg0]) //將常數10儲存到CPU的暫存器Reg0中
VINSTRUCT(1, [cpu moveFromConst:15 toReg:Reg1]) //將常數15儲存到CPU的暫存器Reg1中
VINSTRUCT(2, [cpu addFromReg:Reg0 toReg:Reg1]) //將暫存器Reg0中的值於暫存器Reg1中的值相加並儲存到Reg1中
VINSTRUCT(3, [cpu moveFromReg:Reg1 toAddr:0x1000]) //將儲存在Reg1中的相加結果儲存到記憶體地址為0x1000處的記憶體中
VINSTRUCT(4, [cpu moveFromAddr:0x1000 toReg:Reg0]) //將記憶體地址0x1000處的記憶體值儲存到暫存器Reg0中
VINSTRUCT(5, [cpu moveFromConst:25 toReg:Reg1]) //將常數25儲存到CPU的暫存器Reg1中
VINSTRUCT(6, [cpu isEqualReg:Reg0 withReg:Reg1 thenGoto:9]) //如果Reg0中的值和Reg1中的值相等則執行第9條指令:進行列印輸出
VINSTRUCT(7, [cpu moveFromReg:Reg1 toAddr:0x1000]) //將暫存器Reg1中的值儲存到記憶體地址為0x1000中。
VINSTRUCT(8, [cpu jumpTo:10]) //跳轉去執行第10條指令
VINSTRUCT(9, [cpu sys:Int3]) //系統呼叫,輸出儲存在Reg0中的值。
VINSTRUCT(10, [cpu ret]) //程式結束。
VINSTRUCT_END
}
複製程式碼
您能看懂上面程式碼所實現的功能嗎(要想檢視並執行完整的程式碼請到我的** github站點中的VirtualSystem**處下載)?您是否在例子裡面隱約的感受到了上面程式碼裡面涉及到的一些關於CPU、記憶體、程式等方面的概念和知識了?它其實就是實現瞭如下的簡單功能:
-(void)main
{
int a = 10;
int b = 15;
a = a + b;
if ( a == 25){
NSLog(@"output:%d",a);
}
}
複製程式碼
考察一下VCPU類,你會發現這個類提供了一些非常基礎的操作方法:加減處理、資料移動、比較、地址跳轉、系統呼叫等功能。呼叫者可以利用這個類提供的操作方法來編寫並完成某個特定的功能。這個類的內部實現還提供了幾個臨時儲存空間,可以通過RegNum編號來讀寫裡面的數值。 VCPU實際上是一個對真實CPU所具有的能力的一個簡單的模擬類。我們來看一下CPU的組成:
![CPU組成](https://i.iter01.com/images/d3d22081667e103c18ce1573856ec2b6b6a8ab7eda8c5d3f3328f236d52856d9.jpg)
從上面的CPU結構圖片中可以看出CPU主要分為儲存單元(SU)和運算單元(ALU)以及控制單元(CU)。如果將這些部件和結構對映到VCPU這個類時你會發現:儲存單元所對應的就是裡面的資料成員;而運算單元和控制單元則對應裡面的所有例項方法,運算單元提供了CPU指令的實現(VCPU類提供了眾多的方法實現)。
我們稱一個CPU裡面所提供的所有的指令的集合稱之為指令集。
我們可以用OC語言來實現一個VCPU類,也可以用Swift語言來實現一個Swift版本的VCPU類,也可以用Java語言來實現一個Java版本的VCPU類。 這其中不同的語言所提供的方法的定義形式是完全不同的: 就比如說OC裡面可以提供直接操作記憶體地址的方法,但是Java裡面則無法提供直接操作記憶體地址的方法;即使是OC語言中我們要實現VCPU類中的方法也可以有很多種不同的方式。
不同的廠家以及不同的技術工藝和技術水平以及具體的裝置上所實現的CPU的體系架構以及提供的功能也是有差異的。比如ARM指令架構體系的CPU、x86指令體系架構的CPU、POWER-PC指令架構體系的CPU。這些不同體系的CPU因為架構完全不同導致所提供的指令和儲存單元也完全不同。我們不可能讓ARM指令直接在X86的CPU上執行(就如OC的提供方法無法在Java中執行是一個道理)。相同體系架構下的CPU指令則在一定程度上是可以相互相容的,因為相同架構體系下的CPU的指令集是一致的(類比為介面一致,但是內部實現則不相同),比如說Intel公司所生產的x86系列的CPU和AMD公司所生產的x86系列CPU所提供的指令集是相似和相容的,他們之間的差別只是內部的實現不同而已。
CPU指令集定義的是一箇中央處理器所應該提供的基礎功能的集合,它是一個標準是一個介面也是一個協議。在軟體開發中具有協議和介面定義的概念,無論是消費者還是提供者都需要遵循這個標準來進行程式設計和互動:提供者要實現介面所具有的功能,至於如何實現則是內部的事情,不對外暴露,消費者也不需要知道具體的實現細節;消費者則總是要按介面提供的功能方法並組合使用來完成某種功能。這種設計的思維對於硬體系統也是一樣適用的。一般情況下某種CPU指令集通常都是由某些設計或者生產CPU的公司或者某標準組織共同定義而形成。那麼目前市面上有哪些主流的CPU指令集或CPU架構體系呢?
- x86/x64指令集
此處參考自:https://baike.baidu.com/item/Intel%20x86/1012845?fromtitle=x86&fromid=6150538
x86架構是Intel公司在1978年推出的Intel 8086中央處理器中首度出現,從Intel80386開始支援32位的系統。x86現在幾乎是個人計算機的標準平臺,成為了歷來最成功的CPU架構。其他公司也有製造x86架構的處理器:有AMD、Cyrix、NEC、IBM、IDT以及Transmeta等。
64位架構
到2002年,由於32位特性的長度,x86的架構開始到達某些設計的極限。這個導致要處理大量的資訊儲存大於4GB會有困難。Intel原本已經決定在64位的時代完全地捨棄x86相容性,推出新的架構稱為IA-64技術作為他的Itanium處理器產品線的基礎。IA-64與x86的軟體天生不相容;它使用各種模擬形式來執行x86的軟體,不過,以模擬方式來執行的效率十分低下,並且會影響其他程式的執行。AMD公司則主動把32位x86(或稱為IA-32)擴充為64位。它以一個稱為AMD64的架構出現(在重新命名前也稱為x86-64),且以這個技術為基礎的第一個產品是單核心的Opteron和Athlon 64處理器家族。由於AMD的64位處理器產品線首先進入市場,且微軟也不願意為Intel和AMD開發兩套不同的64位作業系統,Intel也被迫採納AMD64指令集且增加某些新的擴充到他們自己的產品,命名為EM64T架構(顯然他們不想承認這些指令集是來自它的主要對手),EM64T後來被Intel正式更名為Intel 64(也就是x64指令集)。
在iOS程式設計時如果要執行在模擬器上,程式碼生成的機器指令時就需要指定使用i386還是x64指令集,因為目前的mac電腦上基本採用了x86或者x64架構的CPU。
- ARM指令集
此處參考自:https://baike.baidu.com/item/ARM/7518299
ARM處理器是英國Acorn有限公司設計的低功耗成本的第一款RISC微處理器。全稱為Advanced RISC Machine。ARM處理器本身是32位設計,但也配備16位指令集。1978年12月5日,物理學家赫爾曼·豪澤(Hermann Hauser)和工程師Chris Curry,在英國劍橋創辦了CPU公司(Cambridge Processing Unit),主要業務是為當地市場供應電子裝置。1979年,CPU公司改名為Acorn公司。
起初,Acorn公司打算使用摩托羅拉公司的16位晶片,但是發現這種晶片太慢也太貴。"一臺售價500英鎊的機器,不可能使用價格100英鎊的CPU!"他們轉而向Intel公司索要80286晶片的設計資料,但是遭到拒絕,於是被迫自行研發。
1985年,Roger Wilson和Steve Furber設計了他們自己的第一代32位、6M Hz的處理器,
![Roger Wilson和Steve Furber](https://i.iter01.com/images/6217bc5155389c162e57d8a57c2220cbaa98493beec0116598c2a7c36ce860f1.jpg)
並用它做出了一臺RISC指令集的計算機,簡稱ARM(Acorn RISC Machine)。這就是ARM這個名字的由來。
目前市面上的主流智慧手機等移動裝置配備的CPU都採用ARM架構。iOS應用真機編譯出來的機器指令都是ARM指令,因此需要在編譯時指定armv7或者arm64指令集。
- MIPS架構
此處參考自: https://baike.baidu.com/item/MIPS架構/1539401?fr=aladdin
MIPS架構(英語:MIPS architecture,為Microprocessor without interlocked piped stages architecture的縮寫),是一種採取精簡指令集(RISC)的處理器架構,1981年出現,由MIPS科技公司開發並授權,廣泛被使用在許多電子產品、網路裝置、個人娛樂裝置與商業裝置上。最早的MIPS架構是32位,最新的版本已經變成64位。
目前國內的龍芯CPU,採用的就是MIPS指令集。
- POWER -PC
此處參考自:https://baike.baidu.com/item/POWER%20PC/5963071?fr=aladdin
POWER-PC由摩托羅拉公司和蘋果公司聯合開發的高效能32位和64位RISC微處理器系列,以與壟斷PC機市場的Intel微處理器和微軟公司的軟體相競爭。PowerPC微處理器1994年推出。
IBM以前跟Intel競爭過桌面處理器市場,但由於市場策略不當等原因,IBM沒賺到什麼錢,於是決定退出桌面市場。POWER系列處理器是它退出桌面市場後才開發出來的伺服器用處理器,蘋果電腦用的處理器只是Power系列裡的一種,據說是IBM為蘋果特製的簡化版本,而蘋果獨一無二的經營理念使蘋果電腦與其它PC都不相容,所以目前的Power系列處理器不能用於桌面PC。目前蘋果電腦因PowerPC處理器不適合蘋果發展而轉而使用Intel處理器。
您是否在很多iOS庫的標頭檔案裡面看到過POWER-PC的巨集定義,早期的蘋果電腦都用POWER-PC的CPU,現在蘋果電腦基本都改為x64架構的CPU了。
CPU體系的分類
上面列出了一些關於CPU架構和指令集的介紹,不同的體系結構具有各自的優缺點,我們可以從不同的角度對CPU進行分類:
按字長
所謂字長就是指CPU的指令在一個週期內能夠處理的最大的數字或者理解為對記憶體地址的最大的定址能力。因此按這個長度可以做如下分類:
- 8位(比如:Intel8086/以及一些小型家電的晶片)
- 16位(比如:Intel80286)
- 32位(比如:Intel的x86系列, ARM的armv7,armv7s系列)
- 64位(比如:Intel的x64, ARM的arm64)
一般情況下大字長的CPU指令集都會相容小字長的CPU指令集。比如32位的應用程式能夠在64位的CPU上執行,而小字長的CPU指令集則無法直接提供大字長指令集的能力,如需要支撐則通常都是通過模擬來完成的,比如說一個64位字長CPU的讀取資料指令在32位字長CPU上就可以通過模擬兩次讀取來完成,現在有的CPU提供了指令模擬的功能,因此某些64位的應用程式還是可以執行在32位的CPU上的,只不過效能和速度會存在很大的損耗。
按指令複雜度
此處參考自:https://wenku.baidu.com/view/b5a138d43186bceb19e8bb62.html、https://zhidao.baidu.com/question/200786121943026445.html。
所謂指令的複雜度就是指CPU指令集中所提供的指令的數量、指令定址模式、指令引數、以及CPU內部的架構設計的複雜度、以及指令本身所佔據的位元組數等來進行劃分的一種方式,一般有兩種型別的分類:
-
CISC指令集。CISC的英文全稱為“Complex Instruction Set Computer”,即“複雜指令系統計算機”,從計算機誕生以來,人們一直沿用CISC指令集方式。早期的桌面軟體是按CISC設計的,並一直沿續到現在。目前,桌面計算機流行的x86體系結構即使用CISC。在CISC微處理器中,程式的各條指令是按順序序列執行的,每條指令中的各個操作也是按順序序列執行的。順序執行的優點是控制簡單,但計算機各部分的利用率不高,執行速度慢。CISC架構的伺服器主要以x86/x64架構(Intel Architecture)為主,而且多數為中低檔伺服器所採用。
-
RISC指令集。RISC的英文全稱為“Reduced Instruction Set Computer”,即“精簡指令集計算機”,是一種執行較少型別計算機指令的微處理器,起源於80年代的MIPS主機(即RISC機),RISC機中採用的微處理器統稱RISC處理器。這樣一來,它能夠以更快的速度執行操作(每秒執行更多百萬條指令,即MIPS)。目前的智慧移動裝置中的CPU幾乎都採用RISC指令集,比較有代表的就是ARM指令集和POWER-PC指令集。
下面的表格舉出了CISC和RISC兩種體系結構的差別:
![RISC和CISC的差異比較](https://i.iter01.com/images/7becea6d0132451f9e2b67925c1f6884a0a4369fb0b96a5590ce3077f1d465a4.png)
按指令流和資料流來
此處參考自:http://blog.csdn.net/conowen/article/details/7256260
按指令流和資料流來進行分類的依據是CPU的一條指令可以同時處理多少條資料,或者一條資料同時被多少條指令處理,以及在一個CPU時間週期內可以同時執行多少條指令等規則來劃分的。因此可以劃分為如下四種:
-
單指令流單資料流機器(SISD) SISD機器是一種傳統的序列計算機,它的硬體不支援任何形式的平行計算,所有的指令都是序列執行。並且在某個時鐘週期內,CPU只能處理一個資料流。因此這種機器被稱作單指令流單資料流機器。早期的計算機都是SISD機器,如馮諾.依曼架構,如IBM PC機,早期的巨型機和許多8位的家用機等。
-
單指令流多資料流機器(SIMD) SIMD是採用一個指令流處理多個資料流。這類機器在數字訊號處理、影象處理、以及多媒體資訊處理等領域非常有效。Intel處理器實現的MMXTM、SSE(Streaming SIMD Extensions)、SSE2及SSE3擴充套件指令集,都能在單個時鐘週期內處理多個資料單元。也就是說我們現在用的單核計算機基本上都屬於SIMD機器。(個人覺得GPU也屬於這個範疇)
-
多指令流單資料流機器(MISD) MISD是採用多個指令流來處理單個資料流。由於實際情況中,採用多指令流處理多資料流才是更有效的方法,因此MISD只是作為理論模型出現,沒有投入到實際應用之中。
-
多指令流多資料流機器(MIMD) MIMD機器可以同時執行多個指令流,這些指令流分別對不同資料流進行操作。最新的多核計算平臺就屬於MIMD的範疇,例如Intel和AMD的雙核處理器等都屬於MIMD。
虛擬環境
最後我們還是回到VCPU類來,VCPU是一個對CPU的簡單的模擬實現。我們知道用vmware軟體可以用來模擬出一個作業系統執行的硬體環境,而實現了虛擬裝置的功能;微軟公司在2017年宣佈他的Visual studio 2017上能夠開發並執行iOS應用,並且可以無縫的將程式碼拷貝到XCODE上編譯並執行。其實現的原理是Visual studio2017本身提供了一個OC語言編譯器,同時他內部也提供了一個Cocoa UI框架的模擬實現版本,所以能在上面執行iOS應用。
從上面的幾個例子中我們可以發現一個特點就是:一個系統各個層次之間的呼叫總是通過某些約定的規則或者定義的介面來進行的,並且呼叫者是不知道也不需要知道提供者是如何實現這些能力的,總是一切皆是介面:
![介面的形式提供層次之間的呼叫](https://i.iter01.com/images/515b3d42e470f62b16a08a6df5787a8dbb329345d2b01611a6ba4268e2f36b8d.png)
正是因為有這些介面的定義以及標準的形成,我們才可以將原本真實的實現模擬出另外一個虛擬的實現出來。這也就是所謂的虛擬化的本質。虛擬化可以發生在任何一個層面,也可以進行全域性虛擬或者是部分虛擬。我們可以對CPU的指令以及硬體介面進行模擬從而構建出一套類似vmware一樣的虛擬機器軟體來執行任何作業系統;我們也可以對作業系統提供的介面API進行模擬從而構建出一套類似Wine一樣的虛擬Windows執行環境出來;我們還可以對作業系統所提供的檔案系統或者儲存系統來進行模擬從而提供出一套類似Docker之類的應用容器出來;我們也可以對Cocoa Framework進行模擬從而提供出一個套類似Vistual studio2017上能執行和編寫OC應用的編譯環境來(微軟開源了這個框架:微軟的OC實現支援)。
![虛擬的實現原理](https://i.iter01.com/images/7c12694b04d33ffbd98db6dbb0a9688b18e9617016345605374f90cbf4a14deb.png)
虛擬化首先要先介面標準定義,然後再在別人介面之上完成了一套自己的實現。現在的系統從上層的軟體到下層的硬體之間都是通過介面協議進行呼叫的,因此我們可以在各個層次上都實現虛擬的能力。
敬請期待下一篇:深入iOS系統底層之XCODE對彙編的支援介紹
目錄
- 深入iOS系統底層之組合語言
- 深入iOS系統底層之指令集介紹
- 深入iOS系統底層之XCODE對彙編的支援介紹
- 深入iOS系統底層之CPU暫存器介紹
- 深入iOS系統底層之機器指令介紹
- 深入iOS系統底層之賦值指令介紹
- 深入iOS系統底層之函式呼叫介紹
- 深入iOS系統底層之其他常用指令介紹
- 深入iOS系統底層之函式棧介紹
- 深入iOS系統底層之函式棧(二)介紹
- 深入iOS系統底層之不定引數函式實現原理介紹
- 深入iOS系統底層之在高階語言中嵌入組合語言介紹
- 深入iOS系統底層之常見的彙編程式碼片段介紹
- 深入iOS系統底層之OC中的各種屬性以及修飾的實現介紹
- 深入iOS系統底層之ABI介紹
- 深入iOS系統底層之編譯連結過程介紹
- 深入iOS系統底層之可執行檔案結構介紹
- 深入iOS系統底層之MACH-O檔案格式介紹
- 深入iOS系統底層之映像檔案操作API介紹
- 深入iOS系統底層之知名load command結構介紹
- 深入iOS系統底層之程式載入過程介紹
- 深入iOS系統底層之靜態庫介紹
- 深入iOS系統底層之動態庫介紹
- 深入iOS系統底層之framework介紹
- 深入iOS系統底層之基地址介紹
- 深入iOS系統底層之模組內函式呼叫介紹
- 深入iOS系統底層之模組間函式呼叫介紹
- 深入iOS系統底層之機器指令動態構造介紹
- 深入iOS系統底層之crash問題解決方法
- 深入iOS系統底層之無上下文crash解決方法
- 深入iOS系統底層之常用工具和命令的實現原理介紹
- 深入iOS系統底層之真實的OC類記憶體結構介紹