1、現(xiàn)代計算機體系結(jié)構(gòu)(Architecture,也譯作架構(gòu)或者系統(tǒng)結(jié)構(gòu))和CPU微體系結(jié)構(gòu)(Microarchitecture,也譯作微架構(gòu)或者微結(jié)構(gòu))
現(xiàn)代計算機的體系結(jié)構(gòu)基本都是基于馮·諾依曼體系結(jié)構(gòu),也就是“存儲程序方式”,即程序指令代碼(指令)和數(shù)據(jù)都放在內(nèi)存中,運行時CPU從內(nèi)存中逐條取指令并執(zhí)行,指令還可以在必要時訪問內(nèi)存數(shù)據(jù)。
哈佛結(jié)構(gòu)是馮·諾依曼結(jié)構(gòu)的一種變形,特點是將程序(指令)存儲器和數(shù)據(jù)存儲器分開,即有兩塊獨立的內(nèi)存,一塊存放指令,一塊存放數(shù)據(jù),但沒有脫離“存儲程序方式”這種基本的方式,因此仍然屬于馮·諾依曼結(jié)構(gòu)的變形或者改進。
對于程序員,即使是匯編語言甚至機器語言程序員,最多也只能涉及到內(nèi)存指令和數(shù)據(jù)這一級,計算機暴露給程序員的,只能是馮·諾依曼結(jié)構(gòu)或者哈佛結(jié)構(gòu),稱為計算機體系結(jié)構(gòu)(Architecture)。
但CPU本身是由運算器、控制器、寄存器等部件組成的硬件電路,內(nèi)存指令被取入CPU后,在CPU內(nèi)部還要經(jīng)過復雜的指令譯碼等過程,才能驅(qū)動CPU硬件電路相應部件,按照指令具體需求進行相應動作,最終完成指令執(zhí)行,這一過程是由CPU內(nèi)部的取指令、指令譯碼、部件驅(qū)動等硬件電路完成的,不同CPU差異很大,即使指令集相同的CPU也可以有不同的電路實現(xiàn)方法,這種CPU內(nèi)部硬件電路的結(jié)構(gòu),稱為CPU的微體系結(jié)構(gòu)(Microarchitecture)。
CPU微結(jié)構(gòu)是設計CPU的核心,對于程序員而言,CPU微結(jié)構(gòu)并不暴露給程序員,最底層的程序員的極限也只能是通過CPU指令來訪問CPU,換而言之,程序員使用CPU的最小操作粒度是指令。但CPU指令在CPU內(nèi)部可能還會被解析成為更小的執(zhí)行單元,最終驅(qū)動CPU的單個硬件電路部件,也就是說,在CPU微結(jié)構(gòu)中有更小的操作粒度,例如微操作(MicroOP,μOP)或者微指令,它們都是由CPU微結(jié)構(gòu)決定的。
或者說,CPU對于程序員而言也是一個黑盒子,程序員只要按照CPU指令集,用相應的指令編寫程序(如果是高級語言,則由編譯器或者解釋器將高級語言語句最終轉(zhuǎn)換為CPU指令),并將指令程序和相應數(shù)據(jù)加載到內(nèi)存(現(xiàn)代通常由操作系統(tǒng)完成)即可執(zhí)行程序,程序員只訪問到計算機體系結(jié)構(gòu)這一級,至于指令在CPU內(nèi)部是怎么進一步解析并最終執(zhí)行的,那是CPU微結(jié)構(gòu)決定的,對程序員不開放,程序員不關(guān)心,通常也不應該關(guān)心,實際也無法關(guān)心。
2、如何提高CPU的運行速度
CPU執(zhí)行一條指令,一般來說,最少也要經(jīng)過從內(nèi)存中取指令,將指令譯碼解析成微操作(μOP),微操作最終驅(qū)動硬件電路部件三個步驟(簡稱取指令、譯碼和執(zhí)行),如果執(zhí)行一條指令,要等到這三個步驟都完成后,才能執(zhí)行下一條指令,則一條指令執(zhí)行時間過長(用CPU硬件術(shù)語說就是消耗多個時鐘周期),CPU運行速度就無法得到有效提高。
如果在第一條指令譯碼時,同時取第二條指令;然后等到第一條指令執(zhí)行時,第二條指令同時譯碼,同時還取第三條指令……這樣周而復始,取指令、譯碼和執(zhí)行就可以重疊進行,就好比生產(chǎn)流水線上的工人,第一個工人對第一個產(chǎn)品加工好第一道工序后,將產(chǎn)品傳遞給第二個工人加工第二道工序,這是第一個工人可以對第二個產(chǎn)品加工第一道工序了……這樣可以避免工人不必要的空閑等待,生產(chǎn)效率就會大大提高,這一提高CPU運行速度的方法,也就稱為“流水線”,現(xiàn)代CPU均使用流水線技術(shù)。
流水線有效減少了單條指令的平均執(zhí)行時間,但指令仍然是一條條順序執(zhí)行的,實際上,很多指令是彼此不相關(guān)的,例如訪問兩組完全不同寄存器的兩條指令,它們完全可以并行執(zhí)行,執(zhí)行順序的先后并不影響最終執(zhí)行結(jié)果,對于這樣的不相關(guān)指令,完全可以設計多個執(zhí)行部件電路,直接扔到不同執(zhí)行部件中去并行執(zhí)行,稱為亂序(Out-of-order)執(zhí)行,可以進一步提高指令執(zhí)行速度。亂序執(zhí)行在現(xiàn)代CPU中廣泛應用,甚至對于有相關(guān)性的指令,也可以采用寄存器換名等手段將它們變成不相關(guān)指令,從而進行亂序執(zhí)行。
但流水線和亂序執(zhí)行都會碰到一個問題,從原理上講它們僅適用于純順序執(zhí)行的指令,一旦遇到分支,即條件跳轉(zhuǎn)指令,因為不執(zhí)行到條件跳轉(zhuǎn)指令本身,是沒法知道程序轉(zhuǎn)向何處執(zhí)行的,也就是條件跳轉(zhuǎn)指令的下一條指令在未執(zhí)行前不確定,因此無法預先取得條件跳轉(zhuǎn)指令的后續(xù)指令,這時流水線和亂序執(zhí)行都會失效,因為它們的前提是預先取得后續(xù)指令。
為了盡量解決這個問題,現(xiàn)代CPU廣泛使用“分支預測”手段,也就是預測條件跳轉(zhuǎn)指令會跳向哪個分支,然后對這個分支進行預取后續(xù)指令。分支預測的常用策略是:如果某一段時間內(nèi)某一條件跳轉(zhuǎn)都走向某一固定分支,則可以預測這條條件跳轉(zhuǎn)指令下一次很大可能也走向這一分支。
典型例子:程序中的循環(huán)結(jié)構(gòu)一般要循環(huán)多次才結(jié)束,那么在循環(huán)結(jié)束之前,判斷循環(huán)條件的條件跳轉(zhuǎn)指令顯然都是走向繼續(xù)循環(huán)分支的。
分支預測配合流水線和亂序執(zhí)行,能夠大大提高CPU的運行速度,因此現(xiàn)代CPU微結(jié)構(gòu)基本都使用這種設計。
3、分支預測帶來的問題——指令執(zhí)行的“回滾”
分支預測并不能保證100%的成功預測,一旦預測失敗,也就是最終執(zhí)行到條件跳轉(zhuǎn)指令時,發(fā)現(xiàn)跳轉(zhuǎn)目標不是先前預測的分支方向,那么按照分支預測預取的后續(xù)指令實際上失效,這些指令已經(jīng)完成的工作必須“取消”掉,否則就會造成錯誤的指令執(zhí)行。
用一個程序員容易理解的比喻:分支預測的后續(xù)指令執(zhí)行,好比一個“事務”,如果分支預測是正確的,那么“事務”可以“提交”,這些后續(xù)指令就真正起作用;如果最終執(zhí)行到條件跳轉(zhuǎn)指令時發(fā)現(xiàn)分支預測是錯誤的,則“事務”必須“回滾”,即使后續(xù)指令已經(jīng)執(zhí)行了,甚至是亂序執(zhí)行了,已經(jīng)完成的工作也都必須全部“撤銷”,后續(xù)指令要看起來沒有起任何作用,重新到正確的分支取新的指令執(zhí)行。
理論上說,不管指令執(zhí)行“提交”還是“回滾”,都只與CPU微結(jié)構(gòu)相關(guān),其過程程序員應該看不到,程序員只能看到宏觀指令按照程序流程執(zhí)行,CPU內(nèi)部對程序員仍然應該是黑盒子。
4、克服CPU運行速度與內(nèi)存訪問速度的差異——高速緩存(Cache)
目前CPU主頻已經(jīng)達到3GHz以上,普遍采用多核并行,盡管主內(nèi)存(DDR SDRAM)的主頻已經(jīng)達到2GHz—3GHz甚至更高,也無法完全滿足多核CPU運行速度的需求,因為指令執(zhí)行還是必須從內(nèi)存中取指令,如果內(nèi)存訪問速度不夠,CPU運行速度會受到內(nèi)存訪問速度的限制。
為了克服這個問題,目前采用在CPU與主內(nèi)存之間插入多級高速緩存(Cache)的方法,Cache是一種訪問速度極高的存儲器,甚至可以集成在CPU內(nèi)部,成為CPU微結(jié)構(gòu)的一部分。Cache與主內(nèi)存之間以塊為單位交換數(shù)據(jù),塊長一般為數(shù)十字節(jié)。
當CPU需要訪問內(nèi)存,例如從內(nèi)存中取指令時,第一次需要先將相應內(nèi)存塊一次性讀入到空閑的Cache塊,CPU再直接訪問Cache塊,此時內(nèi)存訪問速度會慢一些,因為存在主內(nèi)存與Cache之間傳輸成塊數(shù)據(jù)的時間;CPU第二次訪問相同塊內(nèi)存時,即可直接訪問Cache塊,而無須訪問主內(nèi)存,內(nèi)存訪問速度會快得多。
主內(nèi)存—Cache系統(tǒng)構(gòu)成現(xiàn)代CPU的內(nèi)存儲器系統(tǒng),其原理與操作系統(tǒng)中的硬盤—內(nèi)存系統(tǒng)構(gòu)成虛擬內(nèi)存的原理極其相似。
5、指令執(zhí)行的“回滾”在主內(nèi)存—Cache系統(tǒng)留下的“痕跡”
如上所述,如果分支預測失敗,則分支預測預取的后續(xù)指令,哪怕已經(jīng)亂序執(zhí)行了多條指令,也必須“回滾”,指令在CPU微結(jié)構(gòu)中已經(jīng)完成的工作必須全部“撤銷”。
“撤銷”指令是容易的,指令最終完成的工作,無外乎是對寄存器或者內(nèi)存的修改,可以暫且將修改“緩存”起來,如果“撤銷”,最終不真正修改寄存器或者內(nèi)存即可。
但對于內(nèi)存讀寫指令(在CPU設計中通常稱為Load/Store指令或者LD/ST指令),以讀內(nèi)存指令為例,如果最終“提交”,就必須讀取實際的內(nèi)存地址,如果相應內(nèi)存塊還沒有被讀入到Cache塊,讀取速度就會受到影響,因此在讀內(nèi)存指令最終“提交”或者“回滾”之前,CPU微結(jié)構(gòu)一般會事先將Cache塊準備好,也就是如果相應內(nèi)存塊還沒有被讀入到Cache塊則預先讀入。
也就是說,即使分支預測失敗,已經(jīng)亂序執(zhí)行的多條預取指令中只要有讀內(nèi)存指令,就算最后被“回滾”,對廣義的CPU微結(jié)構(gòu)還是有影響的——相應內(nèi)存塊已經(jīng)讀入到了Cache塊。
而內(nèi)存塊是否已經(jīng)讀入到Cache塊,訪問速度是有一定差異的,這相當于“回滾”的讀內(nèi)存指令在CPU微結(jié)構(gòu)中留下的“痕跡”,這個“痕跡”是可以被作為“側(cè)信道”利用的。
6、Meltdown攻擊原理的通俗簡明解釋
操作系統(tǒng)的內(nèi)核數(shù)據(jù)是受到CPU微結(jié)構(gòu)保護的,用戶模式的應用程序無法訪問,如果訪問是要引發(fā)CPU錯誤異常的。
構(gòu)造一個分支,先檢測讀取內(nèi)存的地址是否合法,合法就讀取相應地址內(nèi)存字節(jié),然后根據(jù)內(nèi)存字節(jié)的值,讓內(nèi)存字節(jié)的值與映射到不同Cache塊的內(nèi)存塊對應起來,再故意讀取一下映射到不同Cache塊的內(nèi)存塊;如果訪問內(nèi)存的地址非法,例如操作系統(tǒng)內(nèi)核數(shù)據(jù)地址,直接不讀取。
顯然,這樣的分支,無論讀取合法地址還是非法地址都是不會出錯的。
用大循環(huán)執(zhí)行多次這個分支,前若干次,讀取內(nèi)存地址都是合法的,“訓練”CPU的分支預測,讓CPU微結(jié)構(gòu)認為下次也應該走向讀取內(nèi)存這一分支。
然后,突然執(zhí)行一次非法的操作系統(tǒng)內(nèi)核數(shù)據(jù)地址讀取。
按道理說,讀取內(nèi)存地址非法,應該走向不讀取這一分支,可是CPU的分支預測已經(jīng)被“訓練”成了“條件反射”,CPU稀里糊涂地預取了讀取非法地址的指令,并亂序并行執(zhí)行,只是暫時沒有“提交”,因為沒有“提交”,即使讀取非法地址內(nèi)存,也不會引發(fā)CPU異常;而且此時根據(jù)非法地址內(nèi)存字節(jié)的值,故意讀取映射到不同Cache塊內(nèi)存塊的指令,相應的Cache塊也被CPU微結(jié)構(gòu)準備好了(因為是并行亂序執(zhí)行嘛),也就是相應內(nèi)存塊已經(jīng)讀入到了Cache塊。
當然,最后CPU發(fā)現(xiàn)這次分支預測錯了,沒關(guān)系,預取的指令亂序并行執(zhí)行“回滾”,讀取非法地址的指令根本沒執(zhí)行,沒有引發(fā)CPU異常,皆大歡喜。
可是,“痕跡”卻悄悄留下了,與非法地址內(nèi)存字節(jié)值有意對應起來的內(nèi)存塊已經(jīng)讀入到了Cache塊。
用程序遍歷一下所有可能對應的內(nèi)存塊,看誰訪問速度最快,誰就很可能在Cache中,而非法地址對應的操作系統(tǒng)內(nèi)核數(shù)據(jù)字節(jié)值是有意與內(nèi)存塊對應的,原本在用戶模式下不能訪問的操作系統(tǒng)內(nèi)核數(shù)據(jù)字節(jié)值就被推算出來。
這就是所謂的“側(cè)信道泄漏”。
7、這一攻擊實質(zhì)的一句話解釋
CPU微結(jié)構(gòu)內(nèi)部信息通過側(cè)信道向宏觀計算機體系結(jié)構(gòu)的泄漏。
8、一句話教訓
設計CPU追求速度快是理所當然的,但速度和安全性之間要有平衡點,微結(jié)構(gòu)無論怎樣追求高速優(yōu)化,屁股要擦干凈,不要向宏觀體系結(jié)構(gòu)泄漏內(nèi)部信息。
1965年intel創(chuàng)世人之一、時任仙童半導體公司電子工程師的戈登摩爾提出了摩爾定律,對人類的計算之路的快速進步做出了預言。過去二十年,人類在互聯(lián)網(wǎng)的帶動下,信息化發(fā)展一路狂奔。這種對速度的追求一定程度上,透支的是安全的掉隊。也許這正是一個重新定義平衡點的時刻。