Btrfs

Btrfs（B-tree檔案系統，通常念成Butter FS，Better FS或B-tree FS），一種支持寫入時複製（COW）的文件系統，運行在Linux作業系統，採用GPL授權。Oracle於2007年對外宣布這項計劃，並釋出原始碼，在2014年8月釋出穩定版。目標是取代當時在Linux上廣泛使用的ext3文件系統，改善文件系統的限制，增加單個文件大小上限支持，總文件系統大小支持以及文件檢查支持。Btrfs還加入了可寫快照（writable snapshots）、快照的快照（snapshots of snapshots）、內建磁碟陣列（RAID），以及子卷（subvolumes）等高級文件系統功能。在宣傳上，Btrfs的特點是「容錯、修復及易於管理」。

## 歷史

Btrfs的核心數據結構——寫時複製B樹——最初是由IBM研究員Ohad Rodeh在2007年USENIX會議上提出的。Chris Mason當時是SUSE公司的ReiserFS工程師，後來加入了Oracle，並開始開發一種基於B樹的新文件系統。

2008年，ext3和ext4文件系統的主要開發者曹子德表示，雖然ext4有改進的功能，但它不是一個重大的進步；它使用了舊技術，是一個權宜之計。曹子德說，Btrfs是一個更好的方向，因為「它在可擴展性、可靠性和易管理性方面提供了改進」。Btrfs也有「一些與reiser3/4相同的設計思想」。

Btrfs 1.0版本具有最終確定的磁碟格式，於2008年底發布，並於2009年被接受進入Linux內核主線。幾個Linux發行版開始在安裝過程中提供Btrfs作為根文件系統的實驗性選擇。

2011年7月，Btrfs自動碎片整理和擦除功能被合併到Linux內核主線的3.0版本中。除了Oracle的Mason外，富士通的Miao Xie也貢獻了性能改進。2012年6月，Chris Mason離開Oracle加入Fusion-io，一年後又與Josef Bacik一起加入Facebook。在這兩家公司工作期間，Mason繼續他對Btrfs的工作。

2012年，兩個Linux發行版將Btrfs從實驗性狀態轉變為生產或支持狀態：3月份的Oracle Linux，8月份的SUSE Linux Enterprise。

2015年，Btrfs被採用為SUSE Linux Enterprise Server（SLE）12的默認文件系統。

2017年8月，紅帽公司在Red Hat Enterprise Linux（RHEL）7.4的發布說明中宣布，不再計劃將Btrfs轉變為一個完全支持的功能（它自RHEL 6 beta以來一直作為「技術預覽」包含在內），並指出它將在RHEL 7發布系列中保持可用。2019年5月，Btrfs從RHEL 8中移除。RHEL從RHEL 6中的ext4轉移到RHEL 7中的XFS。

2020年，Btrfs被選為Fedora 33桌面變體的默認文件系統。

## 特性

### 特性列表

#### 已實現

- 聯機碎片整理
- 聯機卷生長和收縮
- 聯機塊設備增加和刪除
- 聯機負載均衡（塊設備間的對象移動以達到平衡）
- 文件系統級的鏡像（類RAID-1）、條帶（類RAID-0）
- 子卷（一個或多個單獨可掛載基於每個物流分區）
- 透明壓縮（目前支持zlib、LZO和ZSTD (從 4.14 開始支援)）
- 快照（只讀和可寫，寫複製，子卷複製）
- 文件克隆
- 數據和元數據的校驗和（目前是CRC-32C）
- 就地轉換（帶回滾）ext3/4與ReiserFS
- 只讀存儲的聯合掛載，稱為文件系統播種（只讀存儲用作可寫 Btrfs 的寫時複製支持）
- 使用者定義的事務
- 塊丟棄支持
- 發送/接收（將快照之間的差異保存為二進位流）
- 帶外數據去重（需要使用者空間工具）
- 能夠處理交換文件和交換分區

#### 已實現但不建議在生產環境中使用

- 分層的每個子卷配額
- RAID 5，RAID 6

#### 計劃但尚未實現

