1. 程式人生 > >計算機組成原理筆記(四)

計算機組成原理筆記(四)

我的部落格:https://www.luozhiyun.com/

記憶體

記憶體是五大組成部分裡面的儲存器,我們的指令和資料,都需要先載入到記憶體裡面,才會被CPU拿去執行。

我們的記憶體需要被分成固定大小的頁(Page),然後再通過虛擬記憶體地址(Virtual Address)到實體記憶體地址(Physical Address)的地址轉換(Address Translation),才能到達實際存放資料的實體記憶體位置。而我們的程式看到的記憶體地址,都是虛擬記憶體地址。

頁表

想要把虛擬記憶體地址,對映到實體記憶體地址,最直觀的辦法,就是來建一張對映表。虛擬記憶體裡面的頁,到實體記憶體裡面的頁的一一對映。這個對映表,在計算機裡面,就叫作頁表(Page Table)。

頁表這個地址轉換的辦法,會把一個記憶體地址分成頁號(Directory)和偏移量(Offset)兩個部分。

對於一個記憶體地址轉換,其實就是這樣三個步驟:

  1. 把虛擬記憶體地址,切分成頁號和偏移量的組合;
  2. 從頁表裡面,查詢出虛擬頁號,對應的物理頁號;
  3. 直接拿物理頁號,加上前面的偏移量,就得到了實體記憶體地址;

多級頁表(Multi-Level Page Table)

大部分程序所佔用的記憶體是有限的,需要的頁也自然是很有限的。我們只需要去存那些用到的頁之間的對映關係就好了。

在整個程序的記憶體地址空間,通常是“兩頭實、中間空”。在程式執行的時候,記憶體地址從頂部往下,不斷分配佔用的棧的空間。而堆的空間,記憶體地址則是從底部往上,是不斷分配佔用的。

所以,在一個實際的程式程序裡面,虛擬記憶體佔用的地址空間,通常是兩段連續的空間。

我們以一個4級的多級頁表為例,來看一下。

對應的,一個程序會有一個4級頁表。我們先通過4級頁表索引,找到4級頁表裡面對應的條目(Entry)。這個條目裡存放的是一張3級頁表所在的位置。4級頁面裡面的每一個條目,都對應著一張3級頁表,所以我們可能有多張3級頁表。

找到對應這張3級頁表之後,我們用3級索引去找到對應的3級索引的條目。3級索引的條目再會指向一個2級頁表。同樣的,2級頁表裡我們可以用2級索引指向一個1級頁表。

而最後一層的1級頁表裡面的條目,對應的資料內容就是物理頁號了。在拿到了物理頁號之後,我們同樣可以用“頁號+偏移量”的方式,來獲取最終的實體記憶體地址。

TLB加速地址轉換

程式裡面的每一個程序,都有一個屬於自己的虛擬記憶體地址空間。我們可以通過地址轉換來獲得最終的實際實體地址。我們每一個指令都存放在記憶體裡面,每一條資料都存放在記憶體裡面。

“地址轉換”是一個非常高頻的動作,“地址轉換”的效能就變得至關重要了。

多級頁表讓原本只需要訪問一次記憶體的操作,變成了需要訪問4次記憶體,才能找到物理頁號。

程式所需要使用的指令,都順序存放在虛擬記憶體裡面。我們執行的指令,也是一條條順序執行下去的。

於是,計算機工程師們專門在CPU裡放了一塊快取晶片。這塊快取晶片我們稱之為TLB,全稱是地址變換高速緩衝(Translation-Lookaside Buffer)。這塊快取存放了之前已經進行過地址轉換的查詢結果。這樣,當同樣的虛擬地址需要進行地址轉換的時候,我們可以直接在TLB裡面查詢結果,而不需要多次訪問記憶體來完成一次轉換。

TLB和我們前面講的CPU的快取記憶體類似,可以分成指令的TLB和資料的TLB,也就是ITLB和DTLB。

為了效能,我們整個記憶體轉換過程也要由硬體來執行。在CPU晶片裡面,我們封裝了記憶體管理單元(MMU,Memory Management Unit)晶片,用來完成地址轉換。和TLB的訪問和互動,都是由這個MMU控制的。

I/O

我們先來看一個固態硬碟的Benchmark圖:

“4K”的指標就是我們的程式,去隨機讀取磁碟上某一個4KB大小的資料,一秒之內可以讀取到多少資料。

我們拿這個40MB/s和一次讀取4KB的資料算一下。 40MB / 4KB = 10,000 也就是說,一秒之內,這塊SSD硬碟可以隨機讀取1萬次的4KB的資料。如果是寫入的話呢,會更多一些,90MB /4KB 差不多是2萬多次。

這個每秒讀寫的次數,我們稱之為IOPS,也就是每秒輸入輸出操作的次數。
DTR(Data Transfer Rate,資料傳輸率)

我們在實際的應用開發當中,對於資料的訪問,更多的是隨機讀寫,而不是順序讀寫。

診斷 I/O瓶頸

