文件系統及硬鏈接
要說硬鏈接,最好從數據存儲的原理上說起,但知道基礎即可無需過於深入,我也提供了其他人的博客文章[1]供感興趣的訪客深入了解。
硬盤中的最小存儲單位:扇區、塊[2]
- 硬盤上最小的存儲單位是「扇區」(扇形的一段區域),這是物理上實際存在的;
- 過去生產的硬盤每個扇區大小通常是512B,現代生產的硬盤則在慢慢普及4KB(4096B),通常有「512e/4Kn」的標誌,代表硬盤物理扇區大小為4KB,但可以通過技術手段兼容只支持512B模式的舊系統;
- 每個扇區只能存儲一份文件,哪怕這份文件未超過扇區大小也會占據一個扇區的空間。
- 為了提高效率與便於管理,文件系統在扇區之上虛構了「塊」的概念,一個塊通常包括多個扇區(正整數倍個),文件系統以「塊」為基礎管理文件,「塊」並非物理上存在的,是邏輯上的最小存儲單位,也稱為「邏輯塊」;
- 「塊」的大小是在格式化硬盤時設置的,除了它是虛構產物外 ,與扇區具有相同的性質,每個塊只能存儲一份文件,哪怕這份文件未超過所設置的塊大小;
- 若創建1kb的文檔,實際文件體積及顯示的體積均為1kb,但實際占用4kb空間;
- 若創建4.1kb的文檔,文件體積為4.1kb,但占用2個快,及8kb空間。
- 所以若無特殊需要一般都設置為4096B(4KB),大多數現代文件系統亦用此作為默認大小,通常無需變動。但若某硬盤只有備份用途、以大體積的壓縮包或視頻為主,則可將塊大小設為1MB甚至更高,更少的塊意味着在管理塊及對應的扇區上花費更少的資源,每次 I/O 操作能讀寫更多數據意味着更少的尋址及讀寫次數,性能自然會提升。代價便是每個文件都最小占用1MB(所設置的塊大小)空間,如果這塊硬盤要用於其他用途,則不一定適用,更改塊大小又只能格式化硬盤。
- 「塊」的大小是在格式化硬盤時設置的,除了它是虛構產物外 ,與扇區具有相同的性質,每個塊只能存儲一份文件,哪怕這份文件未超過所設置的塊大小;
記錄數據塊的索引節點:inode[3]
數據存儲在文件系統虛擬的塊中,塊由物理上存在的扇區構成,系統又怎麼知道某份文件的數據存儲在哪些塊呢?簡單來說,就靠inode,它存儲的是文件的元數據信息及數據塊的位置。
使用stat命令查詢目錄(值得注意的是:「萬物皆文件」[4]是linux系統中很重要的理念,對新手來說「文件夾(目錄)也是文件[5]」是很反常識的,但它確是如此)的元數據信息,其中包括了如下內容:
anon@anon:~/音乐/Ichiko Aoba/2012 - Ichiko Aoba - うたびこ$ stat .
文件:.
大小:4096 块:8 IO 块大小:4096 目录
设备:259,6 Inode: 24254874 硬链接:2
权限:(0755/drwxr-xr-x) Uid: ( 1000/ anon) Gid: ( 1000/ anon)
访问时间:2024-09-25 00:47:28.225927473 +0800
修改时间:2024-09-25 00:47:03.857699427 +0800
变更时间:2024-09-25 00:47:03.857699427 +0800
创建时间:2024-09-21 00:02:39.657964154 +0800
- 直接運行sata命令得出的結果更傾向於顯示文件的元數據信息,而非inode所記錄的,例如第零行「
文件: XXXX
」它只是表示stat命令所讀取的文件(.
為當前目錄即它自身),塊大小及設備信息等是文件系統的記錄/設置;
- 第一行:
- 第二行:
- 設備編號(文件所在硬盤的編號[10]);
- inode 編號;
- 硬鏈接數量:指向該 inode 的文件數量;
- 其他便是文件權限(讀/寫/執行權限)、擁有者及用戶組的ID;及變動文件[13]相關的時間戳。
在文件系統調試器(debugfs)內使用stat命令讀取文件的inode信息:
/* 使用 < df 文件名 > 命令查询文件存储于哪块 */
anon@anon:~/下载$ df Disk-structure2.svg.png
文件系统 1K的块 已用 可用 已用% 挂载点
/dev/nvme0n1p3 944431232 375957760 520425244 42% /
/* 使用 < sudo debugfs /dev/硬盘编号 > 进入对应硬盘的调试终端 */
anon@anon:~$ sudo debugfs /dev/nvme0n1p3
debugfs 1.47.0 (5-Feb-2023)
debugfs:
/* 使用 < stat /路径/文件 > 查询文件的inode信息 */
debugfs: stat /home/anon/下载/Disk-structure2.svg.png
Inode: 23734514 Type: regular Mode: 0664 Flags: 0x80000
Generation: 1837483936 Version: 0x00000000:00000002
User: 1000 Group: 1000 Project: 0 Size: 268191
File ACL: 0
Links: 1 Blockcount: 528
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x66f2c2e7:34d18f38 -- Tue Sep 24 21:47:19 2024
atime: 0x66f2c344:43b1f34c -- Tue Sep 24 21:48:52 2024
mtime: 0x66f2c2e7:349486a4 -- Tue Sep 24 21:47:19 2024
crtime: 0x66f2c2e7:1d7446b4 -- Tue Sep 24 21:47:19 2024
Size of extra inode fields: 32
Inode checksum: 0x3993049a
EXTENTS:
(0-65):225714571-225714636
/* 使用 < stat /路径/文件 > 查询目录的inode信息 */
debugfs: stat /home/anon/下载/
Inode: 23724069 Type: directory Mode: 0755 Flags: 0x81000
Generation: 2202907076 Version: 0x00000000:0000098e
User: 1000 Group: 1000 Project: 0 Size: 12288
File ACL: 0
Links: 5 Blockcount: 24
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x66f40f70:38538adc -- Wed Sep 25 21:26:08 2024
atime: 0x66f41f00:0a6fcb00 -- Wed Sep 25 22:32:32 2024
mtime: 0x66f40f70:38538adc -- Wed Sep 25 21:26:08 2024
crtime: 0x66ba8b19:4bc4d184 -- Tue Aug 13 06:22:17 2024
Size of extra inode fields: 32
Inode checksum: 0x5f763fea
EXTENTS:
(0):94904370, (1):94910121, (2):94912946
(END)
- 其中前半部分仍舊是inode編號、權限、屬性等信息。
- Fragment:表示數據是被連續存儲的,非連續存儲就涉及到碎片化的知識,一般linux系統不用太過在意,會有碎片但不至於有嚴重影響[14],通常在windows(NTFS文件系統)中比較常見,官方也提供了整理碎片的功能。
- 文件時間戳[13]。
- Inode checksum:校驗和,根據固定算法生成,用於校驗inode數據的完整性。
- EXTENTS:數據塊信息。
「統籌一切」的目錄表(directory entry)
簡單來說,當我們保存編輯的文檔時,數據經由文件系統寫入到文件對應的inode記錄的數據塊的位置,便將文檔保存在了硬盤的扇區內。可是inode本身只包括文件內容相關的元數據信息及存儲的數據塊編號,並不記錄文件名、子文件/夾等與文件內容無關的信息,那我們怎麼知道某個文件夾內包含哪些文件、文件的名字呢?輪到目錄表出場了。
與目錄表相近的另一個概念是「inode 表」,inode 表是實際存在於分區中的一個專門的區域,用於記錄inode信息,而目錄表沒有這種區域。簡單來說,對於普通文件,inode指向的數據塊存儲的就是文件數據本身;對於目錄文件,inode指向的數據塊存儲的便是該目錄下的文件名及inode號的對應關係——即目錄表。
簡單展開說說:
- 從終端運行
nano /home/anon/文档/test.md
(只看文件系統涉及的幾個知識點的作用,下同),文件系統會從「/
」(根目錄)開始,因為根目錄對應的inode號是預分配的[16],文件系統打開其對應的數據塊並查找home
[17]對應的inode號,如此逐級查找……- 從終端運行
nano ./tese.md
時,則是直接查找當前目錄的目錄表中該文件名對應的inode號,因為當前目錄的inode號在進程控制塊(Process Control Block)中保存,無需再從根目錄逐級查找;- 正如在解釋stat命令輸出的硬鏈接數量[11]時的注釋般,也可以運行
nano ../test.md
命令,它會查找當前目錄的目錄表記錄的父目錄的inode號,並從父目錄中查找該文件名對應的inode號。基本可以說索引文件的方式就這三種,當前目錄、父目錄、從根目錄開始。
- 還有一種特殊的
nano ~/test.md
,但~
只是表示當前登陸用戶的目錄(/home/用户名/
),這跟windows的%APPDATA%
表示C:\Users\username\AppData\Roaming
一樣,都是通過「環境變量」的設置決定的,也可以自行設置短語/符號與對應的路徑或命令[18]。shell會將其自動解析為/home/用户名/
再處理命令,本質仍舊是從根目錄逐級訪問。
但它無法被直接訪問,可以在debugfs內使用ls命令讀取文件夾包含的文件/子文件夾及對應的inode編號:
/* 使用 < sudo debugfs /dev/硬盘编号 > 进入对应硬盘的调试终端 */
anon@anon:~$ sudo debugfs /dev/nvme1n1p3
/* 使用 < ls /路径/ > 查询文件夹包含的子文件夹/文件 */
debugfs: ls "/home/anon/下载/[BDMV][240925][UMXK-9037][GIRLS BAND CRY][Vol.4]/"
25183407 (12) . 23724069 (12) .. 25183410 (20) UMXX_1085
25183350 (16) SCANS 25183426 (4024) Bonus CD
(END)
/* 使用 < ls /路径/ > 查询文件夹包含的子文件夹/文件 */
debugfs: ls "/home/anon/下载/[BDMV][240925][UMXK-9037][GIRLS BAND CRY][Vol.4]/SCANS"
25183350 (12) . 25183407 (12) .. 25183420 (16) 12.png
25183488 (16) 01.png 25183489 (16) 02.png 25183490 (16) 03.png
25183495 (16) 04.png 25183520 (16) 05.png 25183521 (16) 06.png
25183522 (16) 08.png 25183523 (16) 07.png 25183524 (16) 09.png
25183525 (16) 10.png 25183526 (16) 11.png 25183527 (16) 13.png
25183528 (16) 14.png 25183529 (16) 15.png 25183530 (16) 16.png
25183531 (16) 17.png 25183532 (16) 18.png 25183533 (16) 19.png
25183534 (16) 20.png 25183535 (16) 21.png 25183536 (16) 22.png
25183537 (16) 23.png 25183538 (16) 24.png 25183539 (16) 25.png
25183549 (3660) 26.png
(END)
/* debugfs内的ls不支持递归搜索、>等符号,可以使用 < find "/使用find递归搜索的目录/" -exec stat --format='%n %i' {} \; > /使用stat获取所有文件的目录及inode编号信息并输出到此文件内 > 实现 */
anon@anon:~$ find "/home/anon/下载/[BDMV][240925][UMXK-9037][GIRLS BAND CRY][Vol.4]" -exec stat --format='%n %i' {} \; > /home/anon/下载/tree.txt
- 左為inode編號;中為
st_mode
的十進制表達(與POSIX標準一致),表示文件類型和權限信息;右為對應的文件夾或文件名。
硬鏈接[19]
到此便是文件系統及數據存儲的基本邏輯[20]。簡而言之,硬鏈接便是為某個inode新增一個指向它的指針、在目錄表中新增一個條目使文件名指向已經存在的某個inode,無論您怎麼理解和描述,這就是它的作用,不一樣的路徑、文件名,但指向同一個文件。
有什麼好處呢?硬鏈接只是新建了指向已經存在的inode的條目,所以它不會占用空間[21];因為是同一個inode的兩個不同入口,所以刪除其中任意一個並不會導致文件從物理上刪除,直至沒有文件指向該inode才會從系統中刪除。
有什麼限制呢?只有支持硬鏈接的文件系統才支持硬鏈接[22];硬鏈接僅限於文件,而不能作用於目錄[23];因為硬鏈接本質是引用相同的inode,inode基於文件系統,所以硬鏈接無法跨越文件系統[24]。
有什麼具體使用場景呢?當多個地方需要同一個文件、當該文件需要不同的文件名或位於不同的路徑時便可通過硬鏈接實現。例如媒體庫,通過bt下載某部動漫後,即想遵循人人為我我為人人的精神持續保種(下載/保種文件夾)、又想將其存檔(收藏/歸檔文件夾)、還想導入到流媒體庫中(媒體庫文件夾),下載的原始文件的命名方式、收藏時遵循/喜歡的命名格式、媒體庫要求的命名規範各不相同,同時存在三份相同的數據太浪費資源,於是便可以通過硬鏈接無傷的滿足需求;例如傳統的文件結構分類中有一份文件同時滿足多個文件夾的標準,拿捏不住具體放哪個文件夾中便可以硬鏈接。
具體來說,硬鏈接方式:ln 原始文件 新文件
軟鏈接
相比於硬鏈接,軟鏈接更容易理解,便是windows中的「快捷方式」。
有哪些優點?不限制文件系統,硬鏈接是多個文件指向同一個inode,而軟鏈接有自己的inode,存儲的內容是目標文件的路徑。
有哪些缺點?因為存儲的是路徑,所以如果目標改變了文件名或路徑便會失效;流媒體庫等無法正常加載內容(其一是軟鏈接指向路徑而不是具體的數據,其二是檢索時或許會忽略軟鏈接屬性的文件)。
適用場景?只是需要一個快捷方式或快捷訪問某個跨盤的目錄。
具體來說,軟鏈接方式:ln -s 原始文件 新文件
參考信息
- ↑ 一口氣搞懂「文件系統」,就靠這 25 張圖了、圖解 | 原來這就是文件系統、簡直不要太硬了!一文帶你徹底理解文件系統、 Linux的文件系統及文件緩存知識點整理、Disk Organisation
- ↑ 參考:存儲基礎知識:扇區與塊/簇、硬盤分區、尋址和系統啟動過程、Ext4 磁盤布局
- ↑ 本文基於ext4文件系統,詳細參考:inode 索引節點、理解inode、索引節點 - Linux 內核文檔、Inode Data Structure
- ↑
驅動除外,感興趣可以見:https://unix.stackexchange.com/a/141020 - ↑ linux系統中文件夾/目錄也是被視為文件的,只是它們的屬性不同導致作用不同,但他們共屬於文件,文件名是不能衝突的,例如文件夾內已經有了名為
1
的文件夾,是無法創建名為1
的文件的,反之亦然。 - ↑ 通過:
sudo blockdev --getbsz /dev/硬盘编号
查詢硬盤設置的塊大小。 - ↑ 通過:
sudo blockdev --getbsz /dev/硬盘编号
查詢。 - ↑ 複雜一些可以簡單理解為歷史遺留因素,參考:How does stat command calculate the blocks of a file?、POSIX.1-2008
- ↑ 詳見:What is the meaning of IO Block and how is it calculated?
- ↑ 詳見:Device number in stat command output
- ↑ 11.0 11.1 這也是為什麼在命令行中可以使用
../
訪問父目錄、./
訪問當前目錄,而沒有.../
等方式,因為該目錄的目錄表中記錄了這兩項。 - ↑ 參考:Why does a new directory have a hard link count of 2 before anything is added to it?
- ↑ 13.0 13.1 詳見:timestamp, modification time, and created time of a file
- ↑ 14.0 14.1 詳見:Is there any disk defragmenting GUI like piriform's Defraggler for Linux?、Why Linux Doesn't Need Defragmenting、Sparse file
- ↑ 參考:Should I defrag an SSD drive?
- ↑ 具體取決於文件系統,如ext4為2:Why do inode numbers start from 1 and not 0?
- ↑ 沒有父目錄及斜線前後綴,那是文件系統加載時自己加的。
- ↑ Linux 的export與alias命令
- ↑ 硬鏈接與軟鏈接、硬鏈接和軟鏈接
- ↑ 其中當然還有非常多的細節及技術,我說的也並不盡然全對,因為只將基礎簡化為這幾個部分是有很多東西無法解釋的,但請您自行探索。
- ↑ 當然部分軟件,尤其是windows等不常用硬鏈接功能系統上的工具或許會錯誤統計占用空間,這是工具問題。
- ↑
廢話,目前常用的文件系統格式如linux常用的etx4、Btrfs;NAS常用的zfs、xfs;windows常用的ntfs都支持硬鏈接。但win的早期(xp時代)流行的FAT32和exFAT(現在多用於u盤等移動存儲設備)格式便不支持硬鏈接。 - ↑ 主要原因是循環問題,最簡單的比如
/home/anon/下载
硬鏈接/home/anon/下载/图片
就會導致兩者相互包括的無限循環,如ext4文件系統在設計時便禁止了這項操作。詳細參考:Directory hardlinks break the filesystem in multiple ways、Why are hard links to directories not allowed in UNIX/Linux? - ↑ 非常容易陷入誤解的地方,但很多文檔/文章都是這麼描述,以我的技術水平也不好隨意修改,不過你可以換一種方式理解:硬鏈接不可以跨分區,哪怕是同一塊硬盤內,因為inode數據是存儲在硬盤分區中的專門一塊區域:inode 表,不同分區有自己的 inode 表。所以組raid的多塊硬盤因為共用同一個inode表,也可以硬鏈接(本身組raid後也不分是哪塊硬盤了)。