Spectre/Meltdown演義--專業篇(1)
微信公眾號 MindShare思享
CPU Speculation
上文說過為了提高CPU的效能,CPU會有speculation的行為,Speculation可以以很多方式實現,比如Speculation memory access,Speculation instruction execution等。
Speculation memory access 可以是speculativecache preload, speculation load.
Cache speculative prefetch
為了提供CPU的memory 訪問效率,特別是比較大塊的資料訪問,比如memcpy。通常可以假設資料訪問時有規則的,比如是連續資料訪問,或是以固定的地址間隔(stride)訪問。這樣的話CPU的cache hardware可以做一些優化。在很多的CPU裡(幾乎所有的Cortex-A CPU)中cache會自動監測CPU訪問的pattern,在下面的例子裡,如果程式碼的前三個LDR都導致了cache miss,那麼cache hardware就會開始推測software是想做連續的訪問,在做第三個LDR的時候(還不知道後面software code會不會真的繼續連續訪問),CPU會開始預取(speculative prefetch)第四個cache line資料。
大多數情況下,這是成立的,因為實際的軟體裡的確很多類似memcpy的大塊資料訪問。因此這種硬體優化在Intel,Arm的CPU裡非常常見。
除了監測連續的訪問,Cache還可以監測隔幾個cache lines的stride訪問方式,比如
Addr,
Addr+2*cache_line_size,
Addr+4*cache_line_size,
更有甚者,Cache還可以同時監測幾個pattern的訪問,比如一個隔2個stride和一個隔5的stride.
Speculative load instruction execution
很多時候軟體程式碼裡有control dependency, 導致一些hazard,
在上面的程式碼裡先從memory裡read一個值,再比較這個值是否是0,
如果不是的話跳到else地方執行,
如果是的話從r4的地址裡讀出一個值。
如果不幸第一load出現TLB miss或是cache miss,那麼比較和跳轉指令就可能不能繼續了,出現pipeline stall.
幸好我們有out of order和speculation技術,它基本上要利用register renaming來實現。
Register Renaming
有了register renaming可以減少register dependency帶來的hazard, 實現方式是,
程式碼裡的暫存器比如r0, 它只是architecture 暫存器,當指令被fetech, decode後,到了dispatch階段,architecture暫存器會被map到真正的physical registers上,通常physical register個數會比architecture 暫存器多不少。
在Dispatch階段,可以做如下的architecture register到physical register的map,
MapTable |
|||||
r1 |
r2 |
r3 |
原始指令 |
Map後的指令 |
|
p1 |
p2 |
p3 |
add r2,r3,r1 |
add p2,p3,p4 |
|
p4 |
p2 |
p3 |
sub r2,r1,r3 |
sub p2,p4,p5 |
|
p4 |
p2 |
p5 |
mul r2,r3,r3 |
mul p2,p5,p6 |
|
p4 |
p2 |
p6 |
div r1,4,r1 |
div p4,4,p7 |
我們可以通過這樣的辦法對上面的指令做這樣的map,
這樣的話我們就可以speculative執行第二個LDR,因為speculative執行只會改變physical暫存器的值,如果之後條件不滿足,只是去掉這個map,不回寫就好了。
但記住資料確實進入過cache,雖然最終程式沒看到這個資料。
需要注意的是,一般來說只會做speculative的讀(load)操作,而不會做speculative的寫操作。因為讀的結果比較容易放棄,但是寫speculation的話,比較能將speculation做的事情放棄,代價會比較高。
下節將介紹cacheside channel attack.