1. 程式人生 > 其它 >Cache與記憶體:程式放在哪兒?

Cache與記憶體:程式放在哪兒?

1)什麼是區域性性原理?

 
 #include <stdio.h>
 int main(){
     int i,j;
     for(i=1;i<=9;i++){        
         for(j=1;j<=i;j++){
             printf("%d*%d=%2d ",i,j,i*j);
        }
         printf("\n");
    }
     return 0;
 }
  • 可以看到,這個程式碼大數時間在執行一個乘法計算和呼叫一個 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;