計算機組成原理筆記(四)
我的部落格:https://www.luozhiyun.com/
記憶體
記憶體是五大組成部分裡面的儲存器,我們的指令和資料,都需要先載入到記憶體裡面,才會被CPU拿去執行。
我們的記憶體需要被分成固定大小的頁(Page),然後再通過虛擬記憶體地址(Virtual Address)到實體記憶體地址(Physical Address)的地址轉換(Address Translation),才能到達實際存放資料的實體記憶體位置。而我們的程式看到的記憶體地址,都是虛擬記憶體地址。
頁表
想要把虛擬記憶體地址,對映到實體記憶體地址,最直觀的辦法,就是來建一張對映表。虛擬記憶體裡面的頁,到實體記憶體裡面的頁的一一對映。這個對映表,在計算機裡面,就叫作頁表(Page Table)。
頁表這個地址轉換的辦法,會把一個記憶體地址分成頁號(Directory)和偏移量(Offset)兩個部分。
對於一個記憶體地址轉換,其實就是這樣三個步驟:
- 把虛擬記憶體地址,切分成頁號和偏移量的組合;
- 從頁表裡面,查詢出虛擬頁號,對應的物理頁號;
- 直接拿物理頁號,加上前面的偏移量,就得到了實體記憶體地址;
多級頁表(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)。
讀取資料,其實就是兩個步驟。
- 把盤面旋轉到某一個位置。在這個位置上,我們的懸臂可以定位到整個盤面的某一個子區間。
- 把我們的懸臂移動到特定磁軌的特定扇區,也就在這個“幾何扇區”裡面,找到我們實際的扇區。找到之後,我們的磁頭會落下,就可以讀取到正對著扇區的資料。
進行一次硬碟上的隨機訪問,需要的時間由兩個部分組成。
第一個部分,叫作平均延時(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裝置來說呢,它又變成了一個主裝置。
我們下面看一張圖:
- 首先,CPU還是作為一個主裝置,向DMAC裝置發起請求。這個請求,其實就是在DMAC裡面修改配置暫存器。
- CPU修改DMAC的配置的時候,會告訴DMAC這樣幾個資訊:
首先是源地址的初始值以及傳輸時候的地址增減方式。
所謂源地址,就是資料要從哪裡傳輸過來。如果我們要從記憶體裡面寫入資料到硬碟上,那麼就是要讀取的資料在記憶體裡面的地址。
- 其次是目標地址初始值和傳輸時候的地址增減方式。
- 第三個是要傳輸的資料長度
- 設定完這些資訊之後,DMAC就會變成一個空閒的狀態(Idle)。
- 如果我們要從硬碟上往記憶體裡面載入資料,這個時候,硬碟就會向DMAC發起一個數據傳輸請求。這個請求並不是通過匯流排,而是通過一個額外的連線。
- 然後,我們的DMAC需要再通過一個額外的連線響應這個申請。
- 於是,DMAC這個晶片,就向硬碟的介面發起要匯流排讀的傳輸請求。資料就從硬盤裡面,讀到了DMAC的控制器裡面。
- 然後,DMAC再向我們的記憶體發起匯流排寫的資料傳輸請求,把資料寫入到記憶體裡面。
- DMAC會反覆進行上面第6、7步的操作,直到DMAC的暫存器裡面設定的資料長度傳輸完成。
- 資料傳輸完成之後,DMAC重新回到第3步的空閒狀態。