JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

固態硬碟:一個截然不同的世界

我們花了三篇導覽談的那張硬碟,在固態硬碟裡完全沒有任何會動的零件——沒有碟片要轉,沒有讀寫臂要尋軌。光是這一件事,就溶解了舊規則,並寫下一整套全新的規則:快閃記憶體必須先以大塊為單位擦除才能重寫、一個藏在凌亂晶片之上、假裝成一張整齊磁碟的隱形翻譯官,以及電梯演算法的悄然退場。

沒有會動的零件,就改變了一切

有三篇導覽,我們都住在一張硬碟裡:一疊每分鐘轉上數千圈的碟片、一隻來回急射的讀寫臂,以及一次得付三段獨立等待的讀取——尋軌移動讀寫臂、旋轉等正確的磁區轉過來,然後才是傳輸。固態硬碟,也就是 SSD,把那一整套機械全丟進了垃圾桶。沒有碟片、沒有讀寫臂、沒有旋轉。資料活在快閃記憶體(flash memory)裡——那是一格格微小到看不見的記憶單元組成的網格,每一格靠困住幾顆電子來記住一個 1 或一個 0,整個裝置裡沒有任何一個會動的零件。

立即的回報大得驚人。沒有讀寫臂要移動、沒有碟片要等,第 2 篇裡那兩大成本——尋軌與旋轉——就這麼消失了。硬碟在單次隨機讀取前得花好幾毫秒把自己就定位,SSD 卻在數十微秒內就回答:大約快上一百倍,而對最折磨硬碟的那種四散隨機讀取,差距還更大。更妙的是,SSD 沒有「近」或「遠」的概念:每一個位置都同樣地快就能抵達,因為根本沒有實體距離要跑。最後這個性質,是埋在我們所學一切磁碟排程之下的一顆無聲炸彈,我們稍後會回來找它。

陷阱:你不能直接覆寫快閃記憶體

這裡有一條藏在快閃記憶體核心的怪規則,其他一切都是為了繞開它而生的。快閃單元被組織成兩種大小不同的單位。你能讀或寫的單位叫頁(page)——令人混淆地,這跟分頁那幾篇講的記憶體分頁是同一個詞,但兩者毫不相干;這裡它只是指一小塊,常常在大約 4 KB 到 16 KB 之間。你能擦除的單位則是一個大得多的擦除區塊(erase block),由許多頁綁成一束,常常是數百 KB 到數 MB。而規則很殘酷:你可以寫一個全新的、空白的頁,卻永遠不能覆寫一個已經裝著資料的頁。要重用它,你必須先擦除——而擦除一次只能對整個大區塊動手,把裡頭每一頁都一併抹掉。

想像一本筆記本的一頁,是用永久墨水寫的。你沒辦法只改其中一行——要改這頁上的任何東西,你都得把這頁所屬的整個章節撕掉、重新印一次。所以當一個程式只想改檔案中間的幾個位元組時,磁碟機沒辦法像硬碟那樣,安安靜靜地就地覆寫它們。它反而會把改過的資料寫到別處一個全新的、已經擦除好的頁上,再把舊頁標記成等著之後清理的陳舊垃圾。這支小小的舞——寫到別處、讓舊副本退役——被強加在每一次修改上,而它正是讓 SSD 變得有趣的幾乎每一個怪癖的源頭。

這裡還藏著第二個、更嚴酷的事實:快閃單元會磨損。每一次擦除都讓單元受損一點點,而在某個次數的擦除週期之後——依快閃類型不同,可能從幾百次到幾十萬次——一個單元就再也無法可靠地困住它的電子,於是死去。硬碟碟片不會因為被讀或被寫而磨損;快閃記憶體卻真的會。所以磁碟機不只要應付「寫前先擦」這條規則,還必須把損傷攤開,免得某一個區塊被擦到死、其他區塊卻閒著沒事。這兩件工作,都落在我們接下來要認識的一項巧思身上。

