1. 程式人生 > >計算機體系結構之一--體系結構【譯】

計算機體系結構之一--體系結構【譯】

IV 體系 課程 c編譯器 表達 世界 高性能 motorola 跳過

說明

本文翻譯自斯坦福開放課程【編程範式】的系列課件,有刪改。

本系列會持續更新,預計一周發布1到2篇。

如有意見/建議或是存在版權問題,歡迎園友指正。

轉載無需通知本人,但請註明出處,謝謝!

計算機體系結構

下面這個簡明的圖片描述了計算機原型的主要特征。CPU是執行所有工作的場所,內存是所有代碼和數據存放的地方,連接這兩者的通路叫做總線。

技術分享圖片

CPU

當內存保存程序和數據的時候,中央處理單元執行所有的工作。CPU有兩個部分--寄存器和算數邏輯單元(ALU)。一個寄存器就像計算器中的臨時內存--每個寄存器能存儲一個值,這些值用於後邊的計算。每個寄存器只能保存一個字。有的寄存器專用於保存特殊類型的數據--浮點數,地址,等等。寄存器存取速度比內存快得多,但是寄存器的可用數量相比內存來就小得多了。有一種很特殊的寄存器叫程序計數器,簡稱PC

,保存著當前執行指令的地址。

ALUCPU的一部分,負責執行加法、乘法等算數操作和比較等邏輯操作。大部分現代的、高性能的CPU實際除了ALU之外,還包括了若幹特殊的邏輯單元,允許CPU執行並行計算。然而,這種復雜度只存在於CPU內部。我們只消把CPU抽象一下,假定它會按照指令在內存中出現的順序,一次一條地執行下去。

為了這份講義的平衡性,我們把註意力集中在一個精簡指令集計算機(RISC)處理器的指令集上。相對來說,處理器指令集更簡潔的就是RISC。精簡指令集是處理器的好搭檔,接著讓我們關註下怎樣最大程度地利用處理器的指令集。RISC處理器沒有那些牛逼的指令,也沒有那些用早期的復雜指令集(CISC

)設計的尋址模式。RISC的好處是易於學習,因為指令集已經被很好地精簡過了。

假設我們有一個內置32個寄存器的處理器,每個寄存器能放置一個4字節大小的字。處理器支持3類指令:Load/Store 指令能在寄存器和內存之間來回地移動字節,ALU指令作用於寄存器,Branch/Jump指令修改接下來要執行的指令。

Load指令

load指令把字節讀到寄存器中。來源可能是一個常量值,另一個寄存器,或者是內存中的一個定位。在我們的簡單語言裏,一個內存定位被表達成Mem[address]address可能是一個數字常量,一個寄存器,或者一個寄存器加上一個常數偏移量。LoadStore

從給定的地址開始搬動字節,通常是一次移動一整個字。如果要移動小於一個字的字節,可以用變體“=.1”(1字節)和“=.2”(2字節)。

把常量23搬運到寄存器4號
R4 = 23

把寄存器2號中的常量復制到寄存器3號
R3 = R2

從內存中地址是244的地方把一個字符(一個字節)搬運到寄存器6號
R6 = .1 Mem[244]

從寄存器1號讀取一個地址,把保存在寄存器5號中的整個字搬運到內存中這個地址對應的地方
R5 = Mem[R1]

取出寄存器1號中存放的地址,在內存中找到這個地址,往後跳過8個字節,從這個新的地址開始,搬運一個字到寄存器4號。這被稱“偏移量”模式,它是RISC處理器所支持的尋址模式中最奇怪的一個。
R4 = Mem[R1+8]

為了說明這一切是怎樣和真實世界關聯的,下面給出Motorola 68000編譯器執行load指令時的情況:

把長常量(4個字節)15搬運到寄存器d2
move1 #15, d2

在內存中找到地址0x40x,從這個地址開始把一個長常量搬運到a0寄存器
move1 @#0x40c, a0

在Sparc編譯器裏,看起來是這個樣子:

把寄存器o0存放的地址取出,加上常數偏移量20(跳過20個字節),作為開始地址從內存把一個字加載到到寄存器o1
ld [ %o0 + 20 ], %o1

我們設計的匯編語法易讀又好學,只是和現代的RISC指令集比起來,它的基礎功能還是太少了。

