多角度分析爲什麼 Linux 的硬連接不能指向目錄

譯者注: 最近在看文件系統相關的,每當讀到inode相關的東西時,書上或者博客上都會涉及硬鏈接/軟鏈接相關的內容,於是今天專門針對硬鏈接翻譯了幾篇英文,弄懂它!

一、硬鏈接

本節翻譯自:http://c2.com/cgi/wiki?HardLink

在傳統的UNIX文件系統中,一個目錄就是一個包含關聯列表的文件。目錄文件中的條目是字符串形式的文件名及其對應的唯一文件標識符-- inode號。一個inode號本質上是一個磁盤上的指針,文件對象可以高效的通過它定位。沒有兩個磁盤對象共享一個inode號,也沒有一個磁盤目標有兩個inode號。

“硬鏈接”本質上是“目錄項”的同義詞當一個目標第一次被創建,就會爲它創建一個目錄項。這其實就是硬鏈接,而大多數人常常把“硬鏈接”聯想成“爲一個已有的對象創建一個額外的目錄項”。但是原來的目錄項其實沒有任何特殊,所有的鏈接都是平等的,所以一定意義上來說沒有方法能識別出哪個是原來的。

目錄也可以包含目錄,當然,這是通過硬鏈接完成的。當一個子目錄被創建時,在其父目錄中也創建了一個目錄項,這個目錄項用於將子目錄的名稱與新創建inode關聯起來。此外,新的目錄文件中也自動創建了兩個目錄項,這兩個目錄項分別將"."".." 當前目錄及其父目錄關聯起來。所以,創建一個子目錄會創建一個新的硬件鏈接到其父目錄,以及兩個對新創建對象(子目錄)的硬件鏈接:一個來自其父目錄,另一個來自他自己("."),也就是說一個目錄項的硬鏈接數最少是2。

long@zhouyl:~/test$ mkdir abc
long@zhouyl:~/test$ ls -l
total 1
drwxr-xr-x  2 long long 4096 Apr 17 09:02 abc
            |
            -- 硬鏈接數

目錄硬鏈接比較特殊。首先,創建它們惟一的方法是創建目錄;操作系統硬件鏈接函數不會允許一個硬鏈接的操作目標是一個目錄inode。其中的原因是可能會在文件系統目錄結構中產生循環。這也得根據內核,是否允許目錄硬鏈接也需要遵從文件系統模塊本身。

在傳統的UNIX文件系統中,循環很不好,有如下兩個原因:
第一,存儲的回收是基於引用計數的,而它不處理循環引用。特殊的方向引用是"."和"..",但是它們是被當作特殊情況來處理的。
其次,在樹形結構中方向引用可以導致噁心的多線程問題。在傳統的內核設計中(比如BSD內核),正在使用的inode表現爲內存中的結構 vnodes。這些節點被同時訪問,並且包含鎖。一些操作會在訪問一個目錄的子目錄時保留該目錄的鎖。這可能會導致死鎖的發生。這些鎖操作一般是不能被信號中斷的,所以死鎖的進程會一直保持死鎖狀態直至重啓。


在BSD中訪問".."時有特殊的方法來避免這種死鎖。基本上,在原來目錄vnode上的鎖剛釋放,".."鎖就被請求然後原來目錄再次被鎖。這就象一個競賽一樣。(這一段和下一段翻譯得不好,但是和理解硬鏈接關係不大,其實上面已經解釋的足夠了!我只是想盡量完整而已)

曾經我實現了一個對於vnode鎖的週期檢測算法,儘量支持一個BSD版本的文件系統的循環硬鏈接,但是問題是:儘管程序運行得很好,但是很難讓內核的其他部分配合。內核中的很多地方,比如文件系統驅動以上的層都簡單地假設鎖會成功,或者最終會成功,所以並沒有處理EDEADLK錯誤的方法。這並不很清楚,甚至如果你被允許使用那些提示你一個死鎖可能會發生的信息,你又該如何處理呢?你會打斷所有的系統調用?你會使用什麼樣的重試?應用進程又該如何響應可能有死鎖的隨機文件系統操作?


二、爲什麼硬鏈接不能指向目錄

這一節翻譯自: http://unix.stackexchange.com/questions/22394/why-hard-links-not-allowed-to-directories-in-unix-linux

第一節中已經對硬鏈接和inode等概念有了很好的解釋,但是爲了保證原文的完整性,下面內容可能有重複解釋!

2.1 從inode角度談

允許目錄的硬鏈接可能會打破文件系統的有向無環圖結構,可能創建目錄循環,這可能會導致fsck以及其他一些遍歷文件樹的軟件出錯。

首先,要想理解這點必須先了解inode。文件系統中的數據保存在磁盤上的數據塊中,而這些數據塊由inode集合在一起。可以說inode就是文件,但是inode缺少文件名,所以就需要鏈接。一個鏈接其實就是一個指向inode的指針。目錄是一個保存着這些鏈接的inode,目錄中的每一個文件名都是一個指向inode的鏈接。這裏提一下,UNIX系統中打開一個文件也會創建一個鏈接,但是它是不同類型的鏈接(它不是一個命名鏈接)。
硬鏈接只是一個指向inode的額外的目錄項,當你使用ls -l命令查看文件時,文件權限後面的數字就是命名連接數。絕大多數文件只有一個鏈接。創建一個新的硬鏈接到一個文件會將兩個文件名指向同一個inode。
long@zhouyl:~/test$ touch test
long@zhouyl:~/test$ ls -l
total 0
-rw-r--r-- 1 long long 0 Apr 16 16:56 test
long@zhouyl:~/test$ ln test test1
long@zhouyl:~/test$ ls -l
total 0
-rw-r--r-- 2 long long 0 Apr 16 16:56 test
-rw-r--r-- 2 long long 0 Apr 16 16:56 test1