- 帶內數據去重
- 在線文件系統檢查
- 最多六個奇偶校驗設備的 RAID，超越了 RAID 5 和 RAID 6 的可靠性
- 對象級 RAID 0，RAID 1 和 RAID 10
- 透明加密
- 持久讀寫緩存（L2ARC + ZIL，lvmcache 等）

### 克隆

Btrfs 提供了一個克隆操作，可以原子地創建一個文件的寫時複製快照。這樣的克隆文件有時被稱為 `reflinks`，因為它們與擬議的相關 Linux 內核系統調用有關。

在克隆時，文件系統不會創建一個指向現有 `inode` 的新連結；相反，它會創建一個新的 `inode`，最初與原始文件共享相同的磁碟塊。因此，克隆只能在同一個 Btrfs 文件系統的邊界內工作，但是在某些情況下，自 Linux 內核 3.6 版本起，它可能會跨越子卷的邊界。實際的數據塊不會被複製；同時，由於 Btrfs 的寫時複製（CoW）特性，對任何克隆文件的修改都不會在原始文件中可見，反之亦然。

克隆不應與硬連結混淆，硬連結是將多個文件名與單個文件關聯的目錄項。雖然硬連結可以被認為是同一文件的不同名稱，但 Btrfs 中的克隆提供了最初共享所有磁碟塊的獨立文件。

GNU coreutils 自 7.5 版本支持了這個 Btrfs 特性，通過 `cp` 命令的 `--reflink` 選項可以使用克隆功能。

除了數據克隆（FICLONE），Btrfs 還支持通過 FIDEDUPERANGE 進行帶外去重。這個功能允許兩個具有（甚至部分）相同數據的文件共享存儲空間。

### 子卷和快照

<image>

Btrfs子卷可視為一個單獨的POSIX文件命名空間，向`mount`命令傳遞`subvol`或`subvolid`選項能夠單獨掛載相對應的子卷。其他子卷也可以通過掛載頂層子捲來訪問，此時其他子卷作為頂層子卷的子目錄供使用者訪問。

子卷可以在文件系統層次結構中的任何位置創建，也可以嵌套。嵌套的子卷在其父子卷中顯示為子目錄，類似於頂層子卷將其他子卷顯示為子目錄的方式。刪除一個子卷前，需要確保該子卷內不存在嵌套的子卷；因此，不能刪除頂層子卷。

Btrfs文件系統有一個默認子卷，最初設置為頂層子卷，在傳遞子卷選擇選項時默認掛載。默認子卷可以根據需要更改。

Btrfs快照是一個與其他子卷共享數據（和元數據）的子卷，使用Btrfs的寫時複製實現，對快照的修改在原始子卷中不可見。可寫的快照可以視為原始文件系統的一個副本。例如，要回滾到一個快照，可以卸載修改過的原始子卷，並將快照掛載到它的位置來實現。此時，也可以刪除原始子卷。

Btrfs的寫時複製（CoW）特性意味著快照可以快速創建，同時剛剛創建時僅消耗很少的磁碟空間。由於快照是一個子卷，因此也可以創建嵌套的快照。對一個子卷進行快照不是一個遞歸的過程；因此，如果創建了一個子卷的快照，該子卷包含的每個嵌套子卷都會映射到同名的空目錄中。

不能對一個目錄進行快照，只有子卷才能有快照。然而，有一種涉及跨越子卷的reflinks的解決方案：創建一個新的子卷，包含指向目標目錄內容的跨越子卷的reflinks。有了這個，就可以創建這個新卷的快照。

Btrfs中的子卷與傳統的LVM邏輯卷非常不同。在LVM中，邏輯卷是一個單獨的塊設備，而Btrfs子卷更類似於POSIX命名空間。原始或副本中的任何一個在同一台電腦上掛載時，對btrfs進行dd或LVM快照會導致數據丟失。

### 子卷發送與接收

給定任意一對子卷（或快照），Btrfs可以在它們之間生成二進位差異（通過使用`btrfs send`命令），稍後可以重放（通過使用`btrfs receive`），可以接收到另一個Btrfs文件系統上。發送-接收功能實際上創建（並應用）了將一個子卷轉換為另一個子卷所需的一組數據修改。