首先看一下CPU有沒有在等待io操作。

# top

top - 06:26:30 up 4 days, 53 min,  1 user,  load average: 0.79, 0.69, 0.65
Tasks: 204 total,   1 running, 203 sleeping,   0 stopped,   0 zombie
%Cpu(s): 20.0 us,  1.7 sy,  0.0 ni, 77.7 id,  0.0 wa,  0.0 hi,  0.7 si,  0.0 st
KiB Mem:   7679792 total,  6646248 used,  1033544 free,   251688 buffers
KiB Swap:        0 total,        0 used,        0 free.  4115536 cached Mem

wa的指標,這個指標就代表著iowait,也就是CPU等待IO完成操作花費的時間佔CPU的百分比。

如果iowait很大,那麼就可以去看看實際的I/O操作情況是什麼樣的。使用iostat,就能夠看到實際的硬碟讀寫情況。

$ iostat

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          17.02    0.01    2.18    0.04    0.00   80.76
Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sda               1.81         2.02        30.87     706768   10777408

tps指標,其實就對應著我們上面所說的硬碟的IOPS效能。而kB_read/s和kB_wrtn/s指標,就對應著我們的資料傳輸率的指標。

使用iotop找出到底是哪一個程序是這些I/O讀寫的來源。

$ iotop

Total DISK READ :       0.00 B/s | Total DISK WRITE :      15.75 K/s
Actual DISK READ:       0.00 B/s | Actual DISK WRITE:      35.44 K/s
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND                                             
  104 be/3 root        0.00 B/s    7.88 K/s  0.00 %  0.18 % [jbd2/sda1-8]
  383 be/4 root        0.00 B/s    3.94 K/s  0.00 %  0.00 % rsyslogd -n [rs:main Q:Reg]
 1514 be/4 www-data    0.00 B/s    3.94 K/s  0.00 %  0.00 % nginx: worker process

硬碟

機械硬碟

一塊機械硬碟是由盤面、磁頭和懸臂三個部件組成的。

首先,自然是盤面(Disk Platter)。盤面其實就是我們實際儲存資料的碟片。
我們的硬碟有5400轉的、7200轉的,乃至10000轉的。這個多少多少轉,指的就是盤面中間電機控制的轉軸的旋轉速度,英文單位叫RPM,也就是每分鐘的旋轉圈數(Rotations Per Minute)。

磁頭:資料並不能直接從盤面傳輸到總線上,而是通過磁頭,從盤面上讀取到,然後再通過電路訊號傳輸給控制電路、介面,再到總線上的。通常,我們的一個盤面上會有兩個磁頭,分別在盤面的正反面。

懸臂連結在磁頭上,並且在一定範圍內會去把磁頭定位到盤面的某個特定的磁軌(Track)上。

一個盤面通常是圓形的,由很多個同心圓組成,每一個同心圓都是一個磁軌。每個磁軌都有自己的一個編號。

一個磁軌,會分成一個一個扇區(Sector)。上下平行的一個一個盤面的相同扇區呢,我們叫作一個柱面(Cylinder)。

讀取資料,其實就是兩個步驟。

  1. 把盤面旋轉到某一個位置。在這個位置上,我們的懸臂可以定位到整個盤面的某一個子區間。
  2. 把我們的懸臂移動到特定磁軌的特定扇區,也就在這個“幾何扇區”裡面,找到我們實際的扇區。找到之後,我們的磁頭會落下,就可以讀取到正對著扇區的資料。

進行一次硬碟上的隨機訪問,需要的時間由兩個部分組成。

第一個部分,叫作平均延時(Average Latency)。這個時間,其實就是把我們的盤面旋轉,把幾何扇區對準懸臂位置的時間。這個時間很容易計算,它其實就和我們機械硬碟的轉速相關。

隨機情況下,平均找到一個幾何扇區,我們需要旋轉半圈盤面。上面7200轉的硬碟,那麼一秒裡面,就可以旋轉240個半圈。那麼,這個平均延時就是:1s / 240 = 4.17ms

第二個部分,叫作平均尋道時間(Average Seek Time),也就是在盤面選轉之後,我們的懸臂定位到扇區的的時間。我們現在用的HDD硬碟的平均尋道時間一般在4-10ms。

SSD硬碟

現在新的大容量SSD硬碟是由很多個裸片(Die)疊在一起的,就好像我們的機械硬碟把很多個盤面(Platter)疊放再一起一樣,這樣可以在同樣的空間下放下更多的容量。

一張裸片上可以放多個平面(Plane),一般一個平面上的儲存容量大概在GB級別。一個平面上面,會劃分成很多個塊(Block),一般一個塊(Block)的儲存大小, 通常幾百KB到幾MB大小。一個塊裡面,還會區分很多個頁(Page),就和我們記憶體裡面的頁一樣,一個頁的大小通常是4KB。

對於SSD硬碟來說,資料的寫入叫作Program。寫入不能像機械硬碟一樣,通過覆寫(Overwrite)來進行的,而是要先去擦除(Erase),然後再寫入。

