Cache與記憶體:程式放在哪兒?
1)什麼是區域性性原理?
-
可以看到,這個程式碼大數時間在執行一個乘法計算和呼叫一個 printf 函式,而程式一旦編譯裝載進記憶體中,它的地址就確定了。也就是說,CPU 大多數時間在訪問相同或者與此相鄰的地址,換句話說就是:CPU 大多數時間在執行相同的指令或者與此相鄰的指令。這就是大名鼎鼎的程式區域性性原理。
要想弄懂區域性性原理,首先得先搞懂記憶體!!!
2)記憶體條的解讀?
-
在 PCB 板上有記憶體顆粒晶片,主要是用來存放資料的。SPD 晶片用於存放記憶體自身的容量、頻率、廠商等資訊。還有最顯眼的金手指,用於連線資料匯流排和地址匯流排,電源等。
3)專業角度來講,記憶體應該叫什麼?
-
DRAM,即動態隨機儲存器
4)記憶體儲存顆粒晶片中的儲存單元是由什麼組成的?
-
電容和相關元件。
-
電容儲存電荷的多、少代表數字訊號 0 和 1
5)隨著時間的流逝,電容會出現什麼問題?
-
漏電,導致電荷不足,就會讓儲存單元的資料出錯。
6)為了處理漏電現象帶來的資料差錯問題,DRAM做了哪些努力?
-
週期性重新整理,以保持電荷狀態。
7)記憶體的速度還有邏輯上記憶體和系統的連線方式和結構是怎樣的?
-
控制記憶體重新整理和記憶體讀寫的是記憶體控制器,而記憶體控制器整合在北橋晶片中
-
傳統方式下,北橋晶片存在於系統主機板上。現在由於技術牛逼了北橋晶片被就放到 CPU 晶片中了,這大大提升了 CPU 訪問記憶體的效能。
8)作為軟體開發人員我們從邏輯上應該怎樣看待記憶體?
-
把記憶體看成一個巨大的位元組陣列就可以,而記憶體地址就是這個陣列的下標。
9)CPU 要資料,記憶體一時給不了怎麼辦?
-
CPU 會讓匯流排插入等待時鐘週期,直到記憶體準備好。根據木桶原理,CPU 的效能多高都沒用,記憶體才是決定系統整體效能的關鍵。
10)有了上面的理論知識,讓我們來看看什麼是Cache?
-
CPU 大多數時間在訪問相同或者與此相鄰的地址。那麼,我們立馬就可以想到用一塊小而快的儲存器,放在 CPU 和記憶體之間,就可以利用程式的區域性性原理來緩解 CPU 和記憶體之間的效能瓶頸。這塊小而快的儲存器就是 Cache,即快取記憶體。
11)Cache 中有哪些內容?
-
存放了記憶體中的一部分資料。
12)有了cache之後,CPU的工作流程是怎樣的?
-
在訪問記憶體時要先訪問 Cache,若 Cache 中有需要的資料就直接從 Cache 中取出,若沒有則需要從記憶體中讀取資料,並同時把這塊資料放入 Cache 中。由於程式的區域性性原理,在一段時間內,CPU 總是能從 Cache 中讀取到自己想要的資料。
13)cache的物理位置在哪裡?
-
可以整合在 CPU 內部,也可以做成獨立的晶片放在總線上。
-
現在 x86 CPU 和 ARM CPU 都是整合在 CPU 內部的。
14)Cache主要由哪幾個部分組成?
-
高速的靜態儲存器
-
地址轉換模組
-
Cache 行替換模組
15)Cache 的邏輯工作流程是怎樣的?
-
地址轉換模組把CPU 發出的地址 切成 3 段:組號,行號,行內偏移。
-
根據組號、行號查詢高速靜態儲存器中對應的行。找到的話用行內偏移讀取並返回資料給 CPU,否則就分配一個新行並訪問記憶體,把記憶體中對應的資料載入到 這個新行並返回給 CPU。
-
如果沒有新行了,替換演算法來覆蓋不常用的行。
-
以上這些邏輯都由 Cache 硬體獨立實現,軟體不用做任何工作,對軟體是透明的。
16)凡事都有兩面性,Cache 帶來了什麼問題?
-
資料一致性問題。
16.1)先來看看Cache 在硬體層面的結構是怎樣的?
-
這是一顆最簡單的雙核心 CPU,它有三級 Cache,第一級 Cache 是指令和資料分開的(指令是由操作碼和運算元組成的,所以指令中也有資料),第二級 Cache 是獨立於 CPU 核心的,第三級 Cache 是所有 CPU 核心共享的。
16.2)Cache 的一致性問題,主要包括哪三個方面?
-
一個 CPU 核心中的指令 Cache 和資料 Cache 的一致性問題。
-
多個 CPU 核心各自的 2 級 Cache 的一致性問題。
-
CPU 的 3 級 Cache 與裝置記憶體,如 DMA、網絡卡幀儲存,視訊記憶體之間的一致性問題。這裡我們不需要關注這個問題。
16.3)為什麼CPU 核心中的指令 Cache 和資料 Cache 會有一致性問題?
-
程式執行的時候,指令經過指令cache,指令中涉及到的資料則會經過資料 Cache。
-
所以,對自修改的程式碼(即修改執行中程式碼指令資料,變成新的程式)而言,比如我們修改了記憶體地址 A 這個位置的程式碼(典型的情況是 Java 執行時編譯器),這個時候我們是通過儲存的方式去寫的地址 A,所以新的指令會進入資料 Cache。(通過寫的方式,所以是一堆資料)
-
但是我們接下來去執行地址 A 處的指令的時候,指令 Cache 裡面可能命中的是修改之前的指令。
16.4)怎樣解決上面的問題?
-
需要把資料 Cache 中的資料寫入到記憶體中,然後讓指令 Cache 無效,重新載入記憶體中的資料。
16.5)多個 CPU 核心各自的 2 級 Cache 為什麼會有資料一致性問題?
-
假如第一個 CPU 核心讀取了一個 A 地址處的變數,第二個 CPU 也讀取 A 地址處的變數。根據硬體設計,直接把第一個 CPU 核心的 A 地址處 Cache 內容直接複製到第二個 CPU 的第 2、1 級 Cache,這樣兩個 CPU 核心都得到了 A 地址的資料。
-
如果這時第一個 CPU 核心改寫了 A 地址處的資料,而第二個 CPU 核心的 2 級 Cache 裡面還是原來的值,資料顯然就不一致了。
16.6)怎樣解決2級快取資料不一致問題?
-
資料同步協議。
16.6.1)典型的多核心 Cache 資料同步協議有哪些?
-
MESI 和 MOESI。
16.7)MESI 協議是怎樣的?
-
定義了 4 種基本狀態:M、E、S、I,即
-
修改(Modified)
-
一個快取修改了值
-
-
獨佔(Exclusive)
-
只有一個有
-
-
共享(Shared)
-
兩個都有
-
-
無效(Invalid)
-
前面三幅圖 Cache 中沒有資料的那些,都屬於這個情況。
-
16.8)Cache 硬體是怎樣設計的?
-
它會監控所有 CPU 上 Cache 的操作,根據相應的操作使得 Cache 裡的資料行在上面這些狀態之間切換。Cache 硬體通過這些狀態的變化,就能安全地控制各 Cache 間、各 Cache 與記憶體之間的資料一致性了。
x86 CPU 上預設是關閉 Cache 的!!!
17)前面講了這麼多細節,那如何開啟 Cache呢?
-
CD=1 時表示 Cache 關閉,NW=1 時 CPU 不維護記憶體資料一致性。
-
所以我們只需要將 CR0 暫存器中 CD、NW 位同時清 0 即可。
mov eax, cr0
;開啟 CACHE
btr eax,29 ;CR0.NW=0
btr eax,30 ;CR0.CD=0
mov cr0, eax
18)如何獲取記憶體檢視?
-
BIOS 提供的真實模式下中斷服務,就是 int 指令後面跟著一個常數的形式。
-
由於 PC 機上電後由 BIOS 執行硬體初始化,中斷向量表是 BIOS 設定的,所以執行中斷自然執行 BIOS 服務。這個中斷服務是 int 15h,但是它需要一些引數,就是在執行 int 15h 之前,對特定暫存器設定一些值,程式碼如下。
_getmemmap:
xor ebx,ebx ;ebx設為0
mov edi,E80MAP_ADR ;edi設為存放輸出結果的1MB內的實體記憶體地址
loop:
mov eax,0e820h ;eax必須為0e820h
mov ecx,20 ;輸出結果資料項的大小為20位元組:8位元組記憶體基地址,8位元組記憶體長度,4位元組記憶體型別
mov edx,0534d4150h ;edx必須為0534d4150h
int 15h ;執行中斷
jc error ;如果flags暫存器的C位置1,則表示出錯
add edi,20;更新下一次輸出結果的地址
cmp ebx,0 ;如ebx為0,則表示迴圈迭代結束
jne loop ;還有結果項,繼續迭代
ret
error:;出錯處理
-
上面的程式碼是在迭代中執行中斷,每次中斷都輸出一個 20 位元組大小資料項,最後會形成一個該資料項(結構體)的陣列,可以用 C 語言結構表示,如下。
#define RAM_USABLE 1 //可用記憶體
#define RAM_RESERV 2 //保留記憶體不可使用
#define RAM_ACPIREC 3 //ACPI表相關的
#define RAM_ACPINVS 4 //ACPI NVS空間
#define RAM_AREACON 5 //包含壞記憶體
typedef struct s_e820{
u64_t saddr; /* 記憶體開始地址 */
u64_t lsize; /* 記憶體大小 */
u32_t type; /* 記憶體型別 */
}e820map_t;