發送/接收功能可以與定期安排的快照一起使用，用於實現文件系統複製的簡單形式，或用於執行增量備份。

### 配額組

<image>

配額組可以對一個子卷或快照可能消耗的空間設定上限。新的快照最初不消耗配額，因為它的數據與其父卷共享，但之後會因為新文件和對現有文件的寫時複製操作而產生空間上的開銷。當配額功能啟用時，每個新的子卷或快照都會自動創建一個配額組。這些初始的配額組是可以分組（使用`btrfs qgroup`命令）成層次結構來實現配額池的構建塊。

配額組只適用於子卷和快照，而不能對單個子目錄、使用者或使用者組強制執行配額。然而，可以通過使用不同的子捲來實現所有需要強制執行配額的使用者或使用者組。

### 原地轉換ext2/3/4和ReiserFS

由於在固定位置的元數據非常少，Btrfs可以適應後端存儲設備的各種的空間布局。`btrfs-convert`工具可以通過在其未分配空間中嵌套等效的Btrfs元數據——同時保留原始文件系統的未修改副本，即原地轉換一個ext2/3/4或ReiserFS文件系統。

轉換需要創建整個ext2/3/4元數據的副本，而Btrfs文件只是指向與ext2/3/4文件使用的相同的塊。這使得在不可逆轉換之前，兩個文件系統共享大部分塊。由於Btrfs的寫時複製特性，在所有文件修改過程中，原始版本的文件數據塊都保持不變。在不可逆轉換之前，只有在ext2/3/4中標記為空閒的塊才用於保存新的Btrfs修改，這意味著轉換可以隨時撤銷轉換（但撤銷轉換會擦除在轉換為Btrfs後所做的任何更改）。

所有轉換後的文件都在Btrfs的默認子卷中。轉換工具會在一個單獨的子卷中創建包含對原始ext2/3/4文件系統所有引用的稀疏文件；這一文件可以作為一個只讀磁碟映像單獨掛載，允許同時訪問原始文件系統和轉換後的文件系統。刪除這個稀疏文件會釋放原有文件系統額外占用的空間，並使轉換變得不可逆。

在主線Linux內核的4.x版本中，就地ext3/4缺乏使用與測試，不能保證穩定性。然而，該功能在2016年的`btrfs-progs` 4.6從頭重寫。Btrfs的就地文件系統轉換功能自此宣布穩定。

就地從ReiserFS轉換在2017年9月引入到Linux 4.13內核中。

### 聯合掛載/種子設備

在創建一個新的Btrfs時，可以使用一個已存在的Btrfs作為一個只讀的「種子」文件系統。新的文件系統將作為種子上的一個寫時複製覆蓋層，作為一種聯合掛載的形式。種子可以稍後從Btrfs中分離，在此之前，重平衡器將簡單地複製任何仍然被新文件系統引用的種子數據，然後分離。Btrfs的主要貢獻者Mason認為這可能對Live CD安裝程序有用，它可以從光碟上的一個只讀Btrfs種子啟動，在使用者繼續工作的同時，在後台將自己重平衡到安裝磁碟上的目標分區，然後彈出光碟以完成安裝而不需要重新啟動。

### 加密

在2009年的採訪中，Chris Mason表示計劃為Btrfs增加加密支持。目前，將加密與Btrfs結合使用的一種解決方案是使用諸如dm-crypt / LUKS之類的全盤加密機制在底層設備上，並在該層之上創建Btrfs文件系統。

開發人員正在努力添加類似HMAC（SHA256）的鍵控哈希。

### 檢查和恢復

Unix系統傳統上使用fsck程序來檢查和修復文件系統。這個功能是通過`btrfs check`程序實現的。從4.0版本開始，這個功能被認為是相對穩定的。然而，截至2023年6月，btrfs文檔建議只有在「開發者或經驗豐富的使用者」建議時才使用其`–repair`選項。截至2023年6月，SLE文檔建議使用一個Live CD，執行一個備份，並只在最後的手段時使用修複選項。