Store指令

store指令基本上和load指令相反--它把值從寄存器搬回到內存中。在RISC體系結構中,沒辦法把字節直接從內存的一個地方移動到內存的另一個地方。你只能使用load指令把字節搬到寄存器裏,然後再用store指令把它們搬回到內存。

把數字37搬到(store)從位置400開始的字中
Mem[400] = 37

把存在寄存器R6中的字搬到(store)R1裏
Mem[R1] = R6

把R2中低位置開始的半個字搬到(store)內存中從1024開始的兩個字節
Mem[1024] = .2 R2

把寄存器R7中的字搬到(store)內存,開始地址比R1中的值多12
Mem[R1 + 12] = R7

ALU

算數邏輯單元(ALU)的指令很像計算器上的操作鍵。ALU的操作只會作用在寄存器和常量上。有的處理器甚至不允許ALU處理常量。(你需要先把常量裝載到寄存器裏)。

把R3的值加3,把結果搬到R1上
R1 = 6 + R3

用R2的值減去R3的值,把結果搬到R1上
R1 = R2 - R3

雖然我們會不加區分地濫用“+”號,但處理器通常持有兩個不同的版本,一個給整數用另一個給浮點數用,調用時使用兩條不同的指令,比如,Sparc使用addfadd。整數計算通常比浮點計算更有效率,因為操作更簡單(比如,不需要標準化)。除法是目前為止最昂貴的算術操作,對兩種類型都是,而且通常不是一條單指令,而是一個“微代碼”程序(可以當成一個非常快的、順手執行的函數)。

分流

默認情況下,CPU按一定的順序從內存接收和執行指令,從內存的低地址到高地址。branch指令修改這個默認規則。branch指令測試完一個條件,可能會通過修改PC寄存器的值來修改下一條要執行的指令。所有處理器都會大量使用的一種情形是,測試兩個值是否相等,若不等,一個是否比另一個小(或比另一個大)。branch指令執行測試時操作的兩個東西一定要是寄存器裏的值或者常數。branch指令被用於實現控制結構,像ifswitch、還有循環結構像forwhile

如果R1的值等於常數0,執行從內存地址344開始的指令,“branch判斷是否相等”
BEQ R1, 0, 344

如果R2的值小於R3的值,開始從當前指令地址加8的位置執行下一條指令,“branch判斷是否小於”
BLT R2, R3, PC+8

下面是全部的branch類指令:

BLT 判斷第一個參數是否小於第二個
BLE 小於等於
BGT 大於
BGE 大於等於
BEQ 相等
BNE 不相等

所有的branch指令都會比較它前兩個參數(都必須是常量或寄存器),然後把控制流強行轉到第三個參數那裏。目標地址可以被指定為一個絕對地址,比如356,或者是相對PC地址的偏移量,比如PC-8或者PC+12。後邊的寫法方便你跳過一些指令(包括向前跳),這和循環語句、條件判斷的執行模式十分接近。

還有一種不需要判斷的無條件跳轉,僅僅是立即把執行流轉移到一個新的地址。它有一點很像branch指令,支持絕對地址和PC偏移量。

無條件地從地址2000開使執行-像goto
Jmp 2000

從當前地址之後12個地址開始執行
Jmp PC-12

數據轉換

還有兩個實用的指令能把值在整型和浮點型之間做轉換。記住,浮點數1.0和整數1有著天差地別的位排列,所以需要一些指令來做轉換。這些指令也會在寄存器結構不一致的計算機之間傳遞浮點數和整數時被用到。

拿出R3總存放的比特位,轉成浮點型,存到R2中
R2 = ItoF R3

拿出R4中的比特位,從浮點型轉成整型,存回R4,這種直接轉化導致了信息丟失,小數部分被切去,然後丟失
R4 = FtoI R4

總結

雖然我們忽視掉了一些東西(算數邏輯單元中的邏輯部分,or/not,和一些用於支持函數調用/返回的操作),但這個小小的指令集給能你一種很好的想法:這些典型的CPU能用指令集來做些什麽。編譯器能提供像C一樣豐富而復雜的編程語言,產生了和for循環、數組引用,函數調用很相似的東西,並且把它們轉化成一個由簡單指令組成的恰當的序列。

計算機體系結構之一--體系結構【譯】