深入講解 linux 中 inode、硬連結、軟連結的原理
inode定義
inode 是 linux 系統中用作資料索引的識別符號。
簡單來說,inode 指示了一個檔案的基本資訊,如inode編號、修改時間、檔案的位置等,就如同一本書的目錄,會直接告訴你想看的章節是在第幾頁。不同的是,書是以頁為單位的,而 linux 檔案存取是以“塊”為單位的。
作業系統在讀取硬碟的時候,會一次性讀取一個“塊”(一個“塊”的大小往往是4kb,包含了連續8個扇區,每個扇區儲存512個位元組)。而inode就告訴了檔案位於哪個“塊”,於是系統就會從這個“塊”開始讀取內容,我們就可以看到這個檔案的內容。
每個檔案都有對應的inode,儲存著關於這個檔案的基本資訊。linux 系統不使用檔名,而使用 inode 號來識別檔案。對於使用者,我們是通過檔名開啟的檔案;但是對於系統內部,是分為三步的:
- 系統找到這個檔名對應的 inode 號
- 通過 inode 號,獲取 inode 資訊
- 根據 inode 資訊,找到檔案資料所在的 block,讀取內容
inode內容
inode 包含了檔案的以下基本資訊:
- 檔案的位元組數
- inode 編號
- 檔案擁有者的 Uid
- 檔案所屬group的 Gid
- 檔案的讀、寫、執行許可權
- 檔案的時間戳,共有三個:
- change:inode 上一次變動的時間
- modify:檔案內容上一次變動的時間
- access:檔案上一次開啟的時間
- 連結數,即有多少檔名指向這個 inode
- 檔案資料 block 的位置
我們可以使用 stat
命令來檢視檔案的 inode 資訊,如:
$ stat v0.1.0.zip
File: ‘v0.1.0.zip’
Size: 94267 Blocks: 192 IO Block: 4096 regular file
Device: 811h/2065d Inode: 5659765 Links: 1
Access: (0640/-rw-r-----) Uid: ( 3457/mart_bda) Gid: ( 3457/mart_bda)
Access: 2018-06-12 14:22:18.434027485 +0800
Modify: 2018-06-12 14:18:00.840994081 +0800
Change: 2018-06-12 14:18:00.840994081 +0800
Birth: -
也可以在 ls
後加上 -i
直接獲取 incode 編號:
$ ls -i v0.1.0.zip
5659765 v0.1.0.zip
inode大小
inode儲存了檔案的基本資訊,雖然資訊很少,但是也會佔用空間。
硬碟格式化的時候,作業系統自動將硬碟分為兩個區域:
- 資料區:存放檔案內容
- inode 區:存放 inode 包含的資訊,也叫作 inode table
每個 inode 節點的大小,一般是 128 位元組或 256 位元組。inode 節點的總數,在硬碟格式化時就固定了。一般,資料區每1KB 或 2KB,inode區就會增加一個 inode。假如在一塊 1GB 的硬碟中,每個 inode 節點的大小為 128 位元組,那麼 inode 表的大小就會達到 128 MB,佔整塊硬碟的 12.8%。
既然 inode 節點總數是有限的,那麼分割槽的節點數就有用完的時候,一旦 inode 用完了,即使磁碟空間還有剩餘,也不能再存放任何資料,因為需要保證每個檔案必須有一個 inode。
檢視每個硬碟分割槽的 inode 或者磁碟容量的使用情況,可以使用 df
命令加上引數 -i
或者 -h
,如:
$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda5 275436544 801853 274634691 1% /
devtmpfs 8192960 524 8192436 1% /dev
tmpfs 8195307 4 8195303 1% /dev/shm
tmpfs 8195307 765 8194542 1% /run
tmpfs 8195307 13 8195294 1% /sys/fs/cgroup
/dev/sda2 204800 342 204458 1% /boot
/dev/sdb1 11443200 3329257 8113943 30% /data0
tmpfs 8195307 1 8195306 1% /run/user/0
tmpfs 8195307 1 8195306 1% /run/user/3457
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda5 263G 134G 130G 51% /
devtmpfs 32G 0 32G 0% /dev
tmpfs 32G 12K 32G 1% /dev/shm
tmpfs 32G 394M 31G 2% /run
tmpfs 32G 0 32G 0% /sys/fs/cgroup
/dev/sda2 197M 139M 58M 71% /boot
/dev/sdb1 11T 5.2T 5.2T 51% /data0
tmpfs 6.3G 0 6.3G 0% /run/user/0
tmpfs 6.3G 0 6.3G 0% /run/user/3457
關於 df -h -i
的區別,可以參考 Linux df命令。
檔案操作對 inode 的影響
要理解檔案的操作對 inode 的影響,先要理解目錄的原理。目錄對外表現是一個容器,存放著子檔案和子目錄,實際上在系統內部,目錄本身也是一個檔案,目錄檔案的內容即是該目錄下的檔名與 inode 號的對映表(即一個個的目錄項)。因此,linux 訪問一個檔案時,要先查詢到上一級目錄,根據目錄內容查詢到檔案對應的 inode 號,然後讀取對應的 block。
cp 命令
系統內部會執行以下操作:
分配一個未被使用的 inode 號,在 inode 表中新添一個專案。
如果是覆蓋複製,則 inode 號不變,沿用之前同名檔案的 inode 號。
在目錄中新建一個目錄項,並指向步驟 1 中的 inode。
- 把資料複製到 block 中。
rm 命令
系統內部會執行以下操作:
- 減少待刪除檔名所對應的 inode 的連結數量,如果連結數變為0,則釋放 inode,同時資料塊放到可用空間中(對外表現為資料已刪除,因為隨時可以覆蓋。如果沒有覆蓋,資料還可以恢復;一旦覆蓋了,那麼刪除的資料無法恢復。)。
- 刪除目錄中的目錄項。
mv 命令
一、如果目標檔案和原始檔屬於同一個檔案系統:
- 在目標檔案的目錄中新建目錄項
- 刪除原始檔的目錄中的目錄項
- 目標檔名會指向原始檔名的 inode。因此該操作對 inode 沒有影響(除了時間戳),對資料的位置也沒有影響,不移動任何資料。
二、如果目標檔案和原始檔屬於不同檔案系統,則相當於 cp + rm。
ln 命令
一、硬連結
一般情況下,檔名和 inode 號是一一對應,但是也有可能多個檔名指向同一個 inode 號,即硬連結。硬連結可以實現用不同的檔名訪問同一個檔案;對檔案內容修改,會影響到所有的檔名;但是,刪除一個檔名,不影響其他檔名的訪問。
建立硬連結的命令:
ln [source file] [new file]
如:
$ ll -h -i
total 479M
5659849 -rw-r----- 1 mart_bda mart_bda 479M Jun 13 10:57 test_file
$ ln test_file test_file_hardlink
$ ll -i -h
total 957M
5659849 -rw-r----- 2 mart_bda mart_bda 479M Jun 13 10:57 test_file
5659849 -rw-r----- 2 mart_bda mart_bda 479M Jun 13 10:57 test_file_hardlink
這樣,兩個檔案的 inode 號均為 5659849。具體檢視兩個檔案的 inode 內容:
$ stat test_file
File: ‘test_file’
Size: 501577774 Blocks: 979656 IO Block: 4096 regular file
Device: 811h/2065d Inode: 5659849 Links: 2
Access: (0640/-rw-r-----) Uid: ( 3457/mart_bda) Gid: ( 3457/mart_bda)
Access: 2018-06-13 10:57:13.961409755 +0800
Modify: 2018-06-13 10:57:14.931383436 +0800
Change: 2018-06-13 10:58:11.382851699 +0800
Birth: -
$ stat test_file_hardlink
File: ‘test_file_hardlink’
Size: 501577774 Blocks: 979656 IO Block: 4096 regular file
Device: 811h/2065d Inode: 5659849 Links: 2
Access: (0640/-rw-r-----) Uid: ( 3457/mart_bda) Gid: ( 3457/mart_bda)
Access: 2018-06-13 10:57:13.961409755 +0800
Modify: 2018-06-13 10:57:14.931383436 +0800
Change: 2018-06-13 10:58:11.382851699 +0800
Birth: -
可以看到,兩個檔案的 inode 內容完全相同,且 Links
變成了 2。修改任何一個檔名的內容,另一個檔名的內容也會同時改變,因為訪問的就是硬碟中的同一塊資料。
如果再將 test_file_hardlink 刪掉,會使得 Links
變回 1。當這個值減到 0 時,說明沒有檔名指向這個 inode,系統就會回收這個號碼,以及所對應的 block 區域。
另外,對於目錄的連結數,建立一個目錄時,預設會生成兩個目錄項:.
和 ..
。前者的 inode 號就是當前目錄的 inode 號,等同於當前目錄的硬連結;後者的 inode 號是父目錄的 inode 號,等同於父目錄的硬連結。因此,任何一個目錄的硬連結總數,總是等於 2 加上它的子目錄總數(含隱藏目錄,且除去.
和 ..
)。
二、軟連結(符號連結)
軟連結也可以通過不同的檔名訪問同一塊資料,但是與硬連結不同的是,兩個檔名的 inode 是不一樣的。那如何訪問同一塊區域呢?比如檔案 A 是檔案 B 的軟連線,那麼檔案 A 的內容存放的是檔案 B 的路徑名(可以通過這個找到檔案 B 的目錄項)。因此訪問 A 時,會讀取檔案 B 的路徑,進而讀取檔案 B 的內容。這樣,對外表現來看,檔案 A 和檔案 B 的內容就相同了。類似於 windows 系統下的快捷方式。
建立軟連結的命令:
ln -s [source file] [new file]
如:
$ ll
total 489824
-rw-r----- 1 mart_bda mart_bda 501577774 Jun 13 11:21 test_file
$ ln -s test_file test_file_soft
$ ll -h -i
total 479M
5659853 -rw-r----- 1 mart_bda mart_bda 479M Jun 13 11:21 test_file
5659854 lrwxrwxrwx 1 mart_bda mart_bda 9 Jun 13 11:22 test_file_soft -> test_file
如果是對資料夾建立軟連結,則為
ln -s /tmp/test_directory ./
會自動地在當前目錄建立一個資料夾 test_directory
,並指向 /tmp/test_directory
可以看到,兩個檔案的 inode 號是不同的。
既然檔案 A 是依賴檔案 B 存在的,那麼如果刪除了檔案 B,開啟檔案 A 就會報錯:No such file or directory
;如果刪除了檔案 A,則對檔案 B 的開啟無影響,因為只是刪除了“快捷方式”而已。
軟連線的建立,不會影響到檔案 B 的 inode 的任何資訊,包括 Links
。
三、硬連結和軟連結的不同
- 本質不同:硬連結是指向同一個檔案,軟連結指向的不是同一個檔案。
- 刪除時:硬連結不受影響,軟連結失效
- 建立連結時:建立硬連結連結數加1,建立軟連結連線數不變
- 是否可以跨分割槽:硬連結不可以跨分割槽,軟連結可以跨分割槽
- 目錄是否可以建立連結:硬連結不可以對目錄建立,軟連結可以對目錄建立
- 硬連結的inode號相同,軟連結inode號不同
四、硬連結和軟連結的佔用空間分析
大家可能注意到了,對於同一個 test_file(大小為 479M
),建立硬連結後,目錄的整體空間佔用為 total 957M
,而建立軟連結後,目錄的整體佔用空間仍為 479M
。既然硬連結指向的是同一塊資料,那麼就不會開闢新的空間去複製一份,應該是不佔用空間的啊?為什麼空間佔用會加倍呢?這個問題,可以參考關於硬連結與軟連線佔用磁碟空間問題的分析研究。