還有另一個工具`btrfs-restore`，可以用於從一個不可掛載的文件系統中恢復文件，而不修改損壞的文件系統本身。

在正常使用中，Btrfs基本上是自我修復的，並且可以在掛載時從損壞的根樹結構中恢復，這是因為默認每30秒向永久存儲刷新數據。因此，孤立的錯誤會導致下一次掛載時最多30秒的文件系統更改丟失。這個周期可以通過在`commit`掛載選項中指定所需的值來更改。（以秒為單位）

## 設計

Ohad Rodeh在2007年的USENIX提出的原始方案指出，廣泛用作資料庫的磁碟數據結構的B+樹不能有效地實現基於寫時複製的快照，因為它的葉子節點是連接在一起的：如果一個葉子被寫時複製，它的兄弟節點和父節點也必須如此，以及它們遞歸的兄弟和父節點，直到整個樹被複製。他建議使用一個修改過的B樹（沒有葉子連結），並且每個樹節點都有一個引用計數，但是存儲在一個專門的自由映射結構中，並且對樹的平衡演算法進行了一定的放鬆，使它們對寫時複製友好。結果將是一個適合高性能對象存儲的數據結構，可以執行寫時複製快照，同時保持良好的並發性。

同年，在Oracle工作的Chris Mason開始了一個能夠使用這種數據結構的快照能力文件系統的工作，幾乎完全採用這種數據結構——不僅用於元數據和文件數據，而且遞歸地用於跟蹤樹本身的空間分配。這允許所有遍歷和修改都通過同一份代碼，只需要實現一次諸如寫時複製、校驗和和鏡像等特性，就可以在整個文件系統的各個地方使用。

Btrfs包含樹組成的幾層結構，所有樹都使用相同的B樹實現。樹存儲了按照136位鍵排序的通用項。鍵中最高的64位是一個唯一的「對象id」。中間8位是一個項類型欄位，用於硬編碼到代碼中作為樹查找中的項過濾器。「對象」可以有多個類型的多個項。剩下的64位以類型特定的方式使用。因此，同一對象的項會在樹中彼此相鄰，按類型分組。通過選擇某些鍵值，對象可以進一步按照類型排列。

內部樹節點只是鍵-指針對的平面列表，其中指針是子節點的邏輯塊號。葉節點包含打包到節點前面的項鍵和打包到節點末尾的項數據，在葉填充時兩者向彼此增長。

### 文件系統樹

在每個目錄中，目錄條目以「目錄項」的形式出現，它們的鍵值的最低位是它們文件名的CRC32C哈希。它們的數據是一個「位置鍵」，或者是它指向的inode項的鍵。目錄項一起可以作為路逕到inode查找的索引，但不用於迭代，因為它們按照它們的哈希排序，有效地隨機排列它們。這意味著在一個大目錄中迭代和打開文件的使用者應用程式將產生更多的磁碟尋道，因此在非相鄰文件之間尋道——這是其他文件系統中具有哈希排序目錄的顯著性能損耗，例如ReiserFS， ext3（啟用了Htree索引）和ext4，它們都有微型加密算法哈希過的文件名。為了避免這個問題，Btrfs每個目錄條目都有一個"目錄索引項"，其項的鍵值被設置為一個每個目錄遞增的計數器。因此，對這些索引項的迭代大致按照存儲在磁碟上的相同順序返回條目。

在多個目錄中具有硬連結的文件有多個引用項，每個父目錄一個。在同一目錄中具有多個硬連結的文件將所有連結的文件名打包到同一個引用項中。這是一個設計缺陷，它將同一目錄中的硬連結數量限制為多少個可以放入單個樹塊中。（在默認塊大小為4 KiB、平均文件名長度為8位元組和每個文件名頭部為4位元組的情況下，這將少於350個。）觀察到大量使用同一目錄中多個硬連結的應用程式，在這個限制下會失敗，例如git、GNUS、GMame和BackupPC。

這個限制最終被移除（截至2012年10月已經合併等待在Linux 3.7中發布）通過引入溢出的"擴展引用項"來保存不適合的硬連結文件名。

#### 區段