現在你可以清楚的看到,其實並沒有什麼硬鏈接,一個硬鏈接和正常的名字是一樣的(這和第一節中介紹的硬鏈接一樣,第一節中解釋硬鏈接就是目錄文件中的條目,記錄的就是一個文件名與其對應inode),在上面的例子中,test 和 test1 哪個是原始文件,哪個是硬鏈接?其實你並不能分辨(忽略時間戳)因爲它們都是指向相同內容相同inode的鏈接。
long@zhouyl:~/test$ ls -li 
total 0
2114356 -rw-r--r-- 2 long long 0 Apr 16 16:56 test
2114356 -rw-r--r-- 2 long long 0 Apr 16 16:56 test1

使用ls -li (-i 標誌讓 ls 將文件的 inode 號顯示在第一列)我們可以看到此時 test 和test1 有着相同的 inode 號。現在,如果你被允許在目錄上使用硬鏈接,文件系統中的不同指針的不同目錄項會指向相同的東西。實際上,一個子目錄可以指向他的父目錄從而創建一個循環。


爲什麼需要考慮這個循環?因爲當你遍歷目錄樹時,你沒有辦法檢測到循環(如果您沒有跟蹤遍歷的inode號)。比如說,你現在在使用du命令,du需要遍歷所有的子目錄來了解磁盤的使用情況。而du命令如何知道它遇到了個循環?這很容易發生錯誤。


軟鏈接,亦稱符號鏈接,是一個完全不同的東東,因爲它們是一種特殊類型的文件(譯者添加:UNIX文件系統中的文件種類包括:普通文件,目錄文件,塊特殊文件,字符特殊文件,FIFO,套接字以及符號鏈接。比如通過 “ ln -s a b ” 創建的軟鏈接,創建軟鏈接之後文件 b 和 a 的 inode 號並不一樣,也就是說此時文件 a 和 b 並不是同一文件。 此時文件 b 中存的是文件 a  的路徑,當讀取 b 時,系統識別出文件 b 是符號鏈接會自動導向其對應的文件 a。)。注意,一個符號鏈接可以指向一個不存在的目標,因爲他們指向的只是名字而不是直接指向inode。這與硬鏈接不一樣,因爲硬鏈接就表示肯定有文件存在。


那麼爲什麼du可以很輕鬆的處理符號鏈接而不能處理硬鏈接?我們前面討論過,如果對目錄使用硬鏈接和正常的目錄是沒有區別的,而軟鏈接是特殊的,可檢測的且可跳過的。du注意到一個目錄是一個符號鏈接它會完全跳過它。
long@zhouyl:~/test$ ln -s  /home/long/Videos test2
long@zhouyl:~/test$ ls -l
total 0
-rw-r--r-- 2 long long  0 Apr 16 16:56 test
-rw-r--r-- 2 long long  0 Apr 16 16:56 test1
lrwxrwxrwx 1 long long 17 Apr 16 17:31 test2 -> /home/long/Videos
long@zhouyl:~/test$ du -ah 
0	./test
0	./test2
4.0K	.


2.2 從掛載點角度談

從掛載點角度來說,任何目錄有且只有一個父目錄".."


pwd的一個方法就是檢查設備:"."和".."的inode,如果它們一樣,說明你已處於"/"。否則,查找父目錄名稱併入棧,然後比較"../."和"../..",此後比較"../../.""../../.."...。直到抵達"/"後,開始出棧並打印棧中保存的目錄項名稱,最後得到當前目錄的完整目錄名。這個算法依賴於每個目錄有且只有一個父目錄


如果對目錄的硬鏈接是允許的,".."該指向多個父目錄中的哪個?這是一個“爲什麼不允許對目錄的硬鏈接”比較令人信服的理由。而目錄的軟鏈接不會引發這種問題,如果一個程序需要,它可以通過對路徑名進行 lstat() 來檢測是否遇到的是符號鏈接。pwd算法會返回目標目錄的正確的路徑。


三、總結

UNIX 文件系統的歷史上,對目錄的硬鏈接是可能的。但是這可能會在文件系統樹中產生循環,而這會使得遍歷文件系統變得混亂(在《Unix高級環境編程》中提到作者Steven在自己的系統上做過實驗,結果是:創建目錄硬鏈接後,文件系統變得錯誤百出)。一個目錄甚至可以是自身的父目錄,如下圖顯示,在目錄foo中如果創建一個testdir 的硬鏈接指向 foo 本身,這樣一個循環就出現了。嘿嘿,a lot of bad things will come !


現代文件系統一般禁止這些混淆狀態,只有根目錄保持了特例:根目錄是自身的父目錄。ls  /.. 就是根目錄的內容。當然,我們可以使用“ mount -o bind  /dir1  /dir2 ” 將dir1掛載到dir2上,從而達到與對目錄硬鏈接一樣的效果,只不過這個命令要求dir1和dir2都必須存在。


也有人說硬鏈接和軟鏈接的本質區別是軟鏈接是可以被系統偵測到的而硬鏈接卻做不到。所以對目錄創建軟鏈接是安全的,而硬鏈接不是。了罷此文,可喜可賀~~吐舌頭



======================

注:此文翻譯的比較長,如果有不正確之處請大家指點。此外,轉載請註明出處 吐舌頭














發佈了90 篇原創文章 · 獲贊 434 · 訪問量 106萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章