咱們看到的計算機系統都只限於機器語言程序級。處理器執行一系列指令天天指令執行某個簡單操做,它們被編碼爲由一個或多個字節序列組成的二進制格式。在本章的學習中,咱們主要了解ISA抽象的做用以及瞭解流水線和實現方式。程序員
字節序列轉換爲Y86-64指令的方法總結以下:網絡
經過代碼部分肯定指令長度,從而以指令爲單位劃分字節序列;
經過功能部分肯定具體的指令;
經過寄存器指示符字節肯定指令中涉及的寄存器;
經過轉換數值部分以小段法編碼的數字來肯定當即數、偏移量、絕對地址等值。函數
一個處理器支持的指令和指令的字節級編碼稱爲它的指令集體系結構ISA。
ISA模型(概念抽象層):CPU容許的指令集編碼,且順序地執行指令,也就是先取出一條指令,等到她執行完畢,再開始下一條。然而,現代處理器的實際工做方式可能跟ISA隱含的計算模型截然不同。經過同時處理多條指令的不一樣部分,處理器能夠得到較高的性能。但其必須對外表現出符合ISA模型的執行結果。性能
CPU硬件簡介
大多數現代電路設計都是用信號線上的高電壓和低電壓來表示不一樣的位值。
要實現一個數字系統須要三個主要的組成部分:
計算對位進行操做的函數的組合邏輯(ALU)
存儲位的存儲器元素(寄存器)
控制存儲器元素更新的時鐘信號
邏輯門是數字電路的基本計算元素,它們產生的輸出,等於它們輸入位值的某個布爾函數。將不少邏輯門組合成一個網,就能構建計算塊,稱爲組合電路。(至關於一個表達式)
算術/邏輯單元(ALU)是一種很重要的組合電路,這個電路有三個輸入:兩個數據輸入及一個控制輸入。根據控制輸入的設置,電路會對數據輸入執行不一樣的算術或邏輯操做。
存儲器和時鐘
組合電路從本質上講,不存儲任何信息。它們只是簡單地響應輸入信號,產生等於輸入的某個函數的輸出。爲了產生時序電路,也就是有狀態而且在這個狀態上進行計算的系統,咱們必須引入按位存儲信息的設備。
存儲設備都是由同一個時鐘控制,時鐘是一個週期性信號,決定了何時要把新值加載到設備中。
大多數時候,寄存器都保持在穩定狀態(用x表示),產生的輸出等於它的當前狀態。信號沿着寄存器前面的組合邏輯傳播,這時,產生了一個新的寄存器輸入(用y表示),但只要時鐘是低電位的,寄存器的輸出就仍然保持不變。當時鍾變成高電位的時候,輸入信號才加載到寄存器中,成爲下一個狀態y,直至下一個時鐘的上升沿。
寄存器是做爲電路不一樣部分中的組合邏輯之間的屏障。每當每一個時鐘到達上升沿時,值纔會從寄存器的輸入傳送到輸出。
寄存器文件(通用寄存器組成的邏輯塊) 有兩個讀端口,還有一個寫端口。電路能夠讀兩個程序寄存器的值,同時更新第三個寄存器的狀態。每一個端口都有一個地址輸入,代表選擇哪一個程序寄存器。
雖然寄存器文件不是組合電路,由於它有內部存儲。不過,從寄存器文件讀數據就好像它是一個以地址爲輸入、數據爲輸出的一個組合邏輯塊。
指令編碼
指令集的一個重要性質就是字節編碼必須有惟一的解釋。任意一個字節序列要麼是一個惟一的指令序列的編碼,要麼就不是一個合法的字節序列。由於每條指令的第一個字節有惟一的代碼和功能組合,給定這個字節,咱們就能夠決定全部其餘附加字節的長度和含義。
每條指令須要1——6個字節不等,這取決於須要哪些字段。每條指令的第一個字節代表指令的類型:高4位是代碼部分(例:6爲整數類操做指令),低4位是功能部分(例:1爲整數類中的減法指令) 61合起來即爲sub指令。
處理一條指令的序列:
取指(fetch)
取值階段從存儲器讀取指令字節,放到指令存儲器(CPU中)中,地址爲程序計數器(PC)的值。
它按順序的方式計算當前指令的下一條指令的地址(即PC的值加上已取出指令的長度)
譯碼(decode)
ALU從寄存器文件(通用寄存器的集合)讀入最多兩個操做數。(即一次最多讀取兩個寄存器中的內容)
執行(execute)
在執行階段會根據指令的類型,將算數/邏輯單元(ALU)用於不一樣的目的。對其餘指令,它會做爲一個加法器來計算增長或減小棧指針,或者計算有效地址,或者只是簡單地加0,將一個輸入傳遞到輸出。
條件碼寄存器(CC)有三個條件位。ALU負責計算條件碼新值。當執行一條跳轉指令時,會根據條件碼和跳轉類型來計算分支信號cnd。
訪存(memory)
訪存階段,數據存儲器(CPU中)讀出或寫入一個存儲器字。指令和數據存儲器訪問的是相同的存儲器位置,可是用於不一樣的目的。
寫回(write back)
寫回階段最多能夠寫兩個結果到寄存器文件。寄存器文件有兩個寫端口。端口E用來寫ALU計算出來的值,而端口M用來寫從數據存儲器中讀出的值。
更新PC(PC update)
根據指令代碼和分支標誌,從前幾步得出的信號值中,選出下一個PC的值。
咱們以SEQ(sequential 順序的)處理器爲例講解CPU的基本原理。每一個時鐘週期上,SEQ執行處理一條完整指令所需的全部步驟。不過這須要一個很長的時鐘週期時間,所以時鐘週期頻率會低到不可接受。學習
SEQ的時序
組合邏輯不須要任什麼時候序或控制——只要輸入變化了,值就經過邏輯門網絡傳播。
咱們也將讀隨機訪問存儲器(寄存器文件、指令存儲器和數據存儲器)當作和組合邏輯同樣的操做。(寫隨機訪問存儲器須要等待高電平)
因爲指令存儲器只用來讀指令,所以咱們能夠將這個單元當作是組合邏輯。(內存向指令存儲器中寫指令是CPU外部的事件 不屬於CPU內的時序)
每一個時鐘週期,程序計數器都會裝載新的指令地址。
只有在執行整數運算指令時,纔會裝載條件碼寄存器。
只有在執行mov、push、call指令時,纔會寫數據存儲器。
要控制處理器中活動的時序,只須要寄存器和存儲器的時鐘控制。
由於指令運行計算的結果,寫入寄存器或存儲器中。
咱們能夠把取指、譯碼、執行等過程看作是組合邏輯的處理過程(由於它們不涉及寫入寄存器)。把寫回看作是另外一個過程。fetch
流水線原理
咱們經過將執行每條指令所需的步驟組織成一個統一的流程,就能夠用不多量的各類硬件單元以及一個時鐘來控制計算的順序,從而實現整個處理器。不過這樣一來,控制邏輯就必需要在這些單元之間路由信號,並根據指令類型和分支條件產生適當的控制信號。(CPU內有三種總線:控制總線、地址總線、數據總線)
SEQ處理器不能充分利用硬件單元,由於每一個單元只在整個時鐘週期的一部分時間內才被使用。咱們會看到引入流水線能得到更好的性能。在流水線化的系統中,待執行的任務被劃分紅了若干獨立的階段。
流水線化的一個重要特性就是增長了系統的吞吐量,也就是單位時間內服務的顧客總數,不過它也會輕微地增長延遲,也就是服務一個用戶所須要的時間。(咱們以前的設計是一條指令執行完,下條指令才能進入CPU,(所不一樣的是時鐘週期的粒度)。流水線化是容許多條指令在CPU中,每條指令在CPU中的時間是同樣的,哪怕你一個週期就執行完了,你也得等剩下的階段結束,使後面的指令被延遲了。
雖然流水線化,全部指令在CPU中待的時間都同樣(且都按最耗時指令算的),但它們的時間是重疊的。假設一條指令在CPU中待6ms,那麼12ms能處理7條指令,而非流水線,雖然一條指令最多執行6ms,但它們的時間是相加的,12ms可能只執行3條。12=6+2+4)
流水化的硬件系統
假設將系統執行的計算分紅三個階段(A、B和C),每一個階段須要100ps,而後在各個階段之間放上流水線寄存器,這樣每條指令都會按照三步通過這個系統,從頭至尾須要三個時鐘週期。
(流水線寄存器的做用:做爲電路不一樣部分中的組合邏輯之間的屏障。保存每步組合邏輯的運算結果。這是爲了分割流水而插入的寄存器。)
流水線,在穩定狀態下,三個階段應該都是活動的,每一個時鐘週期,一條指令離開系統,一條新的進入。
這樣,咱們一個階段的時間,至關於運行了一條指令,在這個系統中,咱們將時鐘週期設爲100+20=120ps,獲得的吞吐量大約爲8.33GIPS。由於處理一條指令須要3個時鐘週期,因此這條流水線的延遲就是3*120=360ps。非流水運行一條完整指令須要320ps。(從宏觀總體上看,一個時鐘週期運行了一條指令(這條指令是由多條指令的各階段拼合的),而從單條指令的執行看,須要3個時鐘週期執行一條完整指令。)咱們將系統吞吐量提升到原來的8.33/3.12=2.67倍,代價是增長一些硬件(流水線寄存器),以及延遲的少許增長(360/320=1.12)。延遲變大是因爲增長的流水線寄存器的時間開銷。時鐘週期的時間就是流水線分割的一個階段的時間,這樣,從宏觀上看,是一個時鐘週期執行一條指令。
流水線的侷限性
一、不一致的劃分
以前的是一個理想的流水線化的系統,每一個階段須要的時間都相同。而實際系統經過各階段的延遲通常是不一樣的。且運行時鐘的速率是由最慢階段的延遲限制的。(即系統吞吐量受最慢階段的速度所限制)
二、流水線過深,收益反而降低
例如,咱們把計算分紅6個階段,每一個階段須要50ps。在每對階段之間插入流水線寄存器就獲得了一個六階段流水線。這個系統的最小時鐘週期爲50+20=70ps,吞吐量爲14.29GIPS。性能比3階段流水提升了14.29/8.33=1.71倍。因爲經過流水線寄存器的延遲,吞吐量並無加倍。這個延遲成了流水線吞吐量的一個制約因素。爲了提升時鐘頻率,現代處理器採用了很深的(15或更多的階段)流水線。編碼
分支預測
流水線化設計的目的就是每一個時鐘週期都發射一條新指令,要作到這一點,咱們必須在取出當前指令以後,立刻肯定下一條指令的位置。
但若是取出的指令是條件分支指令,要到幾個週期後,也就是指令經過執行階段以後,咱們才能知道是否要選擇分支。相似的,若是取出的指令是ret,要到指令經過訪存階段,才能肯定返回地址。
對條件轉移來講,咱們既能夠預測選擇了分支,那麼新PC值應爲valC,也能夠預測沒有選擇分支,那麼新PC值應爲valP。
對ret指令,可能的返回值幾乎是無限的,由於返回地址位於棧頂的字,其內容能夠是任意的。在設計中,咱們不會試圖對返回地址作任何預測。只是簡單地暫停處理新指令,直到ret指令經過寫回階段。
不管哪一種狀況,咱們都必須以某種方式來處理預測錯誤的狀況,由於此時已經取出並部分執行了錯誤的指令。
(流水線懲罰待寫)
流水線冒險
使用流水線技術,當相鄰指令間存在相關時會致使出現問題。
這些相關有:
一、數據相關:下一條指令會用到這一條指令計算出的結果
二、控制相關:一條指令要肯定下一條指令的位置,例如在執行跳轉、調用或返回指令時。
這些相關可能會致使流水線產生計算錯誤,稱爲冒險。
用暫停來避免數據冒險
暫停(stalling)是避免冒險的一種經常使用技術。讓一條指令停頓在譯碼階段,直到產生它的源操做數的指令經過了寫回階段,這樣咱們的處理器就能避免數據冒險。操作系統
暫停技術就是讓一組指令阻塞在它們所處的階段,而容許其餘指令繼續經過流水線。
用轉發來避免數據冒險
在譯碼階段從寄存器文件中讀入源操做數,可是對這些源寄存器的寫有可能要在寫回階段才能進行。與其暫停直到寫完成,不如簡單地將要寫的值傳到流水線寄存器E做爲源操做數。
(即,咱們沒必要等到irmovl $10, %edx和irmovl $3, %eax 完成對寄存器的寫更新以後再繼續addl,而是在addl譯碼階段發現須要%edx、%eax值,譯碼邏輯不從寄存器文件中去讀,而是用前面階段未寫入寄存器的值。)這種將結果直接從一個流水線階段傳到較早階段的技術稱爲數據轉發。在週期4中,譯碼階段邏輯發現有在訪存階段中對寄存器%edx未進行的寫,還發如今執行階段中正在計算寄存器%eax的新值。它用這些值,而不是從寄存器文件中讀出的值,做爲valA和valB的值。
加載/使用數據冒險
有一類數據冒險不能單純用轉發來解決,由於存儲器讀(訪存階段)在流水線發生的比較晚。
咱們能夠將暫停和轉發結合起來,避免加載/使用數據冒險。(既然是來不及發送給後面的指令,那就讓後面的指令暫停幾個週期,再發送)
當mrmovl指令經過執行階段時,流水線控制邏輯發現譯碼階段中的指令(addl)須要從存儲器中讀出的結果。它會將譯碼階段中的addl指令暫停一個週期,致使執行階段中插入一個氣泡。 mrmovl指令從存儲器中讀出的值能夠從訪存階段轉發到譯碼階段中的addl指令。
這種用暫停來處理加載/使用冒險的方法稱爲加載互鎖。加載互鎖和轉發技術結合起來足以處理全部可能類型的數據冒險。
異常處理
異常能夠由程序執行從內部產生,也能夠由某個外部信號從外部產生。
簡單的三種內部異常:
一、halt指令
二、非法指令
三、訪問非法地址
(還有一些外部異常:網口收到新包、用戶點擊鼠標等)
在簡化的ISA模型中,當處理器遇到異常時,會中止,設置適當的狀態碼,且應該是到異常指令以前的全部指令都已經完成,而其後的指令都不該該對程序員可見的狀態產生任何影響。在一個更完整的設計中,處理器會繼續調用異常處理程序,這是操做系統的一部分。
通常地,經過在流水線結構中加入異常處理邏輯,咱們會在每一個流水線寄存器中包括一個狀態碼Stat。若是一條指令在其處理器中於某個階段產生了一個異常,這個狀態字段就被設置成指示異常的種類。
異常狀態和該指令的其餘信息一塊兒沿着流水線傳播,直到它到達寫回階段。在此,流水線控制邏輯發現了異常,並中止執行。
異常事件不會對流水線中的指令流有任何影響,除了會禁止流水線中後面的指令更新程序員的可見狀態(條件碼寄存器和存儲器),直到異常指令到達最後的流水線階段。
由於指令到達寫回階段的順序與它們在非流水化的處理器中執行的順序相同,因此咱們能夠保證第一條遇到異常的指令會第一個到達寫回階段,此時程序執行會中止,流水線寄存器(W寫回)中的狀態碼會被記錄爲程序狀態。設計