SSD的讀取和寫入的基本單位,不是一個位元(bit)或者一個位元組(byte),而是一個頁(Page)。SSD的擦除單位必須按照塊來擦除。

SSD的使用壽命,其實是每一個塊(Block)的擦除的次數。

SLC的晶片,可以擦除的次數大概在10萬次,MLC就在1萬次左右,而TLC和QLC就只在幾千次了。

SSD讀寫的生命週期

白色代表這個頁從來沒有寫入過資料,綠色代表裡面寫入的是有效的資料,紅色代表裡面的資料,在我們的作業系統看來已經是刪除的了。

一開始,所有塊的每一個頁都是白色的。隨著我們開始往裡面寫資料,裡面的有些頁就變成了綠色。

然後,因為我們刪除了硬碟上的一些檔案,所以有些頁變成了紅色。但是這些紅色的頁,並不能再次寫入資料。因為SSD硬碟不能單獨擦除一個頁,必須一次性擦除整個塊,所以新的資料,我們只能往後面的白色的頁裡面寫。這些散落在各個綠色空間裡面的紅色空洞,就好像硬碟碎片。

如果有哪一個塊的資料一次性全部被標紅了,那我們就可以把整個塊進行擦除。它就又會變成白色,可以重新一頁一頁往裡面寫資料。

在快要沒有白色的空頁去寫入資料的時候,SSD會做一次類似於Windows裡面“磁碟碎片整理”或者Java裡面的“記憶體垃圾回收”工作。找一個紅色空洞最多的塊,把裡面的綠色資料,挪到另一個塊裡面去,然後把整個塊擦除,變成白色,可以重新寫入資料。

DMA

為什麼要發明DMA技術?

就目前而言I/O速度如何提升,比起CPU,總還是太慢。如果我們對於I/O的操作,都是由CPU發出對應的指令,然後等待I/O裝置完成操作之後返回,那CPU有大量的時間其實都是在等待I/O裝置完成操作。

但是,這個CPU的等待,在很多時候,其實並沒有太多的實際意義。我們對於I/O裝置的大量操作,其實都只是把記憶體裡面的資料,傳輸到I/O裝置而已。

因此,計算機工程師們,就發明了DMA技術,也就是直接記憶體訪問(Direct Memory Access)技術,來減少CPU等待的時間。

DMA有什麼用?

本質上,DMA技術就是我們在主機板上放一塊獨立的晶片。在進行記憶體和I/O裝置的資料傳輸的時候,我們不再通過CPU來控制資料傳輸,而直接通過DMA控制器(DMA Controller,簡稱DMAC)。

當傳輸大量資料的時候,DMAC可以等資料到齊了,再發送訊號,給到CPU去處理,而不是讓CPU在那裡忙等待。

DMAC是怎麼控制資料傳輸的?

DMAC其實也是一個特殊的I/O裝置,它和CPU以及其他I/O裝置一樣,通過連線到匯流排來進行實際的資料傳輸。總線上的裝置呢,其實有兩種型別。一種我們稱之為主裝置(Master),另外一種,我們稱之為從裝置(Slave)。

想要主動發起資料傳輸,必須要是一個主裝置才可以,CPU就是主裝置。而我們從裝置(比如硬碟)只能接受資料傳輸。

DMAC它既是一個主裝置,又是一個從裝置。對於CPU來說,它是一個從裝置;對於硬碟這樣的IO裝置來說呢,它又變成了一個主裝置。

我們下面看一張圖:

  1. 首先,CPU還是作為一個主裝置,向DMAC裝置發起請求。這個請求,其實就是在DMAC裡面修改配置暫存器。
  2. CPU修改DMAC的配置的時候,會告訴DMAC這樣幾個資訊:
    • 首先是源地址的初始值以及傳輸時候的地址增減方式。

      所謂源地址,就是資料要從哪裡傳輸過來。如果我們要從記憶體裡面寫入資料到硬碟上,那麼就是要讀取的資料在記憶體裡面的地址。

    • 其次是目標地址初始值和傳輸時候的地址增減方式。
    • 第三個是要傳輸的資料長度
  3. 設定完這些資訊之後,DMAC就會變成一個空閒的狀態(Idle)。
  4. 如果我們要從硬碟上往記憶體裡面載入資料,這個時候,硬碟就會向DMAC發起一個數據傳輸請求。這個請求並不是通過匯流排,而是通過一個額外的連線。
  5. 然後,我們的DMAC需要再通過一個額外的連線響應這個申請。
  6. 於是,DMAC這個晶片,就向硬碟的介面發起要匯流排讀的傳輸請求。資料就從硬盤裡面,讀到了DMAC的控制器裡面。
  7. 然後,DMAC再向我們的記憶體發起匯流排寫的資料傳輸請求,把資料寫入到記憶體裡面。
  8. DMAC會反覆進行上面第6、7步的操作,直到DMAC的暫存器裡面設定的資料長度傳輸完成。
  9. 資料傳輸完成之後,DMAC重新回到第3步的空閒狀態。