快閃轉譯層:在凌亂晶片上偽造出的一張整齊磁碟

當底下永遠無法就地覆寫一個頁時,SSD 是怎麼遵守第 1 篇那個簡單的承諾——「把編號 5000 的區塊給我,我會讀它或寫它」——的呢?它跑著一套屬於自己的、小小的、祕密的作業系統。快閃轉譯層,也就是 FTL,是磁碟機內部的韌體,它維護著一張地圖:對作業系統問到的每一個邏輯區塊號碼,它都記下目前是哪一個實體快閃頁裝著那份資料。當你「覆寫」第 5000 號區塊時,FTL 會把新資料寫到一個空白的頁上,接著只要更新地圖、讓第 5000 號區塊指向那個新頁,再把舊頁標記成陳舊。作業系統從頭到尾什麼都沒看見——它以為自己覆寫了一個區塊,就跟它在磁碟上會做的一模一樣。

OS asks:  write logical block 5000  (new contents)

      logical block  ->  physical flash page
  before:   5000     ->   page A   (now stale)
  after:    5000     ->   page G   (fresh, just written)

  page A is left as GARBAGE; reclaimed later, in bulk,
  by erasing the whole block it sits in.
FTL 的間接映射,把一次不可能的就地覆寫,變成了「寫到別處再重新對應」。這跟分頁表替記憶體玩的把戲一模一樣:一層間接,讓實體位置可以移動,而邏輯上的名字維持不變。

那一層間接,正是磁碟機解決它那兩個難題的地方。因為 FTL 能選擇每次寫入要落在哪個實體頁上,它就能施展磨損平均(wear leveling)——見磨損平均——刻意把寫入導向擦除次數最少的區塊,讓磨損均勻攤在整顆晶片上,而不是死命捶打同一個點。少了它,裝著繁忙日誌檔或檔案系統中介資料的那些區塊,會在幾個月內把自己擦到死,而好幾 GB 的冷區塊卻嶄新地閒置著沒人用。有了它,整張磁碟機一起、優雅地老去,活得遠比任何單一單元獨自能撐的都要久。

垃圾回收、寫入放大,與 TRIM

那一堆陳舊的頁不會自己清乾淨。磁碟機終究會把空白、已擦除的頁用得快見底,於是必須回收空間——這個過程叫垃圾回收(garbage collection)。但別忘了擦除規則:你只能擦除一整個區塊,而一個區塊幾乎總是裝著陳舊頁與仍在使用中的頁混在一起。所以在擦除之前,磁碟機必須先把那個區塊裡每一個仍然有效的頁,全都複製到另一個空白區塊去,然後才擦掉舊的。於是每一次使用者的寫入,都悄悄引發了額外的內部複製,而「使用者要求寫入的位元組數」與「快閃實際不得不寫入的位元組數」之間的這道落差,就叫寫入放大(write amplification)。它是「寫前先擦」這條規則的隱形稅金。

  1. 磁碟機的空白頁不夠了,於是垃圾回收挑出一個當受害者的擦除區塊——比方說裡頭有 60 個有效頁、4 個陳舊頁。
  2. 它把那 60 個有效頁全部複製到別處一個空白區塊去,並為每一個更新 FTL 地圖,讓它指向新的家。
  3. 現在那個受害區塊只剩死掉的資料,於是磁碟機一口氣擦除整個區塊,把 64 個嶄新的空白頁歸還給池子。
  4. 淨結果:為了騰出 4 個陳舊頁,磁碟機額外實體寫了 60 個它從沒被要求寫的頁。那個比例就是寫入放大。