文件數據保存在樹外的「區段」中，它們是連續的磁碟數據塊。區段塊默認大小為4 KiB，沒有頭部，只包含（可能壓縮的）文件數據。在壓縮的區段中，單個塊不是單獨壓縮的；相反，壓縮流跨越整個區段。

文件有「區段數據項」來跟蹤保存其內容的區段。項的鍵值是區段的起始字節偏移量。這使得在具有多個區段的大文件中進行有效的尋找，因為任何給定文件偏移量的正確區段可以用一個樹查找來計算。

快照和克隆文件共享區段。當一個大的這樣的區段的一小部分被覆蓋時，結果的寫時複製可能創建三個新的區段：一個小的包含被覆蓋的數據，和兩個大的包含覆蓋兩邊未修改的數據。為了避免重寫未修改的數據，寫時複製可能會創建「書籤區段」，或者只是現有區段的切片。區段數據項允許這樣做，通過包含一個到它們正在跟蹤的區段的偏移量：書籤項是那些具有非零偏移量的項。

### 區段分配樹

「區段分配樹」充當文件系統的分配映射。與其他樹不同，這個樹中的項沒有對象id。它們表示空間區域：它們的鍵值保存了它們所代表的區域的起始偏移量和長度。

文件系統將其分配的空間劃分為「塊組」，它們是可變大小的分配區域，交替地偏好元數據區段（樹節點）和數據區段（文件內容）。數據和元數據塊組的默認比例是1:2。它們旨在使用奧爾洛夫塊分配器的概念來分配相關文件，並通過在組之間留下空閒空間來抵抗碎片化。（然而，Ext3塊組有固定的位置，是根據文件系統的大小計算出來的，而Btrfs中的塊組是動態的，並根據需要創建）每個塊組都與一個「塊組項「相關聯。文件系統樹中的inode項包含對它們當前塊組的引用。

「區段項」包含一個到占用該區段的樹節點或文件的反向引用。如果區段在快照之間共享，可能有多個反向引用。如果有太多的反向引用不能適合於項中，它們會溢出到單獨的「區段數據引用項「中。反過來，樹節點也有對它們所包含的樹的反向引用。這使得通過對一對括住該區域的偏移量進行B樹範圍查找，然後跟隨反向引用，可以找到任何空間區域中的哪些區段或樹節點。對於重新定位數據，這允許從重新定位的塊進行有效的向上遍歷，快速找到並修復對這些塊的所有向下引用，而不必掃描整個文件系統。這反過來又允許文件系統有效地在線縮小、遷移和碎片整理其存儲。

區段分配樹，與文件系統中的所有其他樹一樣，是寫時複製的。寫入文件系統可能會導致一個級聯，其中改變了樹節點和文件數據導致新的區段被分配，導致區段樹本身發生變化。為了避免創建一個反饋迴路，仍然在記憶體中但尚未提交到磁碟的區段樹節點可以就地更新，以反映新複製的寫時複製區段。

從理論上講，由於區段分配樹充當了一個B樹版本的BSP樹，因此不需要傳統的可用空間位圖。但實際上，為了加速分配，使用了頁大小位圖的紅黑樹。這些位圖被持久化到磁碟（從Linux 2.6.37開始，通過`space_cache`掛載選項）作為特殊的區段，它們不受校驗和和寫時複製的影響。

### 校驗和樹和擦除

對數據和元數據都計算CRC-32C校驗和，並存儲為「校驗和樹」中的「校驗和項」。元數據校驗和有256位的空間，而數據校驗和最多可以有一個完整的節點（大約4 KB或更多）。Btrfs有為未來版本的文件系統添加額外的校驗和算法的計劃。

每個連續的已分配塊都有一個校驗和項，每個塊的校驗和緊密地打包到項數據中。如果有太多的校驗和不能適合，它們會溢出到一個新葉子中的另一個校驗和項中。如果文件系統在讀取一個塊時檢測到一個校驗和不匹配，在內部鏡像或RAID技術正在使用的情況下，它首先嘗試從另一個設備 – 獲取（或創建）這個塊的一個好的副本。

