Stories

Detail Return Return

高性能磁盤 I/O 開發學習筆記 -- 硬件原理篇 - Stories Detail

“硬盤” 是什麼這裏我所説的 “硬盤”,也就是所謂的 “hard disk”,經常簡稱為 “disk” 或者 “HDD”,同時還有另外一個更加高大上的名字 “非易失性存儲”。請各位回憶一下計算機組成原理裏關於存儲的部分,從 CPU 開始,存儲層次如下:寄存器緩存(cache),從高到低又可以分一級、二級、三級緩存,數字越高,距離 CPU 越遠、容量越大、速度越慢主存,也就是內存,就是我們常見説 “內存條”硬盤,包括所有的非易失性存儲,就是本文説明的內容。離線存儲,包括那些 CD 啊之類的
圖片
非易失性存儲就是説該存儲介質上的數據,只要寫入了,那麼就算設備掉電了,也能夠保持,而不會被清空。廣義而言,非易失性存儲包含非常多的種類,包括磁盤、閃存、EEPROM 等等。而對於服務器而言,只涉及兩種,那就是磁盤和 SSD。磁盤使用磁性元件做成盤片,然後使用盤片上對應位置是否有磁性,來判斷該位置存儲的值是邏輯 1 還是邏輯 0。另一種是固態硬盤,也就是 SSD。SSD 的原理其實就是我們的 “U盤”。從存儲介質的角度,“U盤” 其實不是一個準確的説法,準確的説,應該叫做 “閃存”(flash memory)。從製造原理上,閃存又分為兩種,一種是 NOR-flash,另一種是 NAND-Flash。NOR-Flash 不會在服務器上使用,正文略過不講(感興趣的話,可以看本文的小知識)。本文關注的是使用 NAND-Flash 製作而成的硬盤,也就是 SSD。
圖片
硬盤 “塊設備” 是什麼?Linux 中,所有的東西都可以抽象成文件。而所有的設備,則會被分為字符設備和塊設備。字符設備的意思是,對於該設備上隨機指定的地址的值,都可以直接寫入。相對應地,塊設備的意思是,對於該設備上隨機指定的地址上的數據,如果需要修改的話,需要連同該地址周邊一整塊數據都一起讀到內存中、修改了指定數據之後,再整塊寫回。<<<< 小知識:為什麼 SSD 是塊設備?為什麼寫入這麼麻煩?>>>>

SSD 不能簡單隨機地讀寫給定地址上的數據。SSD 讀取以 ”頁“ 為單位(如:256 Bytes),擦除 / 

寫入以 ”塊“ (與內存映射的 “4kB” 的那個 “塊” 不是一回事) 為單位。

此外,SSD 還有一個很大的特點,就是修改對應數據的時候,還不是簡單地執行一個 write 動作

(不是標準 C 的 write())就可以了,而是需要首先執行一次 erase 操作,將當前的整個塊擦除掉
(全部變成邏輯 0),然後再將整個塊的數據重新寫一遍。

所以對於塊設備而言,執行讀操作還算簡單,但是執行寫操作的話,整個流程就變成了:read、erase、

write 了,何其複雜!SSD 之所以被設計成這麼複雜的原因,主要是半導體比特密度和製造成本之間的一個
取捨。原理可以寫一整章,本文就不展開講了。

為什麼磁盤是塊設備?這個很抱歉,筆者沒有準確的答案。或許因為磁盤轉的太快,沒辦法極其精

準地定位具體一個 bit?
圖片
磁盤尋址速度的考量這裏我們先放下 SSD,來講一講磁盤。個人電腦和服務器上所使用的磁盤結構都是一模一樣的。磁盤的結構和各術語如下圖所示:
圖片

圖片
磁盤中會並列地擺放很多片盤片,每個盤片的正反面都會有一個磁頭,使用這個磁頭來讀取盤片上的磁狀態。讀數據的時候,一般會同時從幾個盤片讀,以達到最高的讀取速度。所以我們可以看到,磁盤尋址的速度,主要受到以下兩個因素的影響:磁頭轉動到指定位置的時間盤片旋轉,並將指定點轉動到磁頭下的時間極端情況下,磁頭需要從一端轉到另一端;而盤片則需要轉動 180 度。乍一看,其實這兩個時間是非常短的,似乎並不會影響讀取文件的速度。兩邊引起質變,這兩個時間積累起來的時候,就會出現問題了。那麼怎樣才能累積呢?答案就是:頻繁、隨機地存取文件系統。所以,提高磁盤效率的思路就是:避免頻繁的隨機存取文件
圖片
硬盤文件存取速度的考量這裏所説的包含磁盤和 SSD。前面已經説了,硬盤是塊設備,也就是當讀取內容的時候,只能以塊為單位進行操作。這裏舉一個最簡單的例子吧,我們把塊的單位減少到 8bits,存取的單位以 bits 來計算。比如在一段連續的內存中,數據是這樣的:Addr: 0 1 2 3 4 5 6 7
value: 0 0 1 1 0 1 0 0我們只需要讀取地址 5 上的值,這就是所謂的隨機讀取。但是硬盤的原理決定了我們沒辦法只讀這麼一個數據。驅動只能把這一整塊的數據(00110100)一起讀出來,然後再把這地址 5 的值返回給應用程序。寫入的時候就更復雜了。針對 SSD,如果我們需要把地址 5 上的值改為 0,那麼驅動需要幹下面幾件事情:read: 把 [00110100] 讀到內存中mod: 將地址 5 的值在內存中設置為 0,總共變成 [00110000]erase: 將硬盤中這一整塊的存儲內容擦除掉,擦除之後,這段地址的值會變成 [11111111](其實 SSD 的擦除動作還是比較快的)write: 將實際需要設置的值 [00110000] 寫入到硬盤中。磁盤還算比較簡單,只需要執行 1 和 4 兩個步驟就行了。從上面的流程我們大概可以看到磁盤文件存取速度的優化方向了。如果你還沒弄明白的話,我再舉一個例子:假設還是上面的多個 SSD 塊,我們做一次循環操作來模擬多次存取。偽代碼如下:define SECT_SIZE (5)

