Btrfs 詳解:子卷
這篇文章是《Btrfs 詳解》系列文章中的一篇。從 Fedora Linux 33 開始,Btrfs 就是 Fedora Workstation 和 Fedora Silverblue 的默認文件系統。
以防你忘記,這是系列文章中的前一篇:Btrfs 詳解:基礎概念。
簡介
子卷 允許將一個 Btrfs 文件系統劃分成多個獨立的子文件系統。這意味著你可以從 Btrfs 文件系統掛載子卷,就好像它們是獨立的文件系統。除此之外,例如,你還可以通過 限額組 (我們將在本系列的另一篇文章里介紹)定義子卷能夠佔據的最大空間,或者用子捲去包含或排除快照中的文件(我們會後面的文章中會講到)。自 Fedora Linux 33 後每個 Fedora Workstation 和 Fedora Silverblue 默認安裝過程中會利用子卷。在這篇文章中我們會介紹它是如何工作的。
下面你會找到很多關於子卷的例子。如果你想跟著操作,你必須擁有訪問某些 Btrfs 文件系統的許可權和 root 許可權。你可以通過下面命令來驗證你的 /home/
目錄是否是 Btrfs 。
$ findmnt -no FSTYPE /home
btrfs
這個命令會輸出你 /home/
目錄的文件系統名。如果它是 btrfs,那就可以了。讓我們創建一個新的目錄去做實驗:
$ mkdir ~/btrfs-subvolume-test
$ cd ~/btrfs-subvolume-test
在下面的文本中,你會看到很多像上面顯示的那樣的命令輸出框。請在閱讀/比較命令輸出時請記住,框中的內容在行末會被換行。這使得識別跨多行的長行變得困難,降低了可讀性。如果有疑問,試著調整瀏覽器窗口的大小,看看文本的變化!
創建和使用子卷
我們可以通過以下命令創建一個 Btrfs 子卷:
$ sudo btrfs subvolume create first
Create subvolume './first'
當我們檢查當前目錄,我們可以看到現在有一個名為 first
的新目錄。注意到下面輸出的第一個字元 d
:
$ ls -l
total 0
drwxr-xr-x. 1 root root 0 Oct 15 18:09 first
我們可以像常規目錄一樣操作它:我們可以重命名它,移動它,在裡面創建新文件和目錄,等等。注意到目錄屬於 root,所以我們必須以 root 身份去做這些事情。
如果它表現和看起來就像個目錄,那我們如何知道這是不是一個 Btrfs 子卷呢?我們可以使用 btrfs
工具去列出所有子卷:
$ sudo btrfs subvolume list .
ID 256 gen 30 top level 5 path home
ID 257 gen 30 top level 5 path root
ID 258 gen 25 top level 257 path root/var/lib/machines
ID 259 gen 29 top level 256 path hartan/btrfs-subvolume-test/first
如果你安裝的是最新的 Fedora Linux,且未修改過,你很可能會看到和上面一樣的輸出。我們會在之後檢查 home
和 root
,還有全部數字的含義。現在,我們看到在我們指定的路徑下有一個子卷。我們可以將輸出限制在我們當前位置下面的子卷:
$ sudo btrfs subvolume list -o .
ID 259 gen 29 top level 256 path home/hartan/btrfs-subvolume-test/first
讓我們重命名子卷:
$ sudo mv first second
$ sudo btrfs subvolume list -o .
ID 259 gen 29 top level 256 path home/hartan/btrfs-subvolume-test/second
我們還可以嵌套子卷:
$ sudo btrfs subvolume create second/third
Create subvolume 'second/third'
$ sudo btrfs subvolume list .
ID 256 gen 34 top level 5 path home
ID 257 gen 37 top level 5 path root
ID 258 gen 25 top level 257 path root/var/lib/machines
ID 259 gen 37 top level 256 path hartan/btrfs-subvolume-test/second
ID 260 gen 37 top level 259 path hartan/btrfs-subvolume-test/second/third
我們也可以移除子卷,就像移除目錄一樣:
$ sudo rm -r second/third
或者通過特殊的 Btrfs 命令:
$ sudo btrfs subvolume delete second
Delete subvolume (no-commit): '/home/hartan/btrfs-subvolume-test/second'
像單獨的文件系統一樣操作子卷
前面的簡介里說 Btrfs 子卷就好像單獨的文件系統。這意味著我們可以掛載子卷並且傳遞一些掛載選項給它。我們先創建一個小的目錄結構去更好的理解發生了什麼:
$ mkdir -p a a/1 a/1/b
$ sudo btrfs subvolume create a/2
Create subvolume 'a/2'
$ sudo touch a/1/c a/1/b/d a/2/e
這就是目錄結構的樣子:
$ tree
.
└── a
├── 1
│ ├── b
│ │ └── d
│ └── c
└── 2
└── e
4 directories, 3 files
驗證現在這裡有一個新的 Btrfs 子卷:
$ sudo btrfs subvolume list -o .
ID 261 gen 41 top level 256 path home/hartan/btrfs-subvolume-test/a/2
為了掛載子卷,我們必須知道 Btrfs 子卷所在的塊設備路徑。下面的命令會告訴我們:
$ findmnt -vno SOURCE /home/
/dev/vda3
現在我們掛載子卷。確保你將參數替換成你 PC 上的:
$ sudo mount -o subvol=home/hartan/btrfs-subvolume-test/a/2 /dev/vda3 a/1/b
觀察到我們使用 -o
參數去提供額外的選項去掛載程序。在這裡我們告訴它掛載在設備 /dev/vda3
上 btrfs 文件系統里名為 home/hartan/btrfs-subvolume-test/a/2
的子卷。這是 Btrfs 特有的選項,在其他文件系統里沒有的。
我們可以看到目錄結構變化了:
$ tree
.
└── a
├── 1
│ ├── b
│ │ └── e
│ └── c
└── 2
└── e
4 directories, 3 files
現在文件 e
出現了兩次, d
不見了。我們現在可以用兩個不同的路徑訪問相同的 Btrfs 子卷。在一個路徑的所有變化會被立刻反應在其他的位置:
$ sudo touch a/1/b/x
$ ls -lA a/2
total 0
-rw-r--r--. 1 root root 0 Oct 15 18:14 e
-rw-r--r--. 1 root root 0 Oct 15 18:16 x
讓我們嘗試更多的掛載選項。例如我們可以像這樣以只讀方式掛載子卷到 a/1/b
(插入你 PC 的參數):
$ sudo umount a/1/b
$ sudo mount -o subvol=home/hartan/btrfs-subvolume-test/a/2,ro /dev/vda3 a/1/b
我們和上面使用相同的命令,除了我們加上了 ro
在末尾。現在我們不能在這個掛載點上創建文件:
$ sudo touch a/1/b/y
touch: cannot touch 'a/1/b/y': Read-only file system
但直接訪問子卷仍然像之前一樣:
$ sudo touch a/2/y
$ tree
.
└── a
├── 1
│ ├── b
│ │ ├── e
│ │ ├── x
│ │ └── y
│ └── c
└── 2
├── e
├── x
└── y
4 directories, 7 files
在下一步之前不要忘記進行清理:
$ sudo rm -rf a
rm: cannot remove 'a/1/b/e': Read-only file system
rm: cannot remove 'a/1/b/x': Read-only file system
rm: cannot remove 'a/1/b/y': Read-only file system
天啊,發生了什麼?噢,因為我們在上面掛載只讀子卷,所以不能刪除它。從文件系統的角度來看,刪除是一種寫入操作:為了刪除 a/2/b/e
,我們從父目錄 a/1/b
的內容中刪除目錄項 e
。換句話來說,我們必須 寫入 a/1/b
去表明 e
不復存在。所以我們先卸載子卷,然後移除目錄:
$ sudo umount a/1/b
$ sudo rm -rf a
$ tree
.
0 directories, 0 files
子卷 ID
還記得 btrfs subvolume list
命令的第一次輸出嗎?那包含了很多數字,讓我們看看這些究竟什麼。我在這裡複製了輸出,以便再次查看:
ID 256 gen 30 top level 5 path home
ID 257 gen 30 top level 5 path root
ID 258 gen 25 top level 257 path root/var/lib/machines
ID 259 gen 29 top level 256 path hartan/btrfs-subvolume-test/first
我們看到有三列數字,每個前面有一些字母來描述它們的作用。第一列是子卷 ID 。子卷 ID 在 Btrfs 文件系統是唯一的,而且唯一地標識子卷。這意味著名為 home
的子卷也可以用它的 ID 256 來引用。之前的掛載命令是這樣寫的:
$ sudo mount -o subvol=hartan/...
另外一個完全合法的選擇是使用子卷 ID :
$ sudo mount -o subvolid=...
子卷 ID 從 256 開始,每創建一個子卷依次遞增 1 。但是在這裡有一個例外:文件系統的根的子卷名稱總是為 /
,並且子卷 ID 是 5 。沒錯,即使文件系統的根技術上也是一個子卷。這是不言而喻的,因此不會出現在 btrfs subvolume
的輸出列表裡。如果你沒有用 subvol
和 subvolid
參數去掛載一個 Btrfs 文件系統,subvolid=5
的頂級子卷就是默認的掛載對象。下面我們會看到一個想要顯式掛載文件系統根的例子。
第二列的數字是生成號,並且在每次 Btrfs 事務中遞增。這幾乎是一個內部的計數器,我們不會在這裡討論。
最後,第三列數字是 父 子卷的子卷 ID。在上面的輸出我們可以看到子卷 home
和 root
的父子卷 ID 都是 5。記住 ID 5 的特殊含義:這是文件系統的根。所以我們知道 home
和 root
都是頂級子卷的子卷。另一方面 hartan/btrfs-subvolume-test.first
是子卷 ID 256(也就是 home
)的子卷。
在下一節我們會看看子卷 root
和 home
是怎麼來的。
檢查 Fedora Linux 的默認子卷
當你從頭創建一個新的 Btrfs 文件系統,裡面是沒有子卷的(當然,除了頂級子卷)。所以 Fedora Linux 里的 home
和 root
子卷是哪裡來的?
它們是安裝程序在安裝時創建的。傳統的安裝經常會為 /
和 /home
目錄包含單獨的文件系統分區。在啟動時,它們通過恰當的掛載組成一個完整的文件系統。但這個方法有一個問題:除非你使用像 lvm 這樣的技術,想在將來改變分區的大小是非常難的。因而你可能出現 /
或 /home
用完空間的情況,然而還有很多其他沒被使用的分區和空間剩餘。
因為 Btrfs 子卷全都是相同文件系統的一部分,它們共享底層文件系統提供的空間。還記得我們在上面創建的子卷嗎?我們從未告訴 Btrfs 它們多大:一個子卷可以佔據文件系統擁有的全部空間,默認是不會阻止這種行為的。但是,我們 可以 通過 Btrfs 的 限額組 動態地約束其大小,同時也可以在運行時修改(我們將在後續的文章中了解如何做的)。
另外一個分離 /
和 /home
的優勢是我們可以分別進行 快照 。子卷是快照的邊界,對一個子卷的快照永遠不會包含該子卷下面的其他子卷的內容。快照的更多細節會在後續的文章中介紹。
理論已經足夠了!我們來看看這是怎麼回事。首先確保你的根文件系統類型是 Btrfs :
$ findmnt -no FSTYPE /
btrfs
然後我們獲取它所在的分區:
$ findmnt -vno SOURCE /
/dev/vda3
記住我們可以通過特殊的子卷 ID 5 掛載文件系統的根(適應文件系統分區!):
$ mkdir fedora-rootsubvol
$ sudo mount -o subvolid=5 /dev/vda3 ./fedora-rootsubvol
$ ls fedora-rootsubvol/
home root
而且還有 Fedora Linux 安裝的子卷!但 Fedora Linux 是如何知道子卷 root
屬於 /
,而 home
屬於 /home
的呢?
文件 /etc/fstab
包含了所謂的文件系統的靜態信息。簡而言之,在你系統啟動的時候會一行一行地讀取這個文件,然後掛載那裡列出的所有文件系統。在我的系統上,這個文件長這樣:
$ cat /etc/fstab
# [ ... ]
# /etc/fstab
# Created by anaconda on Sat Oct 15 12:01:57 2022
# [ ... ]
#
UUID=5e4e42bb-4f2f-4f0e-895f-d1a46ea47807 / btrfs subvol=root,compress=zstd:1 0 0
UUID=e3a798a8-b8f2-40ca-9da7-5e292a6412aa /boot ext4 defaults 1 2
UUID=5e4e42bb-4f2f-4f0e-895f-d1a46ea47807 /home btrfs subvol=home,compress=zstd:1 0 0
(注意上面的 「UUID」 開頭行的內容被換行成兩行)
每行開頭的 UUID
用於標識你系統上的硬碟和文件系統分區(大概相當於我在上面使用的 /dev/vda3
)。第二列是文件系統應該掛載在文件系統樹上的路徑。第三列是文件系統類型。我們可以看到 /
和 /home
都是 btrfs
類型,正如我們期望的那樣!最後,第四列是:這些是掛載選項,這裡說通過 subvol=root
選項去掛載 /
。這正是我們一直在 btrfs subvolume list /
里看到的輸出!
有了這些信息,我們可以重新構建創建這個文件系統項的 mount
命令
$ sudo mount -o subvol=root,compress=zstd:1 UUID=5e4e42bb-4f2f-4f0e-895f-d1a46ea47807 /
(再次,上面的 「UUID」 開頭行的內容被換行成兩行)
這就是 Fedora Linux 如何使用 Btrfs 子卷!如果你對好奇 Fedora Linux 為什麼選擇 Btrfs 作為默認的文件系統,請參閱下面鏈接的更改提議 [1] 。
Btrfs 子卷的更多內容
Btrfs 維基提供了關於子卷的更多信息,其中最重要的是可應用於 Btrfs 子卷的掛載選項。有些選項,比如 compress
只能應用到文件系統的層面,因而會影響一個 Btrfs 文件系統的所有子卷。你可以通過下面的鏈接找到entry [2] 。
如果你對哪些目錄是普通目錄和哪些是子卷有困惑,你可以對你的子卷採用特殊的命名約定。例如,你可以給子卷名加上 @
前綴去方便區分。
現在你知道子卷表現得就像文件系統,有人可能會問如何才能最好地將子卷放置在特定位置。比如你想要一個 Btrfs 子卷在 ~/games
下面,然而你的主目錄(~
)本身就是一個子卷,你該如何實現呢?鑒於上面的例子,你可以使用像 sudo btrfs subvolume create ~/games
的命令。這樣,你創建了所謂的 嵌套 子卷:在你的子卷 ~
里,有一個子卷 games
。這正是一種達成目的的方法。
其他有效的方法就是如同 Fedora 默認行為那樣:在根子卷下創建所有子卷(也就是它們的父子卷 ID 是 5 ),然後掛載它們到特定的位置。Btrfs 維基有這些方法的概述和對於各自文件系統管理影響的簡短討論 [3] 。
總結
在本文中,我們探索了 Btrfs 子卷,它們像是 Btrfs 文件系統內部的獨立的 Btrfs 文件系統。我們學習了如何創建、掛載和刪除子卷。最後,我們探討了 Fedora Linux 如何在我們完全沒有注意到的情況下使用子卷。
本系列的下一篇文章將討論:
- 快照 - 回到過去
- 壓縮 - 透明地節省存儲空間
- 配額組 - 限制文件系統大小
- RAID - 替代 mdadm 配置
如果你還想了解與 Btrfs 相關的其他主題,請查看 Btrfs 維基 [4] 和文檔 [5] 。不要忘記查看本系列的第一篇文章(如果你還沒有看過的話)!如果你認為本系列文章缺少了一些內容,請在下面的評論中告訴我們。再會!
參考資料
- https://fedoraproject.org/wiki/Changes/BtrfsByDefault#Benefit_to_Fedora ↩︎
- https://btrfs.readthedocs.io/en/latest/Subvolumes.html ↩︎
- https://btrfs.wiki.kernel.org/index.php/SysadminGuide#Layout ↩︎
- https://btrfs.wiki.kernel.org/index.php/Main_Page ↩︎
- https://btrfs.readthedocs.io/en/latest/Introduction.html ↩︎
(題圖:MJ/f047ea87-2490-40e5-9f91-d48d236675e5)
via: https://fedoramagazine.org/working-with-btrfs-subvolumes/
作者:Andreas Hartmann 選題:lujun9972 譯者:A2ureStone 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive