ZoneFS - 分割槽塊裝置的檔案系統¶
簡介¶
zonefs 是一個非常簡單的檔案系統,它將分割槽塊裝置的每個分割槽公開為一個檔案。與具有原生分割槽塊裝置支援的常規 POSIX 相容檔案系統(例如 f2fs)不同,zonefs 不會對使用者隱藏分割槽塊裝置的順序寫入約束。代表裝置順序寫入分割槽的檔案必須從檔案末尾開始按順序寫入(僅追加寫入)。
因此,zonefs 本質上更接近於原始塊裝置訪問介面,而不是功能齊全的 POSIX 檔案系統。zonefs 的目標是透過用更豐富的檔案 API 替換原始塊裝置檔案訪問來簡化應用程式中對分割槽塊裝置的支援,從而避免依賴可能對開發人員來說更晦澀難懂的直接塊裝置檔案 ioctl。這種方法的一個例子是在分割槽塊裝置上實現 LSM(日誌結構合併)樹結構(例如 RocksDB 和 LevelDB 中使用的),方法是允許 SSTable 儲存在分割槽檔案中,類似於常規檔案系統,而不是作為整個磁碟的扇區範圍。引入更高階的構造“一個檔案是一個分割槽”可以幫助減少應用程式中所需的更改量,並引入對不同應用程式程式語言的支援。
分割槽塊裝置¶
分割槽儲存裝置屬於一類儲存裝置,其地址空間被劃分為多個分割槽。一個分割槽是連續 LBA 的一個組,並且所有分割槽都是連續的(沒有 LBA 間隙)。分割槽可能具有不同的型別。
常規分割槽:屬於常規分割槽的 LBA 沒有訪問約束。可以執行任何讀取或寫入訪問,類似於常規塊裝置。
順序分割槽:這些分割槽接受隨機讀取,但必須按順序寫入。每個順序分割槽都有一個由裝置維護的寫指標,用於跟蹤裝置下一次寫入的強制起始 LBA 位置。由於這種寫入約束,順序分割槽中的 LBA 不能被覆蓋。在重寫之前,必須首先使用特殊命令(分割槽重置)擦除順序分割槽。
可以使用各種記錄和媒體技術來實現分割槽儲存裝置。當今最常見的形式是使用 Shingled Magnetic Recording (SMR) HDD 上的 SCSI Zoned Block Commands (ZBC) 和 Zoned ATA Commands (ZAC) 介面。
固態硬碟 (SSD) 儲存裝置也可以實現分割槽介面,例如,減少由於垃圾回收導致的內部寫入放大。NVMe Zoned NameSpace (ZNS) 是 NVMe 標準委員會的技術提案,旨在向 NVMe 協議新增分割槽儲存介面。
Zonefs 概述¶
Zonefs 將分割槽塊裝置的分割槽公開為檔案。代表分割槽的檔案的按分割槽型別分組,分割槽型別本身由子目錄表示。此檔案結構完全使用裝置提供的分割槽資訊構建,因此不需要任何複雜的磁碟元資料結構。
磁碟元資料¶
zonefs 磁碟元資料減少為一個不可變的超級塊,該超級塊持久儲存一個幻數和可選的特徵標誌和值。掛載時,zonefs 使用 blkdev_report_zones() 獲取裝置分割槽配置,並僅根據此資訊使用靜態檔案樹填充掛載點。檔案大小來自裝置分割槽型別和裝置本身管理的寫指標位置。
超級塊始終寫入磁碟上的扇區 0。儲存超級塊的裝置的第一個分割槽永遠不會被 zonefs 公開為分割槽檔案。如果包含超級塊的分割槽是一個順序分割槽,則 mkzonefs 格式化工具總是“完成”該分割槽,即將其轉換為完整狀態以使其只讀,從而防止任何資料寫入。
分割槽型別子目錄¶
代表相同型別分割槽的檔案的都集中在掛載時自動建立的同一個子目錄下。
對於常規分割槽,使用子目錄“cnv”。但是,只有當裝置具有可用的常規分割槽時才會建立此目錄。如果裝置在扇區 0 只有一個常規分割槽,則該分割槽不會公開為檔案,因為它將用於儲存 zonefs 超級塊。對於此類裝置,將不會建立“cnv”子目錄。
對於順序寫入分割槽,使用子目錄“seq”。
這兩個目錄是 zonefs 中存在的唯一目錄。使用者無法建立其他目錄,也無法重新命名或刪除“cnv”和“seq”子目錄。
由 stat() 或 fstat() 系統呼叫獲得的 struct stat 的 st_size 欄位指示的目錄大小指示目錄下的檔案數量。
分割槽檔案¶
分割槽檔案使用它們在特定型別分割槽集中代表的分割槽編號命名。也就是說,“cnv”和“seq”目錄都包含名為“0”、“1”、“2”的檔案。檔案編號還表示裝置上遞增的分割槽起始扇區。
不允許對超出檔案最大大小(即超出分割槽容量)的分割槽檔案執行所有讀取和寫入操作。任何超過分割槽容量的訪問都會失敗,並顯示 -EFBIG 錯誤。
不允許建立、刪除、重新命名或修改檔案和子目錄的任何屬性。
stat() 和 fstat() 報告的檔案塊數表示分割槽檔案的容量,換句話說,就是最大檔案大小。
常規分割槽檔案¶
常規分割槽檔案的大小固定為它們代表的分割槽的大小。常規分割槽檔案不能被截斷。
這些檔案可以使用任何型別的 I/O 操作隨機讀取和寫入:緩衝 I/O、直接 I/O、記憶體對映 I/O (mmap) 等。除了上面提到的檔案大小限制之外,這些檔案沒有 I/O 約束。
順序分割槽檔案¶
“seq”子目錄中分組的順序分割槽檔案的大小表示檔案相對於分割槽起始扇區的分割槽寫指標位置。
順序分割槽檔案只能從檔案末尾開始按順序寫入,也就是說,寫入操作只能是追加寫入。Zonefs 不會嘗試接受隨機寫入,並且會使任何具有不對應於檔案末尾或未完成的最後一個已發出的寫入(對於非同步 I/O 操作)的起始偏移量的寫入請求失敗。
由於頁面快取的髒頁面寫回不能保證順序寫入模式,因此 zonefs 會阻止順序檔案上的緩衝寫入和可寫共享對映。只有直接 I/O 寫入才能被這些檔案接受。 zonefs 依賴於塊層電梯實現的將寫入 I/O 請求順序傳遞給裝置。必須使用實現分割槽塊裝置的順序寫入功能的電梯(ELEVATOR_F_ZBD_SEQ_WRITE 電梯功能)。預設情況下,在裝置初始化時為分割槽塊裝置設定此型別的電梯(例如 mq-deadline)。
對於順序分割槽檔案中的讀取操作使用的 I/O 型別沒有限制。接受緩衝 I/O、直接 I/O 和共享讀取對映。
僅允許將順序分割槽檔案截斷為 0,在這種情況下,分割槽將重置以將檔案分割槽寫指標位置倒回到分割槽開始處,或者截斷為分割槽容量,在這種情況下,檔案分割槽將轉換為 FULL 狀態(完成分割槽操作)。
格式化選項¶
可以在格式化時啟用 zonefs 的幾個可選功能。
常規分割槽聚合:可以將連續常規分割槽的範圍聚合為單個更大的檔案,而不是預設的每個分割槽一個檔案。
檔案所有權:分割槽檔案的所有者 UID 和 GID 預設值為 0(root),但可以更改為任何有效的 UID/GID。
檔案訪問許可權:可以更改預設的 640 訪問許可權。
IO 錯誤處理¶
分割槽塊裝置可能會因與常規塊裝置類似的原因(例如,由於壞扇區)而導致 I/O 請求失敗。但是,除了這種已知的 I/O 故障模式外,管理分割槽塊裝置行為的標準還定義了導致 I/O 錯誤的附加條件。
一個分割槽可能會轉換為只讀狀態 (BLK_ZONE_COND_READONLY):雖然分割槽中已寫入的資料仍然可讀,但該分割槽不能再寫入。使用者對分割槽(分割槽管理命令或讀/寫訪問)的任何操作都無法將分割槽狀態改回正常的讀/寫狀態。雖然裝置將分割槽轉換為只讀狀態的原因未由標準定義,但這種轉換的典型原因將是 HDD 上的有缺陷的寫磁頭(此磁頭下的所有分割槽都更改為只讀)。
一個分割槽可能會轉換為離線狀態 (BLK_ZONE_COND_OFFLINE):無法讀取或寫入離線分割槽。沒有任何使用者操作可以將離線分割槽轉換回可操作的良好狀態。與分割槽只讀轉換類似,驅動器將分割槽轉換為離線狀態的原因未定義。典型的原因是 HDD 上有缺陷的讀寫磁頭導致斷裂的磁頭下的碟片上的所有分割槽都無法訪問。
未對齊的寫入錯誤:這些錯誤是由於主機發出寫入請求時,起始扇區不對應於裝置執行寫入請求時的分割槽寫指標位置而導致的。即使 zonefs 強制順序分割槽進行順序檔案寫入,在拆分為多個 BIO/請求或非同步 I/O 操作的非常大的直接 I/O 操作部分失敗的情況下,仍然可能發生未對齊的寫入錯誤。如果發往裝置的一組順序寫入請求中的一個寫入請求失敗,則在其之後排隊的所有寫入請求都將變為未對齊狀態並失敗。
延遲寫入錯誤:與常規塊裝置類似,如果啟用了裝置端寫入快取,則當裝置寫入快取重新整理時(例如在 fsync() 上),可能會在先前完成的寫入範圍內發生寫入錯誤。與之前的立即未對齊寫入錯誤情況類似,延遲寫入錯誤可能會透過分割槽的快取順序資料流傳播,導致在導致錯誤的扇區之後丟棄所有資料。
zonefs 檢測到的所有 I/O 錯誤都會透過觸發或檢測到錯誤的系統呼叫的錯誤程式碼返回來通知使用者。zonefs 響應 I/O 錯誤而採取的恢復操作取決於 I/O 型別(讀取與寫入)和錯誤原因(壞扇區、未對齊的寫入或分割槽狀態更改)。
對於讀取 I/O 錯誤,zonefs 不會執行任何特定的恢復操作,但前提是檔案分割槽仍處於良好狀態,並且檔案 inode 大小與其分割槽寫指標位置之間沒有不一致。如果檢測到問題,則執行 I/O 錯誤恢復(請參閱下表)。
對於寫入 I/O 錯誤,始終執行 zonefs I/O 錯誤恢復。
分割槽狀態更改為只讀或離線也始終會觸發 zonefs I/O 錯誤恢復。
Zonefs 最小 I/O 錯誤恢復可能會更改檔案大小和檔案訪問許可權。
檔案大小更改:順序分割槽檔案中的立即或延遲寫入錯誤可能會導致檔案 inode 大小與檔案中成功寫入的資料量不一致。例如,多 BIO 大型寫入操作的部分失敗將導致分割槽寫指標部分前進,即使整個寫入操作將被報告為對使用者失敗。在這種情況下,必須提前檔案 inode 大小以反映分割槽寫指標更改,並最終允許使用者在檔案末尾重新開始寫入。還可以減小檔案大小以反映在 fsync() 上檢測到的延遲寫入錯誤:在這種情況下,分割槽中有效寫入的資料量可能少於最初由檔案 inode 大小指示的資料量。在此類 I/O 錯誤之後,zonefs 始終修復檔案 inode 大小,以反映持久儲存在檔案分割槽中的資料量。
訪問許可權更改:分割槽狀態更改為只讀狀態,這透過檔案訪問許可權的更改來指示,以使檔案變為只讀狀態。這將停用對檔案屬性和資料修改的更改。對於離線分割槽,將停用對檔案的所有許可權(讀取和寫入)。
使用者可以使用“errors=xxx”掛載選項控制 zonefs I/O 錯誤恢復採取的進一步操作。下表總結了 zonefs I/O 錯誤處理的結果,具體取決於掛載選項和分割槽狀態
+--------------+-----------+-----------------------------------------+
| | | Post error state |
| "errors=xxx" | device | access permissions |
| mount | zone | file file device zone |
| option | condition | size read write read write |
+--------------+-----------+-----------------------------------------+
| | good | fixed yes no yes yes |
| remount-ro | read-only | as is yes no yes no |
| (default) | offline | 0 no no no no |
+--------------+-----------+-----------------------------------------+
| | good | fixed yes no yes yes |
| zone-ro | read-only | as is yes no yes no |
| | offline | 0 no no no no |
+--------------+-----------+-----------------------------------------+
| | good | 0 no no yes yes |
| zone-offline | read-only | 0 no no yes no |
| | offline | 0 no no no no |
+--------------+-----------+-----------------------------------------+
| | good | fixed yes yes yes yes |
| repair | read-only | as is yes no yes no |
| | offline | 0 no no no no |
+--------------+-----------+-----------------------------------------+
更多說明
如果未指定任何 errors 掛載選項,“errors=remount-ro”掛載選項是 zonefs I/O 錯誤處理的預設行為。
使用“errors=remount-ro”掛載選項,將檔案訪問許可權更改為只讀狀態適用於所有檔案。檔案系統以只讀方式重新掛載。
由於裝置將分割槽轉換為離線狀態而導致的訪問許可權和檔案大小更改是永久性的。使用 mkfs.zonefs (mkzonefs) 重新掛載或重新格式化裝置不會將離線分割槽檔案更改回良好狀態。
由於裝置將分割槽轉換為只讀狀態而導致的檔案訪問許可權更改是永久性的。重新掛載或重新格式化裝置不會重新啟用檔案寫入訪問。
remount-ro、zone-ro 和 zone-offline 掛載選項隱含的檔案訪問許可權更改對於處於良好狀態的分割槽是臨時的。解除安裝並重新掛載檔案系統將恢復受影響檔案的先前預設(格式化時間值)訪問許可權。
repair 掛載選項僅觸發最小的 I/O 錯誤恢復操作集,即良好狀態的分割槽的檔案大小修復。裝置指示為只讀或離線狀態的分割槽仍然意味著如上表所述更改分割槽檔案訪問許可權。
掛載選項¶
zonefs 定義了幾個掛載選項: * errors=<behavior> * explicit-open
“errors=<behavior>”選項¶
“errors=<behavior>”選項掛載選項允許使用者指定 zonefs 響應 I/O 錯誤、inode 大小不一致或分割槽狀態更改的行為。定義的行為如下
remount-ro(預設)
zone-ro
zone-offline
repair
先前部分詳細介紹了為每個行為定義的執行時 I/O 錯誤操作。掛載時 I/O 錯誤將導致掛載操作失敗。只讀分割槽的處理在掛載時和執行時也有所不同。如果在掛載時發現只讀分割槽,則始終以與離線分割槽相同的方式處理該分割槽,即,停用所有訪問並將分割槽檔案大小設定為 0。這是必要的,因為 ZBC 和 ZAC 標準將只讀分割槽的寫指標定義為 invalib,因此無法發現已寫入分割槽的資料量。如上一節所述,如果在執行時發現只讀分割槽。分割槽檔案的大小與其上次更新的值保持不變。
“explicit-open”選項¶
分割槽塊裝置(例如 NVMe 分割槽名稱空間裝置)可能對可以處於活動狀態的分割槽數量有限制,即,處於隱式開啟、顯式開啟或關閉狀態的分割槽。如果使用者發出寫入請求時,檔案的分割槽尚未處於活動狀態,則這種潛在的限制會轉化為應用程式因超過此限制而看到寫入 IO 錯誤的風險。
為了避免這些潛在的錯誤,“explicit-open”掛載選項強制使用開啟分割槽命令啟用分割槽,即在首次開啟檔案進行寫入時。如果分割槽開啟命令成功,則可以保證應用程式可以處理寫入請求。相反,如果分割槽未滿或為空,則“explicit-open”掛載選項將在最後一次 close() 分割槽檔案時導致向裝置發出分割槽關閉命令。
執行時 sysfs 屬性¶
zonefs 為已掛載的裝置定義了幾個 sysfs 屬性。所有屬性都是使用者可讀的,可以在目錄 /sys/fs/zonefs/<dev>/ 中找到,其中 <dev> 是已掛載的分割槽塊裝置的名稱。
定義的屬性如下。
max_wro_seq_files:此屬性報告可以開啟進行寫入的順序分割槽檔案的最大數量。此數字對應於裝置支援的最大顯式或隱式開啟分割槽數。值為 0 表示裝置沒有限制,並且任何分割槽(任何檔案)都可以隨時開啟進行寫入和寫入,而不管其他分割槽的狀態如何。當使用 *explicit-open* 掛載選項時,當已開啟進行寫入的順序分割槽檔案的數量達到 *max_wro_seq_files* 限制時,zonefs 將使任何請求開啟順序分割槽檔案進行寫入的 open() 系統呼叫失敗。
nr_wro_seq_files:此屬性報告當前開啟進行寫入的順序分割槽檔案的數量。當使用“explicit-open”掛載選項時,此數字永遠不能超過 *max_wro_seq_files*。如果未使用 *explicit-open* 掛載選項,則報告的數字可以大於 *max_wro_seq_files*。在這種情況下,應用程式有責任不要同時寫入超過 *max_wro_seq_files* 個順序分割槽檔案。否則可能導致寫入錯誤。
max_active_seq_files:此屬性報告處於活動狀態的順序分割槽檔案的最大數量,即部分寫入(不為空或已滿)或具有顯式開啟的分割槽(僅當使用 *explicit-open* 掛載選項時才會發生)的順序分割槽檔案。此數字始終等於裝置支援的最大活動分割槽數。值為 0 表示已掛載的裝置對可以活動的順序分割槽檔案的數量沒有限制。
nr_active_seq_files:此屬性報告當前活動的順序分割槽檔案的數量。如果 *max_active_seq_files* 不為 0,則 *nr_active_seq_files* 的值永遠不能超過 *nr_active_seq_files* 的值,而不管是否使用 *explicit-open* 掛載選項。
Zonefs 使用者空間工具¶
mkzonefs 工具用於格式化分割槽塊裝置以與 zonefs 一起使用。此工具可在 Github 上獲得,網址為
https://github.com/damien-lemoal/zonefs-tools
zonefs-tools 還包括一個測試套件,該套件可以針對任何分割槽塊裝置執行,包括使用分割槽模式建立的 null_blk 塊裝置。
示例¶
以下格式化了一個啟用了常規分割槽聚合功能的具有 256 MB 分割槽的 15TB 主機管理型 SMR HDD
# mkzonefs -o aggr_cnv /dev/sdX
# mount -t zonefs /dev/sdX /mnt
# ls -l /mnt/
total 0
dr-xr-xr-x 2 root root 1 Nov 25 13:23 cnv
dr-xr-xr-x 2 root root 55356 Nov 25 13:23 seq
分割槽檔案子目錄的大小指示每種型別分割槽存在的檔案數量。在此示例中,只有一個常規分割槽檔案(所有常規分割槽都聚合在單個檔案下)
# ls -l /mnt/cnv
total 137101312
-rw-r----- 1 root root 140391743488 Nov 25 13:23 0
此聚合的常規分割槽檔案可以用作常規檔案
# mkfs.ext4 /mnt/cnv/0
# mount -o loop /mnt/cnv/0 /data
在此示例中,將檔案分組到順序寫入分割槽的“seq”子目錄具有 55356 個分割槽
# ls -lv /mnt/seq
total 14511243264
-rw-r----- 1 root root 0 Nov 25 13:23 0
-rw-r----- 1 root root 0 Nov 25 13:23 1
-rw-r----- 1 root root 0 Nov 25 13:23 2
...
-rw-r----- 1 root root 0 Nov 25 13:23 55354
-rw-r----- 1 root root 0 Nov 25 13:23 55355
對於順序寫入分割槽檔案,檔案大小隨著資料附加到檔案末尾而變化,類似於任何常規檔案系統
# dd if=/dev/zero of=/mnt/seq/0 bs=4096 count=1 conv=notrunc oflag=direct
1+0 records in
1+0 records out
4096 bytes (4.1 kB, 4.0 KiB) copied, 0.00044121 s, 9.3 MB/s
# ls -l /mnt/seq/0
-rw-r----- 1 root root 4096 Nov 25 13:23 /mnt/seq/0
可以將寫入的檔案截斷為分割槽大小,從而防止任何進一步的寫入操作
# truncate -s 268435456 /mnt/seq/0
# ls -l /mnt/seq/0
-rw-r----- 1 root root 268435456 Nov 25 13:49 /mnt/seq/0
截斷為 0 大小允許釋放檔案分割槽儲存空間並重新開始對檔案進行附加寫入
# truncate -s 0 /mnt/seq/0
# ls -l /mnt/seq/0
-rw-r----- 1 root root 0 Nov 25 13:49 /mnt/seq/0
由於檔案在磁碟上靜態對映到分割槽,因此 stat() 和 fstat() 報告的檔案塊數表示檔案分割槽的容量
# stat /mnt/seq/0
File: /mnt/seq/0
Size: 0 Blocks: 524288 IO Block: 4096 regular empty file
Device: 870h/2160d Inode: 50431 Links: 1
Access: (0640/-rw-r-----) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2019-11-25 13:23:57.048971997 +0900
Modify: 2019-11-25 13:52:25.553805765 +0900
Change: 2019-11-25 13:52:25.553805765 +0900
Birth: -
以 512B 塊為單位的檔案塊數(“Blocks”)給出了最大檔案大小 524288 * 512 B = 256 MB,這對應於此示例中的裝置分割槽容量。值得注意的是,“IO 塊”欄位始終指示寫入的最小 I/O 大小,並且對應於裝置的物理扇區大小。