現在,作業系統與磁碟機之間冒出了一道微妙的縫隙。當你刪除一個檔案時,檔案系統只是在自己的帳本裡把那些邏輯區塊標記成空閒——它並不會去動那份資料,就跟你在磁碟那邊學到的完全一樣。但 FTL 沒辦法讀檔案系統的心:它仍然以為那些陳舊頁裝著珍貴的、有效的資料,於是在垃圾回收時永遠盡責地把它們搬來搬去。磁碟機正在賣力地保存沒人想要的資料。解方是 TRIM 指令——見 TRIM——這是作業系統送出的一則訊息,告訴磁碟機:「這些邏輯區塊現在空閒了;它們的內容是垃圾,你可以別再保存它們了。」有了 TRIM,FTL 就能把那些頁丟掉、而不是複製它們,這降低了寫入放大,也讓磁碟機在被填滿時依然保持快速。

電梯到此為止

現在我們可以引爆第一節埋下的那顆無聲炸彈了。上一篇導覽通篇都在講磁碟排程:重新排序待處理的請求,好讓磁碟讀寫臂以一趟順暢的掃掠跑過碟片,就像電梯用一趟平順的行程依序服務各樓層、而不是上上下下亂彈。SSTF 總是跳到最近的請求;SCAN(以及 LOOK)朝一個方向掃過去、再掃回來。那些演算法每一個的存在,都是為了把尋軌時間降到最小——為了減少讀寫臂實際移動的距離。在 SSD 上,沒有讀寫臂、也沒有尋軌時間,所以根本沒有「距離」可以去最小化。電梯演算法所立足的整個前提,根本就不存在。

所以在 SSD 上,那個聰明的掃掠幾乎幫不上忙。把請求重新排序成「彼此靠得更近」基本上贏不到什麼,因為每一頁都同樣地近。真實的作業系統注意到了:例如 Linux 就為快閃記憶體提供了一個近乎不做事的排程器(甚至有個幾乎完全不重排的選項),而不是它替旋轉磁碟仍然保留著的那套精巧電梯。磁碟排程的重點,已經從「把讀寫臂的移動降到最小」整個轉移到別的目標上了——讓 SSD 內部那許多通道平行地保持忙碌、把小筆寫入打包成批、以及在各行程之間保持公平——這是一個不一樣、也溫和得多的問題。

SSD 沒能修好的事——以及接下來

人們很容易以為 SSD 把儲存問題解決了。它沒有。我們得誠實面對它的極限。SSD 仍然遠遠慢於主記憶體與它上方的那些快取——第 1 篇的儲存階層只是被壓扁了、而非被廢除,RAM 與快閃之間的落差依舊大到足以讓作業系統一如既往地把磁碟資料快取在記憶體裡。快閃單元仍然會磨損,所以 SSD 有一份硬碟所沒有的、有限的寫入額度。而最關鍵的是,SSD 仍然只是一個裝置:當它的控制器故障、或它的晶片壞死時,上頭的每一個位元組都可能一瞬間消失,跟硬碟發生磁頭撞毀而失去資料是一樣的。

注意貫穿整篇導覽的那條線:間接救了我們。FTL 骨子裡就是一張從邏輯到實體的地圖——這跟分頁表把虛擬位址對應到實體頁框、或者邏輯區塊定址把區塊號碼對應到磁碟幾何,是完全同一個點子。每一次系統需要讓一個堅硬的實體現實穿上一張乾淨、穩定的臉孔,它就插進一層轉譯。這一招——一張在名字與位置之間的地圖——是整個計算領域裡最被反覆重用的點子之一,而你現在已經看著它解決了三個截然不同的問題。

但那個沒解決的問題——一個裝置、一個故障點——是剩下最大的一個,也正是這個階段的終點所在。不管一張磁碟機有多快、多聰明,如果失去它就等於失去一切,那你其實並沒有真正解決儲存;你只是讓人更快抵達那道懸崖邊。最後一篇導覽用 RAID 正面迎擊這件事:把好幾張磁碟機組合起來,讓它們合力變得更快(分條 striping)、或者能撐過彼此的死亡(鏡像 mirroring 與同位 parity)、或者兩者兼得——並直視一個令人不安的真相:更多的磁碟機,意味著更多會壞掉的東西,而故障之後的重建,本身就是一個真正危險的時刻。