行到Flush+Reload:緩存?zhèn)刃诺缹?shí)驗(yàn)原理與工程實(shí)踐)
1. 從一條緩存命中說(shuō)起這個(gè)實(shí)驗(yàn)到底在做什么第一次接觸這個(gè)實(shí)驗(yàn)的人大多會(huì)被標(biāo)題里的詞嚇一跳。其實(shí)把外殼剝掉它要講的事情非常樸素現(xiàn)代處理器為了跑得快會(huì)在你還沒(méi)明確要求讀某個(gè)內(nèi)存地址之前就猜你可能要讀然后提前把數(shù)據(jù)搬進(jìn)緩存。這個(gè)猜的行為本身沒(méi)問(wèn)題問(wèn)題在于——猜錯(cuò)了之后處理器雖然會(huì)把結(jié)果丟掉但緩存里的痕跡不會(huì)立刻消失。于是攻擊者可以通過(guò)測(cè)量哪個(gè)地址讀得快反推出剛才處理器偷偷碰過(guò)哪個(gè)地址進(jìn)而讀到本不該讀的數(shù)據(jù)。這個(gè)實(shí)驗(yàn)就是圍繞這條鏈路設(shè)計(jì)的先制造一個(gè)越權(quán)訪問(wèn)的瞬間再通過(guò)緩存?zhèn)刃诺腊涯莻€(gè)瞬間泄露的信息撈出來(lái)。它屬于體系結(jié)構(gòu)安全里最經(jīng)典的一類(lèi)動(dòng)手實(shí)驗(yàn)適合已經(jīng)學(xué)過(guò)操作系統(tǒng)、計(jì)算機(jī)組成原理想真正搞明白CPU 到底怎么泄露秘密的同學(xué)。你不需要什么特殊設(shè)備一臺(tái)普通的 x86 機(jī)器、一個(gè) Linux 環(huán)境、一段 C 代碼就夠了。我把它拆成三層來(lái)理解第一層是原理為什么亂序執(zhí)行會(huì)留下可觀測(cè)的副作用第二層是手法怎么用 FlushReload 把緩存狀態(tài)變成可讀信號(hào)第三層是工程怎么把噪聲壓下去、把成功率提上來(lái)。很多人卡在第二層和第三層之間原理看懂了代碼跑出來(lái)全是噪聲這篇就把這幾層的細(xì)節(jié)都攤開(kāi)講。注意本文討論的所有內(nèi)容僅用于理解處理器微架構(gòu)的公開(kāi)教學(xué)原理請(qǐng)?jiān)谧约旱膶?shí)驗(yàn)環(huán)境中進(jìn)行不要用于任何未授權(quán)的系統(tǒng)。2. 原理拆解亂序執(zhí)行為什么會(huì)說(shuō)漏嘴2.1 現(xiàn)代處理器的先斬后奏機(jī)制要理解這個(gè)實(shí)驗(yàn)得先接受一個(gè)反直覺(jué)的事實(shí)CPU 執(zhí)行指令的順序和你寫(xiě)的代碼順序往往不是一回事。編譯器會(huì)重排處理器自己也會(huì)重排。這種能力叫亂序執(zhí)行Out-of-Order Execution它的目的是填滿流水線不讓昂貴的執(zhí)行單元閑著。打個(gè)比方你去餐廳點(diǎn)餐廚師不會(huì)等你把整張菜單念完才開(kāi)火。他聽(tīng)到一份炒飯就先把鍋熱上等你念到再加個(gè)湯時(shí)炒飯已經(jīng)在鍋里了。如果最后你說(shuō)算了炒飯不要了廚師把炒飯倒掉就行但灶臺(tái)已經(jīng)熱過(guò)、鍋已經(jīng)動(dòng)過(guò)——這個(gè)動(dòng)過(guò)的痕跡就是這個(gè)實(shí)驗(yàn)要利用的東西。處理器也一樣。當(dāng)它遇到一條需要等待的指令比如要讀的內(nèi)存不在緩存里得等幾百個(gè)周期它不會(huì)干等而是往后看幾條指令挑那些不依賴當(dāng)前結(jié)果的先執(zhí)行。這些搶跑的指令如果最后被證明不該執(zhí)行處理器會(huì)回滾它們的架構(gòu)狀態(tài)寄存器、內(nèi)存但微架構(gòu)狀態(tài)緩存、分支預(yù)測(cè)器往往不會(huì)完全回滾。2.2 權(quán)限檢查與數(shù)據(jù)搬運(yùn)的時(shí)間差關(guān)鍵點(diǎn)來(lái)了。當(dāng)一條指令試圖讀取一個(gè)它沒(méi)有權(quán)限訪問(wèn)的地址時(shí)處理器會(huì)做權(quán)限檢查。但這個(gè)檢查發(fā)生在流水線的某個(gè)階段而數(shù)據(jù)從內(nèi)存搬到緩存的動(dòng)作可能已經(jīng)在這個(gè)檢查完成之前就啟動(dòng)了。換句話說(shuō)權(quán)限檢查說(shuō)不行的時(shí)候數(shù)據(jù)可能已經(jīng)在緩存里躺著了。處理器隨后會(huì)拋出一個(gè)異常程序看起來(lái)什么都沒(méi)讀到但緩存里那個(gè)不該存在的副本成了攻擊者的信標(biāo)。這里有個(gè)容易被忽略的細(xì)節(jié)并不是所有處理器、所有版本都受這個(gè)時(shí)間差影響。不同微架構(gòu)對(duì)權(quán)限檢查的位置、對(duì)推測(cè)執(zhí)行的限制都不一樣。所以做這個(gè)實(shí)驗(yàn)時(shí)你會(huì)發(fā)現(xiàn)同一段代碼在不同機(jī)器上表現(xiàn)差異很大這不是你寫(xiě)錯(cuò)了而是硬件行為本身就有差異。2.3 緩存為什么能當(dāng)測(cè)謊儀緩存的核心特性是快。從 L1 緩存讀數(shù)據(jù)大概幾個(gè)周期從主存讀要一兩百個(gè)周期。這個(gè)數(shù)量級(jí)的差距用高精度計(jì)時(shí)器一測(cè)就出來(lái)了。于是攻擊邏輯就變成這樣準(zhǔn)備一塊大緩沖區(qū)先把整塊緩沖區(qū)從緩存里踢出去flush然后觸發(fā)一次越權(quán)訪問(wèn)讓處理器猜著去讀某個(gè)秘密值并用這個(gè)秘密值當(dāng)索引去訪問(wèn)緩沖區(qū)里的某個(gè)位置。如果秘密值是 42那么緩沖區(qū)偏移 42 的那塊數(shù)據(jù)就被帶進(jìn)了緩存。最后攻擊者遍歷整個(gè)緩沖區(qū)測(cè)量每一塊的讀取時(shí)間哪一塊特別快哪一塊就是被訪問(wèn)過(guò)的秘密值就是它的索引。整個(gè)過(guò)程里攻擊者從來(lái)沒(méi)有合法地讀到那個(gè)秘密值他讀到的只是哪塊內(nèi)存變快了。這就是側(cè)信道攻擊的精髓不直接偷內(nèi)容而是偷內(nèi)容的影子。3. 實(shí)驗(yàn)環(huán)境搭建把地基打牢3.1 硬件與系統(tǒng)的選擇這個(gè)實(shí)驗(yàn)對(duì)硬件有實(shí)打?qū)嵉囊蟛皇请S便一臺(tái)虛擬機(jī)就能跑出漂亮結(jié)果的。項(xiàng)目推薦配置說(shuō)明CPU 架構(gòu)x86_64該實(shí)驗(yàn)的經(jīng)典版本針對(duì) x86 設(shè)計(jì)ARM 上行為差異大物理機(jī) vs 虛擬機(jī)優(yōu)先物理機(jī)虛擬機(jī)的緩存行為被宿主干擾噪聲極大操作系統(tǒng)主流 Linux 發(fā)行版需要能加載內(nèi)核模塊、能讀/proc編譯器gcc 或 clang需要能控制優(yōu)化等級(jí)權(quán)限能執(zhí)行特權(quán)操作部分步驟需要加載模塊或調(diào)整配置我踩過(guò)的第一個(gè)坑就是在云主機(jī)上跑結(jié)果全是噪聲。原因是云主機(jī)是多租戶共享物理核你的緩存隨時(shí)被鄰居刷掉。所以能上物理機(jī)就上物理機(jī)實(shí)在不行也要找獨(dú)占核的機(jī)器。3.2 關(guān)閉干擾源在開(kāi)始之前有幾件事必須先做否則你測(cè)出來(lái)的時(shí)間全是抖動(dòng)的關(guān)閉地址空間隨機(jī)化echo 0 /proc/sys/kernel/randomize_va_space。地址每次變你的緩沖區(qū)偏移就對(duì)不上實(shí)驗(yàn)沒(méi)法復(fù)現(xiàn)。固定 CPU 頻率把調(diào)頻策略設(shè)成 performance避免 CPU 在測(cè)量中途降頻導(dǎo)致時(shí)間基準(zhǔn)漂移。綁定 CPU 核心用taskset把進(jìn)程綁到某個(gè)核上減少遷移帶來(lái)的緩存失效。關(guān)閉超線程干擾如果條件允許盡量讓測(cè)量線程獨(dú)占物理核別和別的線程共享。這些操作看起來(lái)瑣碎但每一條都直接影響信噪比。我見(jiàn)過(guò)太多人代碼寫(xiě)得沒(méi)問(wèn)題就是因?yàn)闆](méi)關(guān)隨機(jī)化跑十次錯(cuò)十次。3.3 計(jì)時(shí)工具的選擇測(cè)量緩存命中與否靠的是高精度計(jì)時(shí)。常用的有幾種rdtsc指令讀時(shí)間戳計(jì)數(shù)器開(kāi)銷(xiāo)極低適合細(xì)粒度測(cè)量但要注意它可能被亂序執(zhí)行影響需要配合lfence做序列化。clock_gettime精度到納秒開(kāi)銷(xiāo)比 rdtsc 大但更穩(wěn)定。性能計(jì)數(shù)器通過(guò)perf讀取緩存命中/未命中事件更直接但需要權(quán)限且粒度較粗。在這個(gè)實(shí)驗(yàn)里主流做法是用rdtsc。原因是它足夠快測(cè)量本身的開(kāi)銷(xiāo)不會(huì)淹沒(méi)信號(hào)。但用rdtsc有個(gè)經(jīng)典陷阱它自己可能被亂序執(zhí)行提前讀導(dǎo)致測(cè)出來(lái)的時(shí)間不準(zhǔn)。解決辦法是在rdtsc前后加內(nèi)存屏障強(qiáng)制序列化。static inline uint64_t rdtsc_begin() { uint64_t a, d; asm volatile(mfence\n\t rdtsc\n\t mov %%rax, %0\n\t mov %%rdx, %1\n\t : r(a), r(d) : : %rax, %rdx, memory); return (d 32) | a; } static inline uint64_t rdtsc_end() { uint64_t a, d; asm volatile(rdtscp\n\t mov %%rax, %0\n\t mov %%rdx, %1\n\t mfence\n\t : r(a), r(d) : : %rax, %rdx, memory); return (d 32) | a; }mfence和rdtscp的作用就是保證計(jì)時(shí)指令本身不被重排否則你測(cè)的就不是你想測(cè)的那段代碼。4. 核心手法實(shí)現(xiàn)FlushReload 的完整鏈路4.1 為什么選 FlushReload緩存?zhèn)刃诺烙泻脦追N玩法比如 PrimeProbe、EvictTime、FlushReload。這個(gè)實(shí)驗(yàn)用的是FlushReload原因是它最簡(jiǎn)單、最直接、對(duì)共享內(nèi)存的依賴最小。它的邏輯只有三步Flush用clflush指令把目標(biāo)地址從緩存里踢出去。觸發(fā)執(zhí)行那段會(huì)越權(quán)訪問(wèn)的代碼讓處理器推測(cè)性地把秘密值對(duì)應(yīng)的緩存行拉進(jìn)來(lái)。Reload重新讀一遍目標(biāo)地址測(cè)時(shí)間??斓木褪潜焕M(jìn)來(lái)的慢的就是沒(méi)被碰過(guò)的。對(duì)比 PrimeProbeFlushReload 不需要去爭(zhēng)搶緩存組也不需要復(fù)雜的驅(qū)逐策略代碼量小得多。代價(jià)是它要求攻擊者能對(duì)目標(biāo)地址執(zhí)行clflush在真實(shí)攻擊場(chǎng)景里這個(gè)前提不一定成立但在教學(xué)實(shí)驗(yàn)里完全夠用。4.2 緩沖區(qū)設(shè)計(jì)與索引映射緩沖區(qū)的大小和索引映射方式直接決定實(shí)驗(yàn)?zāi)懿荒艹晒Α:诵募s束是秘密值的每一個(gè)可能取值都要對(duì)應(yīng)緩沖區(qū)里一個(gè)獨(dú)立的、不共享緩存行的位置。假設(shè)秘密值是一個(gè)字節(jié)取值范圍 0 到 255。那么緩沖區(qū)至少要能容納 256 個(gè)槽位每個(gè)槽位占一個(gè)緩存行x86 上通常是 64 字節(jié)。所以緩沖區(qū)大小至少是 256 × 64 16384 字節(jié)。#define CACHE_LINE_SIZE 64 #define SLOT_COUNT 256 #define BUFFER_SIZE (SLOT_COUNT * CACHE_LINE_SIZE) char probe_buffer[BUFFER_SIZE]; // 訪問(wèn)秘密值對(duì)應(yīng)的槽位 void access_slot(uint8_t secret) { volatile char *addr probe_buffer secret * CACHE_LINE_SIZE; (void)*addr; }這里有個(gè)細(xì)節(jié)為什么要乘CACHE_LINE_SIZE而不是直接probe_buffer[secret]因?yàn)槿绻麅蓚€(gè)不同的秘密值落在同一個(gè)緩存行里它們就無(wú)法區(qū)分了。乘上緩存行大小保證每個(gè)秘密值獨(dú)占一行測(cè)量時(shí)才能干凈地分辨。4.3 觸發(fā)越權(quán)訪問(wèn)的代碼結(jié)構(gòu)實(shí)驗(yàn)里通常用一個(gè)受害者函數(shù)來(lái)模擬越權(quán)訪問(wèn)。它的結(jié)構(gòu)大致是這樣char *secret_data; // 攻擊者不該讀的數(shù)據(jù) char *probe_buffer; // 攻擊者的探測(cè)緩沖區(qū) void victim_function(size_t index) { if (index secret_size) { // 邊界檢查 uint8_t value secret_data[index]; // 越權(quán)讀取 access_slot(value); // 用秘密值索引緩沖區(qū) } }正常情況下如果index越界if判斷為假后面的代碼不會(huì)執(zhí)行。但處理器在推測(cè)執(zhí)行時(shí)可能已經(jīng)猜if為真提前把secret_data[index]讀了出來(lái)并用它去訪問(wèn)了probe_buffer。等邊界檢查的結(jié)果真正出來(lái)發(fā)現(xiàn)猜錯(cuò)了架構(gòu)狀態(tài)回滾但probe_buffer里那一行已經(jīng)進(jìn)了緩存。這就是整個(gè)實(shí)驗(yàn)的心臟。你要做的就是反復(fù)觸發(fā)這個(gè)函數(shù)然后測(cè)量probe_buffer哪一行變快了。4.4 測(cè)量與統(tǒng)計(jì)把噪聲壓下去單次測(cè)量幾乎必然被噪聲淹沒(méi)。緩存狀態(tài)、中斷、調(diào)度、頻率波動(dòng)任何一個(gè)都能讓你的信號(hào)消失。所以必須做多次采樣 統(tǒng)計(jì)。常見(jiàn)做法是對(duì)每個(gè)候選索引重復(fù)測(cè)量幾百到幾千次。記錄每次的讀取時(shí)間。用閾值把時(shí)間分成命中和未命中兩類(lèi)。統(tǒng)計(jì)每個(gè)索引命中的次數(shù)取最高的那個(gè)作為猜測(cè)值。#define TRIES 1000 #define THRESHOLD 100 // 周期數(shù)需根據(jù)機(jī)器實(shí)測(cè)調(diào)整 int scores[SLOT_COUNT] {0}; for (int t 0; t TRIES; t) { // 1. flush 整個(gè)緩沖區(qū) for (int i 0; i SLOT_COUNT; i) { _mm_clflush(probe_buffer i * CACHE_LINE_SIZE); } _mm_mfence(); // 2. 觸發(fā)越權(quán)訪問(wèn) victim_function(attack_index); // 3. reload 并計(jì)時(shí) for (int i 0; i SLOT_COUNT; i) { uint64_t start rdtsc_begin(); volatile char c probe_buffer[i * CACHE_LINE_SIZE]; (void)c; uint64_t end rdtsc_end(); if (end - start THRESHOLD) { scores[i]; } } } // 4. 找最高分 int best 0; for (int i 1; i SLOT_COUNT; i) { if (scores[i] scores[best]) best i; }閾值THRESHOLD是最需要調(diào)的參數(shù)。設(shè)太小噪聲全被當(dāng)成命中設(shè)太大真正的命中被漏掉。我的經(jīng)驗(yàn)是先跑一次純 flush reload 的基線看看未命中時(shí)的時(shí)間分布再跑一次故意命中的看看命中時(shí)的時(shí)間分布兩個(gè)分布之間的谷底就是合適的閾值。5. 實(shí)操中的坑與排查手冊(cè)5.1 信號(hào)完全出不來(lái)怎么辦這是最常見(jiàn)的問(wèn)題。代碼看起來(lái)沒(méi)錯(cuò)但scores數(shù)組里全是均勻分布沒(méi)有明顯峰值。按下面的順序排查現(xiàn)象可能原因排查方法所有槽位分?jǐn)?shù)接近推測(cè)執(zhí)行沒(méi)發(fā)生檢查編譯器是否把if優(yōu)化掉了用-O0試試分?jǐn)?shù)隨機(jī)波動(dòng)噪聲太大關(guān)隨機(jī)化、綁核、提高采樣次數(shù)完全沒(méi)有峰值越權(quán)訪問(wèn)沒(méi)觸發(fā)確認(rèn)index確實(shí)越界且secret_data可訪問(wèn)峰值位置漂移緩存行映射問(wèn)題確認(rèn)槽位間隔等于緩存行大小時(shí)快時(shí)慢CPU 降頻固定頻率策略我遇到過(guò)一次特別隱蔽的情況編譯器把victim_function里的if判斷整個(gè)優(yōu)化掉了因?yàn)樗C明了index永遠(yuǎn)在范圍內(nèi)。解決辦法是把secret_size聲明為volatile或者在編譯時(shí)加-O0讓編譯器別自作聰明。5.2 閾值怎么定才靠譜定閾值不要拍腦袋。正確做法是畫(huà)時(shí)間分布直方圖先只做 flush不做越權(quán)訪問(wèn)測(cè)一批 reload 時(shí)間這是未命中分布。再故意讓某個(gè)槽位進(jìn)緩存測(cè)一批 reload 時(shí)間這是命中分布。兩個(gè)分布通常會(huì)在某個(gè)區(qū)間分開(kāi)取中間值當(dāng)閾值。如果兩個(gè)分布重疊嚴(yán)重說(shuō)明你的測(cè)量精度不夠要么提高計(jì)時(shí)精度要么增加采樣次數(shù)要么換臺(tái)機(jī)器。5.3 為什么換個(gè)機(jī)器結(jié)果就變了這個(gè)實(shí)驗(yàn)對(duì)微架構(gòu)極其敏感。不同代際的處理器對(duì)推測(cè)執(zhí)行的限制不同權(quán)限檢查的位置不同緩存替換策略也不同。有的機(jī)器上成功率 90%換一臺(tái)可能只有 10%。這不是代碼的問(wèn)題是硬件的問(wèn)題。所以做這個(gè)實(shí)驗(yàn)時(shí)記錄你的硬件型號(hào)和微架構(gòu)代號(hào)這樣結(jié)果才有可比性。如果你在報(bào)告里寫(xiě)成功率 80%卻不寫(xiě)機(jī)器型號(hào)那這個(gè)數(shù)字沒(méi)有意義。5.4 提高成功率的幾個(gè)實(shí)用技巧增加采樣次數(shù)從 1000 提到 10000峰值會(huì)更明顯代價(jià)是時(shí)間變長(zhǎng)。多次攻擊取眾數(shù)對(duì)同一個(gè)秘密值攻擊多次取出現(xiàn)次數(shù)最多的結(jié)果比單次更穩(wěn)。優(yōu)化 flush 順序先 flush 再觸發(fā)順序不能反否則緩存狀態(tài)不對(duì)。減少測(cè)量循環(huán)里的分支測(cè)量代碼越簡(jiǎn)單越好任何多余操作都會(huì)引入噪聲。用volatile防止優(yōu)化確保每次 reload 都真的去讀內(nèi)存而不是被編譯器緩存到寄存器里。提示如果你發(fā)現(xiàn)峰值時(shí)有時(shí)無(wú)先別改代碼先看看是不是后臺(tái)有進(jìn)程在搶 CPU。用top看一眼往往問(wèn)題就出在這。6. 從實(shí)驗(yàn)到認(rèn)知這件事到底告訴了我們什么6.1 性能與安全的永恒拉扯這個(gè)實(shí)驗(yàn)最讓人震撼的地方不是它有多復(fù)雜而是它揭示了一個(gè)根本矛盾處理器為了快必須猜只要猜就可能猜出不該猜的東西。這不是某個(gè)廠商的失誤而是整個(gè)高性能計(jì)算范式的固有代價(jià)。你不可能既要極致的亂序執(zhí)行又要絕對(duì)的信息隔離。每一代處理器都在這個(gè)天平上找平衡點(diǎn)有的偏向性能有的偏向安全但沒(méi)有人能完全消除這個(gè)矛盾。理解了這一點(diǎn)你就不會(huì)再問(wèn)為什么會(huì)有這種漏洞而會(huì)問(wèn)在什么條件下這個(gè)漏洞可被利用代價(jià)有多大。這才是安全思維的正確打開(kāi)方式。6.2 側(cè)信道思維的遷移價(jià)值拋開(kāi)具體的攻擊手法這個(gè)實(shí)驗(yàn)真正教給你的是一種思維方式當(dāng)直接讀取被禁止時(shí)去讀它的副作用。這個(gè)思路可以遷移到很多地方。比如判斷某個(gè)文件是否存在不一定非要打開(kāi)它看它的訪問(wèn)時(shí)間、大小變化、錯(cuò)誤碼差異都能推斷出信息。再比如判斷某個(gè)分支走了哪條路不一定非要看結(jié)果看執(zhí)行時(shí)間、功耗、緩存占用都能反推。這種從側(cè)面看的能力是安全分析里最值錢(qián)的東西之一。它逼你跳出功能正確的層面去思考實(shí)現(xiàn)過(guò)程本身泄露了什么。6.3 防御思路的啟發(fā)既然攻擊靠的是緩存痕跡那防御自然就從消除痕跡入手。常見(jiàn)的思路有幾類(lèi)隔離讓不同安全域的數(shù)據(jù)不共享緩存從物理上切斷側(cè)信道。填充在關(guān)鍵操作后主動(dòng)刷新緩存讓痕跡來(lái)不及被觀測(cè)。隨機(jī)化讓時(shí)間測(cè)量失去穩(wěn)定的基準(zhǔn)增加攻擊者的噪聲。限制推測(cè)在權(quán)限檢查點(diǎn)插入屏障禁止跨邊界的推測(cè)執(zhí)行。這些防御各有代價(jià)有的損失性能有的增加復(fù)雜度。實(shí)際系統(tǒng)里往往是組合使用根據(jù)威脅模型取舍。理解攻擊原理才能理解防御為什么這么設(shè)計(jì)而不是死記硬背一堆緩解措施的名字。7. 一些個(gè)人體會(huì)這個(gè)實(shí)驗(yàn)我前后做過(guò)好幾輪每輪都有新發(fā)現(xiàn)。第一輪卡在環(huán)境上虛擬機(jī)里怎么跑都是噪聲第二輪卡在閾值上拍腦袋定的值把信號(hào)全濾掉了第三輪才真正看到那個(gè)清晰的峰值那一刻確實(shí)有點(diǎn)激動(dòng)——你親眼看到了 CPU 說(shuō)漏嘴。如果讓我給后來(lái)者一句建議那就是別急著追求高成功率先把單次測(cè)量做干凈。很多人一上來(lái)就想跑出 99% 的準(zhǔn)確率結(jié)果連基線都沒(méi)測(cè)過(guò)根本不知道自己的噪聲水平在哪。先把 flush reload 的基線時(shí)間測(cè)出來(lái)把命中/未命中的分布畫(huà)出來(lái)剩下的就是水到渠成的事。另外這個(gè)實(shí)驗(yàn)的價(jià)值不在于復(fù)現(xiàn)一個(gè)攻擊而在于它逼你去讀處理器的微架構(gòu)手冊(cè)去理解流水線、緩存、推測(cè)執(zhí)行這些平時(shí)被抽象掉的東西。做完之后你看代碼的眼光會(huì)變——你會(huì)開(kāi)始想這行代碼在硬件層面到底發(fā)生了什么有沒(méi)有留下不該留的痕跡。這種視角的轉(zhuǎn)變比實(shí)驗(yàn)本身的結(jié)果重要得多。后續(xù)如果想繼續(xù)深入可以往兩個(gè)方向走一是換不同的緩存?zhèn)刃诺朗址ū热?PrimeProbe體會(huì)它們各自的適用場(chǎng)景二是研究不同微架構(gòu)上的行為差異把同一段代碼在幾臺(tái)不同機(jī)器上跑一遍對(duì)比結(jié)果。這兩個(gè)方向都能讓你對(duì)硬件到底怎么工作有更立體的認(rèn)識(shí)。