2. 高層設計¶
ext4 檔案系統被劃分為一系列塊組。為了減少碎片導致的效能問題,塊分配器會盡力將每個檔案的塊保留在同一個組內,從而減少尋道時間。塊組的大小由 sb.s_blocks_per_group 塊指定,也可以透過 8 * block_size_in_bytes 計算得出。預設塊大小為 4KiB 時,每個組將包含 32,768 個塊,長度為 128MiB。塊組的數量是裝置大小除以塊組大小得出的結果。
ext4 中的所有欄位都以小端序寫入磁碟。然而,jbd2(日誌)中的所有欄位都以大端序寫入磁碟。
2.1. 塊¶
ext4 以“塊”為單位分配儲存空間。一個塊是 1KiB 到 64KiB 之間的一組扇區,並且扇區數量必須是 2 的整數冪。塊又被分組為更大的單元,稱為塊組。塊大小在 mkfs 時指定,通常為 4KiB。如果塊大小大於頁面大小(即在只有 4KiB 記憶體頁的 i386 上使用 64KiB 塊),您可能會遇到掛載問題。預設情況下,檔案系統可以包含 2^32 個塊;如果啟用“64bit”特性,檔案系統可以擁有 2^64 個塊。結構的位置是根據結構所在的塊號儲存的,而不是磁碟上的絕對偏移量。
對於 32 位檔案系統,限制如下:
專案 |
1KiB |
2KiB |
4KiB |
64KiB |
|---|---|---|---|---|
塊 |
2^32 |
2^32 |
2^32 |
2^32 |
inodes |
2^32 |
2^32 |
2^32 |
2^32 |
檔案系統大小 |
4TiB |
8TiB |
16TiB |
256TiB |
每塊組塊數 |
8,192 |
16,384 |
32,768 |
524,288 |
每塊組inodes數 |
8,192 |
16,384 |
32,768 |
524,288 |
塊組大小 |
8MiB |
32MiB |
128MiB |
32GiB |
每檔案塊數,範圍 |
2^32 |
2^32 |
2^32 |
2^32 |
每檔案塊數,塊對映 |
16,843,020 |
134,480,396 |
1,074,791,436 |
4,398,314,962,956 (由於欄位大小限制,實際為 2^32) |
檔案大小,範圍 |
4TiB |
8TiB |
16TiB |
256TiB |
檔案大小,塊對映 |
16GiB |
256GiB |
4TiB |
256TiB |
對於 64 位檔案系統,限制如下:
專案 |
1KiB |
2KiB |
4KiB |
64KiB |
|---|---|---|---|---|
塊 |
2^64 |
2^64 |
2^64 |
2^64 |
inodes |
2^32 |
2^32 |
2^32 |
2^32 |
檔案系統大小 |
16ZiB |
32ZiB |
64ZiB |
1YiB |
每塊組塊數 |
8,192 |
16,384 |
32,768 |
524,288 |
每塊組inodes數 |
8,192 |
16,384 |
32,768 |
524,288 |
塊組大小 |
8MiB |
32MiB |
128MiB |
32GiB |
每檔案塊數,範圍 |
2^32 |
2^32 |
2^32 |
2^32 |
每檔案塊數,塊對映 |
16,843,020 |
134,480,396 |
1,074,791,436 |
4,398,314,962,956 (由於欄位大小限制,實際為 2^32) |
檔案大小,範圍 |
4TiB |
8TiB |
16TiB |
256TiB |
檔案大小,塊對映 |
16GiB |
256GiB |
4TiB |
256TiB |
注意:不使用 extents(即使用塊對映)的檔案必須放置在檔案系統的前 2^32 個塊內。使用 extents 的檔案必須放置在檔案系統的前 2^48 個塊內。對於更大的檔案系統會發生什麼尚不清楚。
2.2. 佈局¶
標準塊組的佈局大致如下(以下每個欄位將在下面的單獨部分中討論):
組 0 填充 |
ext4 超級塊 |
組描述符 |
保留的 GDT 塊 |
資料塊點陣圖 |
inode 點陣圖 |
inode 表 |
資料塊 |
|---|---|---|---|---|---|---|---|
1024 位元組 |
1 個塊 |
多個塊 |
多個塊 |
1 個塊 |
1 個塊 |
多個塊 |
更多塊 |
對於塊組 0 的特殊情況,前 1024 位元組是未使用的,以便安裝 x86 引導扇區和其他特殊內容。超級塊將從偏移量 1024 位元組處開始,無論它位於哪個塊(通常是塊 0)。但是,如果由於某種原因塊大小等於 1024,那麼塊 0 將被標記為已使用,超級塊位於塊 1。對於所有其他塊組,沒有填充。
ext4 驅動程式主要處理超級塊和塊組 0 中找到的組描述符。超級塊和組描述符的冗餘副本會寫入磁碟上的某些塊組,以防磁碟開頭部分損壞,儘管並非所有塊組都必然包含冗餘副本(詳見下一段)。如果該組沒有冗餘副本,則塊組以資料塊點陣圖開始。另請注意,當檔案系統首次格式化時,mkfs 會在塊組描述符之後和塊點陣圖開始之前分配“保留 GDT 塊”空間,以允許檔案系統未來擴充套件。預設情況下,檔案系統允許以原始檔案系統大小的 1024 倍增加大小。
inode 表的位置由 grp.bg_inode_table_* 給出。它是一個連續的塊範圍,足以包含 sb.s_inodes_per_group * sb.s_inode_size 位元組。
至於塊組中專案的順序,通常規定超級塊和組描述符表(如果存在)將位於塊組的開頭。點陣圖和 inode 表可以位於任何位置,並且點陣圖完全有可能在 inode 表之後,或者兩者位於不同的組中 (flex_bg)。剩餘空間用於檔案資料塊、間接塊對映、範圍樹塊和擴充套件屬性。
2.3. 彈性塊組¶
從 ext4 開始,有一個名為彈性塊組 (flex_bg) 的新特性。在 flex_bg 中,幾個塊組被繫結在一起成為一個邏輯塊組;flex_bg 中第一個塊組的點陣圖空間和 inode 表空間被擴充套件,以包含 flex_bg 中所有其他塊組的點陣圖和 inode 表。例如,如果 flex_bg 大小為 4,那麼組 0 將(按順序)包含超級塊、組描述符、組 0-3 的資料塊點陣圖、組 0-3 的 inode 點陣圖、組 0-3 的 inode 表,並且組 0 中的剩餘空間用於檔案資料。這樣做的目的是將塊組元資料緊密地分組在一起以便更快地載入,並使大檔案在磁碟上連續。即使啟用了 flex_bg,超級塊和組描述符的備份副本也始終位於塊組的開頭。構成 flex_bg 的塊組數量由 2 ^ sb.s_log_groups_per_flex 給出。
2.4. 元塊組¶
如果沒有 META_BG 選項,出於安全考慮,所有塊組描述符副本都儲存在第一個塊組中。鑑於預設的 128MiB (2^27 位元組) 塊組大小和 64 位元組的組描述符,ext4 最多可以擁有 2^27/64 = 2^21 個塊組。這將整個檔案系統大小限制為 2^21 * 2^27 = 2^48 位元組或 256TiB。
解決此問題的方法是使用元塊組特性 (META_BG),該特性已包含在 ext3 的所有 2.6 版本中。啟用 META_BG 特性後,ext4 檔案系統被劃分為許多元塊組。每個元塊組都是一個塊組的叢集,其組描述符結構可以儲存在一個磁碟塊中。對於塊大小為 4 KB 的 ext4 檔案系統,單個元塊組分割槽包含 64 個塊組,即 8 GiB 的磁碟空間。元塊組特性將組描述符的位置從整個檔案系統的第一個擁擠的塊組移動到每個元塊組自身的第一個組中。備份位於每個元塊組的第二個和最後一個組中。這將 2^21 的最大塊組限制提高到硬性限制 2^32,從而支援 512PiB 的檔案系統。
檔案系統格式的更改取代了當前超級塊後跟可變長度塊組描述符集的方案。相反,超級塊和單個塊組描述符塊放置在元塊組的第一個、第二個和最後一個塊組的開頭。元塊組是可以透過單個塊組描述符塊描述的塊組集合。由於塊組描述符結構的大小為 64 位元組,因此對於塊大小為 1KB 的檔案系統,一個元塊組包含 16 個塊組;對於塊大小為 4KB 的檔案系統,包含 64 個塊組。檔案系統可以使用這種新的塊組描述符佈局建立,或者現有檔案系統可以線上調整大小,超級塊中的 s_first_meta_bg 欄位將指示第一個使用這種新佈局的塊組。
請參閱關於塊和 inode 點陣圖部分中有關 BLOCK_UNINIT 的重要說明。
2.5. 惰性塊組初始化¶
ext4 的一個新特性是三個塊組描述符標誌,它們使 mkfs 能夠跳過初始化塊組元資料的其他部分。具體來說,INODE_UNINIT 和 BLOCK_UNINIT 標誌意味著該組的 inode 和塊點陣圖可以計算得出,因此磁碟上的點陣圖塊不會被初始化。這通常發生在空塊組或只包含固定位置塊組元資料的塊組中。INODE_ZEROED 標誌意味著 inode 表已初始化;mkfs 將取消設定此標誌並依賴核心在後臺初始化 inode 表。
透過不向點陣圖和 inode 表寫入零,mkfs 時間大大減少。請注意,特性標誌是 RO_COMPAT_GDT_CSUM,但 dumpe2fs 輸出將其列印為“uninit_bg”。它們是同一回事。
2.6. 特殊 inode¶
ext4 為特殊功能保留了一些 inode,如下所示:
inode 號 |
用途 |
|---|---|
0 |
不存在;沒有 inode 0。 |
1 |
壞塊列表。 |
2 |
根目錄。 |
3 |
使用者配額。 |
4 |
組配額。 |
5 |
引導載入程式。 |
6 |
取消刪除目錄。 |
7 |
保留組描述符 inode。(“resize inode”) |
8 |
日誌 inode。 |
9 |
“排除”inode,用於快照(?) |
10 |
副本 inode,用於某些非上游特性? |
11 |
傳統的第一個非保留 inode。通常這是 lost+found 目錄。請參閱超級塊中的 s_first_ino。 |
請注意,還有一些從非保留 inode 號分配的 inode 用於其他檔案系統特性,這些特性未從標準目錄層次結構中引用。它們通常從超級塊中引用。它們是:
超級塊欄位 |
描述 |
|---|---|
s_lpf_ino |
lost+found 目錄的 inode 號。 |
s_prj_quota_inum |
跟蹤專案配額的配額檔案的 inode 號 |
s_orphan_file_inum |
跟蹤孤立 inode 的檔案的 inode 號。 |
2.7. 塊和 inode 分配策略¶
ext4 認識到(無論如何都比 ext3 更好)資料區域性性通常是檔案系統的一個理想特性。在旋轉磁碟上,將相關塊彼此靠近可以減少磁頭致動器和磁碟訪問資料塊所需的移動量,從而加快磁碟 I/O。在 SSD 上當然沒有移動部件,但區域性性可以增加每次傳輸請求的大小,同時減少請求的總數。這種區域性性還可能將寫入集中到單個擦除塊上,從而顯著加快檔案重寫。因此,儘可能減少碎片很有用。
ext4 用於對抗碎片的第一個工具是多塊分配器。當首次建立檔案時,塊分配器會推測性地為檔案分配 8KiB 的磁碟空間,假設該空間很快就會被寫入。當檔案關閉時,未使用的推測性分配當然會被釋放,但如果推測正確(通常是小檔案完整寫入的情況),那麼檔案資料將以單個多塊範圍的形式寫入。ext4 使用的第二個相關技巧是延遲分配。在這種方案下,當檔案需要更多塊來吸收檔案寫入時,檔案系統會推遲決定磁碟上的精確位置,直到所有髒緩衝區都寫入磁碟。透過在絕對必要之前(達到提交超時,或呼叫 sync(),或核心記憶體不足)不承諾特定的位置,希望檔案系統能夠做出更好的位置決策。
ext4(和 ext3)使用的第三個技巧是,它嘗試將檔案的檔案資料塊與其 inode 保持在同一個塊組中。這減少了檔案系統首次必須讀取檔案 inode 以瞭解檔案資料塊位於何處,然後尋道到檔案資料塊以開始 I/O 操作時的尋道懲罰。
第四個技巧是,如果可行,目錄中的所有 inode 都放置在與該目錄相同的塊組中。這裡的工作假設是,目錄中的所有檔案可能都是相關的,因此嘗試將它們全部放在一起是有用的。
第五個技巧是,磁碟卷被劃分為 128MB 的塊組;這些迷你容器如上所述用於嘗試維護資料區域性性。然而,存在一個故意的怪癖——當在根目錄中建立目錄時,inode 分配器會掃描塊組,並將該目錄放入它能找到的負載最輕的塊組中。這鼓勵目錄在磁碟上分散開來;當頂級目錄/檔案 blob 填滿一個塊組時,分配器會簡單地移動到下一個塊組。據稱,這種方案可以平衡塊組的負載,儘管作者懷疑那些不幸落到旋轉驅動器末尾的目錄在效能方面會受到不公平待遇。
當然,如果所有這些機制都失敗了,總是可以使用 e4defrag 來對檔案進行碎片整理。
2.8. 校驗和¶
從 2012 年初開始,元資料校驗和被新增到所有主要的 ext4 和 jbd2 資料結構中。相關的特性標誌是 metadata_csum。所需的校驗和演算法在超級塊中指示,儘管截至 2012 年 10 月,唯一支援的演算法是 crc32c。一些資料結構沒有足夠的空間容納完整的 32 位校驗和,因此只儲存了低 16 位。啟用 64bit 特性會增加資料結構大小,以便為許多資料結構儲存完整的 32 位校驗和。但是,現有的 32 位檔案系統無法擴充套件以啟用 64bit 模式,至少在沒有實驗性 resize2fs 補丁的情況下不能。
透過對底層裝置執行 tune2fs -O metadata_csum,可以為現有檔案系統新增校驗和。如果 tune2fs 遇到缺少足夠空閒空間來新增校驗和的目錄塊,它將要求您執行 e2fsck -D 以使用校驗和重建目錄。這具有從目錄檔案中移除鬆弛空間和重新平衡 htree 索引的額外好處。如果您 _忽略_ 此步驟,您的目錄將不受校驗和保護!
下表描述了每種校驗和型別包含的資料元素。校驗和函式是超級塊描述的任何函式(截至 2013 年 10 月為 crc32c),除非另有說明。
元資料 |
長度 |
成分 |
|---|---|---|
超級塊 |
__le32 |
整個超級塊直到校驗和欄位。UUID 存在於超級塊內部。 |
MMP |
__le32 |
UUID + 整個 MMP 塊直到校驗和欄位。 |
擴充套件屬性 |
__le32 |
UUID + 整個擴充套件屬性塊。校驗和欄位設定為零。 |
目錄項 |
__le32 |
UUID + inode 號 + inode 代數 + 目錄塊直到包含校驗和欄位的虛假條目。 |
HTREE 節點 |
__le32 |
UUID + inode 號 + inode 代數 + 所有有效範圍 + HTREE 尾部。校驗和欄位設定為零。 |
範圍 |
__le32 |
UUID + inode 號 + inode 代數 + 整個範圍塊直到校驗和欄位。 |
點陣圖 |
__le32 或 __le16 |
UUID + 整個點陣圖。校驗和儲存在組描述符中,如果組描述符大小為 32 位元組(即 ^64bit)則被截斷。 |
inodes |
__le32 |
UUID + inode 號 + inode 代數 + 整個 inode。校驗和欄位設定為零。每個 inode 都有自己的校驗和。 |
組描述符 |
__le16 |
如果 metadata_csum,則為 UUID + 組號 + 整個描述符;否則如果 gdt_csum,則為 crc16(UUID + 組號 + 整個描述符)。在所有情況下,只儲存低 16 位。 |
2.9. 大分配¶
目前,塊的預設大小為 4KiB,這是大多數支援 MMU 的硬體上普遍支援的頁面大小。這是幸運的,因為 ext4 程式碼尚未準備好處理塊大小超過頁面大小的情況。然而,對於主要包含巨大檔案的檔案系統,能夠以多塊為單位分配磁碟塊是可取的,以減少碎片和元資料開銷。bigalloc 特性正是提供了這種能力。
bigalloc 特性 (EXT4_FEATURE_RO_COMPAT_BIGALLOC) 使 ext4 使用簇分配,因此 ext4 塊分配點陣圖中的每個位都定址 2 的冪次方數量的塊。例如,如果檔案系統主要儲存 4-32 兆位元組範圍的大檔案,那麼將簇大小設定為 1 兆位元組可能是有意義的。這意味著塊分配點陣圖中的每個位現在定址 256 個 4k 塊。這將 2T 檔案系統的塊分配點陣圖總大小從 64 兆位元組縮小到 256 千位元組。這也意味著一個塊組定址 32 吉位元組而不是 128 兆位元組,也縮小了檔案系統元資料的開銷。
管理員可以在 mkfs 時設定塊簇大小(儲存在超級塊的 s_log_cluster_size 欄位中);從那時起,塊點陣圖跟蹤的是簇,而不是單個塊。這意味著塊組可以達到幾 GB 大小(而不僅僅是 128MiB);但是,即使對於目錄,最小分配單位也變為簇,而不是塊。淘寶曾有一個補丁集,旨在將“使用簇作為單位而不是塊”擴充套件到範圍樹,但目前尚不清楚這些補丁去了哪裡——它們最終演變為“範圍樹 v2”,但截至 2015 年 5 月,該程式碼尚未合併。
2.10. 內聯資料¶
內聯資料特性旨在處理檔案資料非常小以至於可以輕鬆放入 inode 的情況,這(理論上)減少了磁碟塊消耗並減少了尋道。如果檔案小於 60 位元組,則資料以內聯方式儲存在 inode.i_block 中。如果檔案的其餘部分可以放入擴充套件屬性空間,那麼它可能會作為 inode 正文(“ibody EA”)中的擴充套件屬性“system.data”找到。這當然限制了可以附加到 inode 的擴充套件屬性的數量。如果資料大小超過 i_block + ibody EA,則會分配一個常規塊並將內容移動到該塊。
在等待更改以壓縮用於儲存內聯資料的擴充套件屬性鍵的情況下,應該能夠在 256 位元組的 inode 中儲存 160 位元組的資料(截至 2015 年 6 月,當時 i_extra_isize 為 28)。在此之前,由於 inode 空間使用效率低下,限制為 156 位元組。
內聯資料特性要求存在“system.data”的擴充套件屬性,即使該屬性值為零長度。
2.10.1. 內聯目錄¶
i_block 的前四個位元組是父目錄的 inode 號。緊隨其後是一個 56 位元組的空間,用於儲存目錄條目陣列;請參閱 struct ext4_dir_entry。如果 inode 正文中存在“system.data”屬性,則 EA 值也是一個 struct ext4_dir_entry 陣列。請注意,對於內聯目錄,i_block 和 EA 空間被視為單獨的 dirent 塊;目錄條目不能跨越兩者。
內聯目錄條目不進行校驗和計算,因為 inode 校驗和應該保護所有內聯資料內容。
2.11. 大型擴充套件屬性值¶
為了使 ext4 能夠儲存不適合 inode 或附加到 inode 的單個擴充套件屬性塊中的擴充套件屬性值,EA_INODE 特性允許我們將值儲存在常規檔案 inode 的資料塊中。此“EA inode”僅從擴充套件屬性名稱索引連結,並且不得出現在目錄條目中。inode 的 i_atime 欄位用於儲存 xattr 值的校驗和;i_ctime/i_version 儲存 64 位引用計數,這使得大型 xattr 值可以在多個擁有 inode 之間共享。為了向後相容此特性的舊版本,i_mtime/i_generation 可能 儲存對 一個 擁有 inode 的 inode 號和 i_generation 的反向引用(在 EA inode 未被多個 inode 引用的情況下),以驗證訪問的 EA inode 是否正確。
2.12. Verity 檔案¶
ext4 支援 fs-verity,這是一項檔案系統特性,為單個只讀檔案提供基於 Merkle 樹的雜湊。fs-verity 的大部分內容與所有支援它的檔案系統通用;請參閱 Documentation/filesystems/fsverity.rst 以獲取 fs-verity 文件。然而,verity 元資料的磁碟佈局是檔案系統特有的。在 ext4 上,verity 元資料儲存在檔案資料本身之後,格式如下:
填充零到下一個 65536 位元組邊界。此填充實際上不必在磁碟上分配,即它可能是一個空洞。
Merkle 樹,如 Documentation/filesystems/fsverity.rst 中所述,樹級別按從根到葉的順序儲存,每個級別內的樹塊按其自然順序儲存。
填充零到下一個檔案系統塊邊界。
verity 描述符,如 Documentation/filesystems/fsverity.rst 中所述,可選擇附加簽名 blob。
填充零到距離檔案系統塊邊界前 4 位元組的下一個偏移量。
verity 描述符的大小(以位元組為單位),表示為 4 位元組小端整數。
Verity inode 設定了 EXT4_VERITY_FL,並且它們必須使用 extents,即 EXT4_EXTENTS_FL 必須設定,EXT4_INLINE_DATA_FL 必須清除。它們可以設定 EXT4_ENCRYPT_FL,在這種情況下,verity 元資料和資料本身都會被加密。
Verity 檔案不能在 verity 元資料結束之後分配塊。
Verity 和 DAX 不相容,嘗試在一個檔案上同時設定這兩個標誌將失敗。
2.13. 原子塊寫入¶
2.13.1. 引言¶
原子(非撕裂)塊寫入確保要麼整個寫入都提交到磁碟,要麼都不提交。這可以防止在斷電或系統崩潰期間發生“撕裂寫入”。ext4 檔案系統支援對具有 extents 的常規檔案進行原子寫入(僅限直接 I/O),前提是底層儲存裝置支援硬體原子寫入。這透過以下兩種方式支援:
單檔案系統塊原子寫入:EXT4 從 v6.13 開始支援單檔案系統塊的原子寫入操作。在此模式下,原子寫入單元的最小和最大大小均設定為檔案系統塊大小。例如,在 64KB 頁面大小的系統上,使用 16KB 檔案系統塊大小進行 16KB 的原子寫入是可能的。
使用 Bigalloc 的多檔案系統塊原子寫入:EXT4 現在還支援使用名為 bigalloc 的特性進行跨多個檔案系統塊的原子寫入。原子寫入單元的最小和最大大小由檔案系統塊大小和簇大小決定,具體取決於底層裝置支援的原子寫入單元限制。
2.13.2. 要求¶
ext4 中原子寫入的基本要求:
必須啟用 extents 特性(ext4 預設啟用)
底層塊裝置必須支援原子寫入
對於單檔案系統塊原子寫入:
具有適當塊大小(最大為頁面大小)的檔案系統
對於多檔案系統塊原子寫入:
必須啟用 bigalloc 特性
必須正確配置簇大小
注意:EXT4 不支援基於軟體或 COW 的原子寫入,這意味著 ext4 上的原子寫入僅在底層儲存裝置支援的情況下才受支援。
2.13.3. 多檔案系統塊實現細節¶
bigalloc 特性將 ext4 更改為以多個檔案系統塊(也稱為簇)為單位進行分配。使用 bigalloc,塊點陣圖中的每個位都表示一個簇(2 的冪次方的塊數),而不是單個檔案系統塊。EXT4 支援使用 bigalloc 進行多檔案系統塊原子寫入,但受以下約束。最小原子寫入大小是檔案系統塊大小和最小硬體原子寫入單位中的較大者;最大原子寫入大小是 bigalloc 簇大小和最大硬體原子寫入單位中的較小者。Bigalloc 確保所有分配都與簇大小對齊,如果分割槽/邏輯卷的起始本身正確對齊,則滿足硬體裝置的 LBA 對齊要求。
以下是 bigalloc 中用於原子寫入的塊分配策略:
對於具有完全對映範圍的區域,無需額外工作
對於追加寫入,分配一個新的對映範圍
對於完全是空洞的區域,建立未寫入的範圍
對於大型未寫入範圍,該範圍會被拆分為兩個適當請求大小的未寫入範圍
對於混合對映區域(空洞、未寫入範圍或已對映範圍的組合),
ext4_map_blocks()會在一個迴圈中以 EXT4_GET_BLOCKS_ZERO 標誌呼叫,透過向其寫入零並將範圍內的任何未寫入範圍轉換為已寫入,從而將該區域轉換為單個連續對映範圍。
注意:在單個連續的底層範圍上寫入,無論是已對映還是未寫入,本身都沒有問題。然而,在執行原子寫入時,必須避免寫入混合對映區域(即包含已對映和未寫入範圍組合的區域)。
原因是,透過 pwritev2() 並帶有 RWF_ATOMIC 標誌發出的原子寫入,要求要麼所有資料都寫入,要麼都不寫入。在寫入操作期間發生系統崩潰或意外斷電的情況下,受影響的區域(稍後讀取時)必須反映完整的舊資料或完整的最新資料,但絕不能是兩者的混合。
為了強制執行此保證,我們確保在寫入任何資料之前,寫入目標由單個連續的範圍支援。這至關重要,因為 ext4 會將未寫入範圍轉換為已寫入範圍的操作推遲到 I/O 完成路徑(通常在 ->end_io() 中)。如果允許寫入操作在混合對映區域(包含已對映和未寫入範圍)上進行,並且在寫入過程中發生故障,則系統在重新啟動後可能會觀察到部分更新的區域,即已對映區域上的新資料,以及從未標記為已寫入的未寫入範圍上的陳舊(舊)資料。這違反了原子性或撕裂寫入防止保證。
為防止此類撕裂寫入,ext4 透過 ext4_map_blocks_atomic() 在 ext4_iomap_alloc 中主動為整個請求區域分配單個連續範圍。如果分配是在混合對映上完成的,EXT4 還會強制提交當前的日誌事務。這確保了在執行實際寫入 I/O 之前,此範圍內的任何待處理元資料更新(例如未寫入到已寫入範圍的轉換)與檔案資料塊處於一致狀態。如果提交失敗,則必須中止整個 I/O,以防止任何可能的撕裂寫入。只有在此步驟之後,實際資料寫入操作才由 iomap 執行。
2.13.4. 處理跨葉塊的拆分範圍¶
存在一種特殊邊緣情況,即我們有邏輯上和物理上連續的範圍,但它們儲存在磁碟範圍樹的不同葉節點中。這發生的原因是磁碟範圍樹的合併只發生在葉塊內,除了兩級樹可以完全合併並摺疊到 inode 中的情況。如果存在這種佈局,並且在最壞的情況下,由於記憶體壓力導致範圍狀態快取條目被回收,ext4_map_blocks() 可能永遠不會為這些拆分的葉範圍返回單個連續範圍。
為解決此邊緣情況,添加了一個新的獲取塊標誌 EXT4_GET_BLOCKS_QUERY_LEAF_BLOCKS flag 以增強 ext4_map_query_blocks() 的查詢行為。
這個新的獲取塊標誌允許 ext4_map_blocks() 首先檢查範圍狀態快取中是否存在完整範圍的條目。如果不存在,它會使用 ext4_map_query_blocks() 查詢磁碟範圍樹。如果找到的範圍位於葉節點的末尾,它會探測下一個邏輯塊 (lblk) 以檢測相鄰葉中的連續範圍。
目前,為了保持效率,只查詢一個額外的葉塊,因為原子寫入通常受限於較小的尺寸(例如 [塊大小, 簇大小])。
2.13.5. 處理日誌事務¶
為支援多檔案系統塊原子寫入,我們確保在以下期間保留足夠的日誌信用:
在
ext4_iomap_alloc()中的塊分配時間。我們首先查詢底層請求範圍內是否存在混合對映。如果存在,則我們最多保留m_len的信用,假設每個交替塊都可以是一個未寫入範圍後跟一個空洞。在呼叫
->end_io()期間,我們確保啟動單個事務以進行未寫入到已寫入的轉換。轉換迴圈主要僅用於處理跨葉塊的拆分範圍。
2.14. 如何操作¶
2.14.1. 建立支援原子寫入的檔案系統¶
首先檢查塊裝置支援的原子寫入單位。詳見 硬體支援。
對於具有較大塊大小(在塊大小 < 頁面大小的系統上)的單檔案系統塊原子寫入
# Create an ext4 filesystem with a 16KB block size
# (requires page size >= 16KB)
mkfs.ext4 -b 16384 /dev/device
對於使用 bigalloc 的多檔案系統塊原子寫入
# Create an ext4 filesystem with bigalloc and 64KB cluster size
mkfs.ext4 -F -O bigalloc -b 4096 -C 65536 /dev/device
其中 -b 指定塊大小,-C 指定簇大小(以位元組為單位),-O bigalloc 啟用 bigalloc 特性。
2.14.2. 應用程式介面¶
應用程式可以使用帶 RWF_ATOMIC 標誌的 pwritev2() 系統呼叫執行原子寫入
pwritev2(fd, iov, iovcnt, offset, RWF_ATOMIC);
寫入必須與檔案系統的塊大小對齊,且不能超過檔案系統的最大原子寫入單元大小。詳見 generic_atomic_write_valid()。
帶 STATX_WRITE_ATOMIC 標誌的 statx() 系統呼叫可提供以下詳細資訊:
stx_atomic_write_unit_min:原子寫入請求的最小大小。
stx_atomic_write_unit_max:原子寫入請求的最大大小。
stx_atomic_write_segments_max:分段上限。可以聚合到一個寫入操作中的獨立記憶體緩衝區數量(例如,IOV_ITER 的 iovcnt 引數)。目前,此值始終設定為一。
如果支援原子寫入,statx->attributes 中的 STATX_ATTR_WRITE_ATOMIC 標誌將被設定。
2.15. 硬體支援¶
底層儲存裝置必須支援原子寫入操作。現代 NVMe 和 SCSI 裝置通常提供此功能。Linux 核心透過 sysfs 暴露此資訊:
/sys/block/<device>/queue/atomic_write_unit_min- 最小原子寫入大小/sys/block/<device>/queue/atomic_write_unit_max- 最大原子寫入大小
這些屬性的非零值表示裝置支援原子寫入。
2.16. 另請參閱¶
大分配 (Bigalloc) - 關於 bigalloc 特性的文件
塊和 inode 分配策略 - 關於 ext4 中塊分配的文件
6.13 版本中對原子塊寫入的支援:https://lwn.net/Articles/1009298/