Btrfs可以通過觸發一個在後台執行的文件系統擦除作業來啟動對整個文件系統的在線檢查。擦除作業掃描整個文件系統的完整性，並自動嘗試報告和修復它在沿途發現的任何壞塊。

### 塊和設備樹

塊設備被劃分為1 GiB用於數據的物理塊，和256 MiB用於元數據的物理塊。多個設備上的物理塊可以鏡像或條帶化成一個單一的「邏輯塊」。這些邏輯塊被組合成一個單一的邏輯地址空間，供文件系統的其餘部分使用。

「塊樹」通過將其中的每個設備存儲為一個「設備項」和邏輯塊作為「塊映射項」來跟蹤這一點，提供了從邏輯到物理地址的正向映射。塊映射項可以是以下幾種類型之一：

single : 1個邏輯到1個物理塊

dup : 1個邏輯塊到1個塊設備上的2個物理塊

raid0 : N個邏輯塊到N≥2個設備上的N≥2個物理塊

raid1 : 1個邏輯塊到N≥2個設備中的2個物理塊，與傳統的RAID 1不同，它有N個物理塊

raid1c3 : 1個邏輯塊到N≥3個設備中的3個物理塊 ;

raid1c4 : 1個邏輯塊到N≥4個設備中的4個物理塊 ;

raid5 : N（對於N≥2）個邏輯塊到N+1個設備上的N+1個物理塊，其中1個物理塊用作奇偶校驗 ;

raid6 : N（對於N≥2）個邏輯塊到N+2個設備上的N+2個物理塊，其中2個物理塊用作奇偶校驗 「N」是在分配塊時仍然有空閒空間的塊設備的數量。如果N對於選擇的鏡像/映射不夠大，那麼文件系統就有效地沒有空間了。

### 重定位樹

碎片整理、縮小和重新平衡操作需要重新定位區段。然而，對正在重新定位的區段進行簡單的寫時複製將破壞快照之間的共享並消耗磁碟空間。為了保持共享，使用一個更新和交換演算法，一個特殊的重定位樹作為受影響的元數據的臨時空間。首先將要重新定位的區段複製到它的目的地。然後，通過沿著受影響的子卷的文件系統樹向上跟隨反向引用，逐漸更新指向舊區段的元數據，使其指向新的區段；任何新更新的項都存儲在重定位樹中。一旦更新完成，重定位樹中的項與受影響子卷中的對應項交換，然後丟棄重定位樹。

### 超級塊

文件系統中的所有樹——包括塊樹本身——都存儲在塊中，這會產生一個引導問題，當掛載文件系統時。為了引導到一個掛載點，塊樹和根樹所屬的塊的物理地址列表存儲在超級塊中。

超級塊鏡像保存在固定位置：每個塊設備64 KiB處，以及64 MiB、256 GiB和1 PiB處的額外副本。當更新一個超級塊鏡像時，它的生成號遞增。在掛載時，使用生成號最高的副本。除了SSD模式，所有超級塊鏡像都同時更新；而在SSD模式下，為了提供一定程度的磨損平衡，超級塊鏡像之間交替更新。

## 基本資料

- 開發者：SUSE、Meta、威騰電子、甲骨文公司、富士通、Red Hat
- 發布：Linux
- 發布：穩定版本：6.4，2023年6月、不穩定版本：6.4，2023年6月
- 目錄內容：B樹
- 文件分配：extents
- 最長文件名：255位元組
- 最大文件數量：264（每個子卷）
- 最大卷容量：16 EiB
- 最大文件尺寸：16 EiB
- 文件名字符集：除`'/'`和`NUL`（`'\0'`）以外的所有字符
- 日期記錄：內容更改時間（mtime），屬性更改時間（ctime），訪問時間（atime）
- 日期解析度：納秒
- 屬性：POSIX，擴展文件屬性
- 文件系統權限：POSIX，訪問控制表
- 透明壓縮：是（zlib、LZO和ZSTD（從Linux 4.14開始））
- 透明加密：否（計劃支持）
- single_instance_storage：是（計劃支持，通過補丁支持）
- 作業系統：Linux , ReactOS