int addr = 0;

for (sectNum = 0 to 100)
{

bits[SECT_SIZE] = read_data(from: addr, len: SECT_SIZE);

bits[0] = 0;
bits[SECT_SIZE - 1] = 0;    // 只修改一部分
write_data(data: bits, from: addr, len: SECT_SIZE)

addr += SECT_SIZE

}
這段代碼有什麼問題呢?我們只需要取前兩次循環,看看驅動做了什事情就知道了。首先是第一次循環(Loop 0):此時 addr = 0,

  1. 從 0 號塊讀出 8 bits 的數據,並且取其中的 5bits 返回給應用程序(耗時 t1)
  2. 應用程序修改了兩個 bits
  3. 將 0 號塊的 8bits 寫回(擦除 + 寫入,耗時 t2)第二次循環就不一樣了:此時 addr = 5
  4. 從 0 號塊取出 8bits 的數據,並且取最後 3bits 為有效數據(耗時 t1)
  5. 從 1 號塊取出 8bits 的數據,並且取前 2bits 為有效數據,與前面的 3bits 拼在一起,返回 5bits 給應用程序(耗時 t1)
  6. 應用程序修改了兩個 bits
  7. 將 0 號塊的 8bits 寫回(耗時 t2)
  8. 將 1 號塊的 8bits 寫回(耗時 t2)
    總耗時是第一個循環的兩倍。可見,由於橫跨了兩個塊,雖然應用程序操作的數據量是一樣的,但是操作的時間卻多出了整整一倍。因此從這裏我們可以看出,提高硬盤存取效率的另一個途徑是:儘可能以塊為單位,進行讀寫操作。在操作系統中,軟件層面關心的塊的單位一般是 4096 字節(4kB)。
    圖片
    關於 NOR Flash一句話,NOR Flash 只用在嵌入式設備中,做服務器開發的不需要關心。本小節沒有正片,只有小知識。<<<< 小知識:NOR Flash 是什麼?有什麼特點? >>>>

    NOR Flash 和本文所説的 SDD(NAND Flash)的區別,首先在硬件結構上顯然是不同的。但是嘛,
    如前文所述,硬件的咱不講。

    NOR Flash 的特點有下面幾點:

    1. NOR Flash 也是塊設備,但 NOR Flash 支持絕對的隨機讀取,要讀多長就讀多長,並且讀取
      速度高於 NAND Flash
    2. 相同條件下,NOR Flash 的擦除和寫入耗時遠遠大於 NAND Flash
    3. NOR Flash 支持有限的隨機寫能力,所謂有限,意思是 NOR Flash 可以將任意邏輯 0 的位改
      寫為邏輯 1,但是卻沒辦法將 1 改寫為 0
    4. NOR 執行塊的擦除操作後,整個塊都會變成邏輯 0。因此對於絕大部分數據(0 和 1 混雜的)
      寫入操作,實際上和 NAND Flash 的操作流程是一樣的
    5. 當容量小於 16MB 時,NOR Flash 的成本小於 NAND Flash。再往上就不如 NAND 了。

    上面的特性,使得 NOR Flash 幾乎不會用在 PC 的存儲體系中。但卻在嵌入式設備中(包括計算
    機主板上 BIOS 的存儲器)應用極廣,因為:

    1. 嵌入式設備主要是保存大量的代碼數據(只讀,特點1),和少數並且很少變動的用户數據(讀寫,
      特點1)
    2. 嵌入式設備的程序空間很小,裁減過的 Linux 內核經過壓縮可以達到2MB 甚至更低(特點5)
    3. 嵌入式設備可能隨時斷電,支持 jffs2 文件系統之後,可以保護文件內容(特點3)<<<< 小知識:為什麼支持了 jffs2 就支持斷電保護?>>>>

    可以説 jffs2 簡直就是為 NOR Flash 量身定做的文件系統了。一般而言,比如我們使用 FAT32
    對 U盤進行格式化之後,如果 U盤在寫入數據的時候突然從主機設備上拔掉,那麼文件系統很可能會崩
    潰、損壞。這就是為什麼操作系統會有 “安全移除存儲設備” 的功能。

    Jffs2 完美地利用了 NOR Flash “可以將 0 改寫為 1” 的特性。首先在格式化的時候,jffs2 以
    塊為單位劃分存儲空間,然後在創建索引表的時候。文件系統在將數據寫完之前,會對應的塊設置為 0,
    也就是標記為無效塊。當數據完全寫入完成之後,就將標記從 0 修改為 1。而 jffs2 每次加載的時候,
    會遍歷所有的塊,並且在內存中重建索引表。

    如果在寫入數據的過程中斷電了,那麼相應的塊繼續保持無效狀態。這樣就保證了文件系統的斷電
    保護。
    當然 jffs2 的原理遠遠沒有這麼簡單。Linux 裏面就有 jffs2 的代碼,各位看官可以自行取用。
    圖片
    小結針對硬盤作為塊設備的各種特點,從硬件原理上優化磁盤效率,我們的思路主要就是兩個方向:避免或者儘可能減少存取磁盤上隨機地址的數據,也就是儘量順序地存取文件存取文件時儘可能以塊為單位存取(2 的倍數,最好是 4kB 的倍數)實際代碼中具體應該怎麼操作,在下一篇文中給出。

Add a new Comments

Some HTML is okay.