文件系統及硬連結

出自Wired
跳至導覽跳至搜尋

  要說硬連結,最好從數據存儲的原理上說起,但知道基礎即可無需過於深入,我也提供了其他人的博客文章[1]供感興趣的訪客深入了解。

File:文件系統與硬連結.png.png
我畫了一張拓撲圖供您整合本章涉及的知識點所用。

硬碟中的最小存儲單位:扇區、塊[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命令所讀取的文件(.為當前目錄即它自身),塊大小及設備信息等是文件系統的記錄/設置;
  • 第一行:
    • 文件大小:文件占用的大小,如果小於一個塊的大小,那也會占據一個塊[6]
    • 塊:情況稍微特殊些,儘管您設置的文件系統塊大小[7]為4KB,但它仍舊以512B為基礎計算[8]
    • IO 塊大小:通過:sudo fdisk -l /dev/硬盘编号 查詢I/O操作大小,是文件系統用於讀寫文件的首選字節數而不是唯一標準[9]
    • 文件屬性。
  • 第二行:
    • 設備編號(文件所在硬碟的編號[10]);
    • inode 編號;
    • 硬連結數量:指向該 inode 的文件數量;
      • 目錄特殊點(注意這裡是目錄表(見下一小節)的知識點):
        • 新建的目錄會有兩個特殊條目,一個指向自身(.)的inode、一個指向父目錄(..)的inode[11],若創建子目錄則還會包括指向子目錄(XXX)inode的條目;
        • 例如假設三個空文件夾如此排列 /home/anon/音乐,其中「音樂」有來自於它自己父目錄的兩個硬連結;而「anon」有來自於父目錄它自己子目錄指向的父目錄的三個硬連結;此時創建 /home/anon/电影/ 目錄 則「anon」的硬連結+1(來自於新文件夾指向的父目錄..)[12]
  • 其他便是文件權限(讀/寫/執行權限)、擁有者及用戶組的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:數據塊信息。
    • (0-65):225714571-225714636表示文件數據共有66份被存儲到225714571-225714636這段連續的數據塊中;
    • (0):94904370, (1):94910121, (2):94912946 表示文件被分割為三份存儲到三個不連續的塊中,其中第0塊存儲在編號為94904370的塊中,以此類推,嚴格來說這就有碎片化的問題,但通常無大礙[14],固態硬碟更不需要整理碎片化[15]

 

「統籌一切」的目錄表(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 原始文件 新文件

 

參考信息

  1. 一口氣搞懂「文件系統」,就靠這 25 張圖了圖解 | 原來這就是文件系統簡直不要太硬了!一文帶你徹底理解文件系統Linux的文件系統及文件緩存知識點整理Disk Organisation
  2. 參考:存儲基礎知識:扇區與塊/簇硬碟分區、尋址和系統啟動過程Ext4 磁碟布局
  3. 本文基於ext4文件系統,詳細參考:inode 索引節點理解inode索引節點 - Linux 內核文檔Inode Data Structure
  4. 驅動除外,感興趣可以見:https://unix.stackexchange.com/a/141020
  5. linux系統中文件夾/目錄也是被視為文件的,只是它們的屬性不同導致作用不同,但他們共屬於文件,文件名是不能衝突的,例如文件夾內已經有了名為1的文件夾,是無法創建名為 1 的文件的,反之亦然。
  6. 通過:sudo blockdev --getbsz /dev/硬盘编号 查詢硬碟設置的塊大小。
  7. 通過:sudo blockdev --getbsz /dev/硬盘编号 查詢。
  8. 複雜一些可以簡單理解為歷史遺留因素,參考:How does stat command calculate the blocks of a file?POSIX.1-2008
  9. 詳見:What is the meaning of IO Block and how is it calculated?
  10. 詳見:Device number in stat command output
  11. 11.0 11.1 這也是為什麼在命令行中可以使用 ../ 訪問父目錄、./訪問當前目錄,而沒有.../等方式,因為該目錄的目錄表中記錄了這兩項。
  12. 參考:Why does a new directory have a hard link count of 2 before anything is added to it?
  13. 13.0 13.1 詳見:timestamp, modification time, and created time of a file
  14. 14.0 14.1 詳見:Is there any disk defragmenting GUI like piriform's Defraggler for Linux?Why Linux Doesn't Need DefragmentingSparse file
  15. 參考:Should I defrag an SSD drive?
  16. 具體取決於文件系統,如ext4為2:Why do inode numbers start from 1 and not 0?
  17. 沒有父目錄及斜線前後綴,那是文件系統加載時自己加的。
  18. Linux 的export與alias命令
  19. 硬連結與軟連結硬連結和軟連結
  20. 其中當然還有非常多的細節及技術,我說的也並不盡然全對,因為只將基礎簡化為這幾個部分是有很多東西無法解釋的,但請您自行探索。
  21. 當然部分軟體,尤其是windows等不常用硬連結功能系統上的工具或許會錯誤統計占用空間,這是工具問題。
  22. 廢話,目前常用的文件系統格式如linux常用的etx4、Btrfs;NAS常用的zfs、xfs;windows常用的ntfs都支持硬連結。但win的早期(xp時代)流行的FAT32和exFAT(現在多用於u盤等移動存儲設備)格式便不支持硬連結。
  23. 主要原因是循環問題,最簡單的比如 /home/anon/下载 硬連結 /home/anon/下载/图片 就會導致兩者相互包括的無限循環,如ext4文件系統在設計時便禁止了這項操作。詳細參考:Directory hardlinks break the filesystem in multiple waysWhy are hard links to directories not allowed in UNIX/Linux?
  24. 非常容易陷入誤解的地方,但很多文檔/文章都是這麼描述,以我的技術水平也不好隨意修改,不過你可以換一種方式理解:硬連結不可以跨分區,哪怕是同一塊硬碟內,因為inode數據是存儲在硬碟分區中的專門一塊區域:inode 表,不同分區有自己的 inode 表。所以組raid的多塊硬碟因為共用同一個inode表,也可以硬連結(本身組raid後也不分是哪塊硬碟了)。