JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

硬連結 vs 符號連結

前幾篇導覽裡,你看到了那個讓其他一切成為可能的安靜把戲:一個目錄項目,不過是一個指向檔案的名字,而名字與檔案是兩樣分開的東西。順著這條線拉下去,你就得到了連結(link)——讓一個檔案多一個名字的兩種誠實做法。硬連結是另一個瞄準同一個 inode 的目錄項目;符號連結則是一個只存著一條路徑的小小檔案。它們看起來幾乎一模一樣——直到有東西被搬走或被刪除,那一刻差別就是一切。這篇導覽會把那個差別講得具體。

一個檔案,兩個名字:扯一扯那道縫

本階第一篇導覽埋下了一顆小種子,並承諾它會長大:一個名字與一個檔案是兩樣分開的東西,由一個目錄牽在一起。目錄項目裝著一個人類看得懂的名字,而檔案真正的屬性、以及它位元組的位址,則住在別處——在 Unix 風格的系統上,住在 inode(索引節點)裡。目錄做的事,不過就是把一個名字跟一個 inode 號碼配成一對,再普通不過。這個分離安靜到你會一眼讀過去,但它正是這整篇導覽的根基。如果一個名字只是一支射進某個 inode 的箭,那就沒有任何東西阻止兩支箭瞄準同一個 inode。

當兩個目錄項目指向同一個 inode,每個名字都是這個檔案真正的、平起平坐的名字——沒有哪個是「正本」,也沒有哪個是「副本」。這就是 硬連結(hard link),而「連結」這個詞正是重點:你是把第二個名字連到一個既有的檔案上,不是把它的位元組複製一份。另一種,符號連結(symbolic link,又叫軟連結,soft link),走的是一條完全不同的路:它不指向 inode,而是一個獨立的、小小的檔案,整個內容就是一條文字路徑,像 /home/ana/essays/draft.txt。這篇導覽接下來的內容,就是這兩條路的故事,以及為什麼大多數日子你分不出它們——直到那一天,差別變得無比要緊。

硬連結:射向同一個 inode 的第二支箭

執行 `ln target name`,作業系統做的事直白得令人舒坦:它在一個目錄裡多加一個新項目,把你選的名字,跟目標早就在用的那個 inode 號碼配成一對。沒有位元組被複製;沒有新的 inode 被建立。事後有兩個名字,但檔案就只有一個,而透過任一個名字讀或寫,動到的都是同一份位元組——用一個名字改了這個檔案,改動會立刻在另一個名字下看得到,因為要改的檔案就只有一個。這兩個名字是貨真價實地對等:檔案系統自己也說不出哪一個是先做出來的。

如果兩個名字共用一個檔案,一個硬邦邦的問題就跟著來了:當你刪掉其中一個名字,位元組會不會消失?inode 用它身上帶著的一個數字回答這件事,這個數字叫連結計數(link count)——就是「有幾個目錄項目指向我」。做一個硬連結,計數加一;移除一個名字,計數減一。資料只有在計數歸零、也就是最後一個名字都沒了的那一刻,才會被釋放。所以刪掉一個被硬連結的名字,並不是「刪掉這個檔案」——它是移除一個名字、把計數減一。只要還有另一個硬連結活著,位元組就完好無損地活著。

符號連結:一個裝著路徑的小小檔案

改成執行 `ln -s target name`——注意那個 -s,代表 symbolic(符號)——你得到的東西在結構上就不同了。作業系統建立一個全新的檔案,有它自己的 inode,被標上一個特殊的檔案類型,意思是「我是一個 symlink」。它的內容不是你的資料;它就只是你指定的那條路徑的文字,擺在那兒,像一個寫下來的地址。當你之後打開這個 symlink,作業系統會讀出那條存著的路徑,再順著它走到它指向的地方,這一步叫做解析(resolution)。一個 symlink,照最字面的意思,就是一塊路標:它不含著目的地,它靠名字替你指出通往那裡的路。

因為 symlink 存的是一個名字、而不是一個 inode,兩個後果就直接掉了出來——而它們正是它賺到飯吃的地方。第一,它存的那條路徑可以指名一個位在完全不同檔案系統上的目標,所以一個 symlink 能自由地跨越檔案系統,它甚至能指向一個目錄,而不只是一個普通檔案。第二,也比較讓人清醒:它指名的那個目標,可能根本不存在。把目標搬走、改名、或刪掉,symlink 仍會繼續指著一條如今哪兒也通不到的路徑。這就是一條懸空(dangling,斷掉)的 symlink——路標還立在那兒,但它指向的地方沒了,而試著打開它會以一個像「查無此檔」的錯誤失敗。

看出差別:inode 與連結計數

關於箭與路標的抽象談法,在你一看真實的 inode 號碼與連結計數的那一刻,就變得鋒利起來。想像一個檔案 draft.txt,住在 inode 1733,連結計數是 1。對它做一個硬連結 essay.txt:現在兩個名字都坐在目錄裡、指向 inode 1733,而那個 inode 的連結計數讀作 2。把它們列出來,它們無從分辨——同一個 inode、同樣的大小、同樣的位元組、兩個對等的名字。換成做一個符號連結 shortcut.txt 看看:它拿到自己全新的 inode(比方說 1899),它的連結計數仍是 1,而它的大小就只是那條被存起來的路徑字串的長度。底下的圖把這兩幅畫面並排定格起來。

HARD LINK (ln draft.txt essay.txt)
  directory:                          inode 1733  (link count = 2)
    draft.txt  -> 1733  ----\
    essay.txt  -> 1733  ----+---->  [ size, owner, perms ] -> DATA BLOCKS
                                     one file, two equal names

SYMLINK (ln -s draft.txt shortcut.txt)
  directory:                          inode 1733  (link count = 1)
    draft.txt    -> 1733  -------->  [ size, owner, perms ] -> DATA BLOCKS
    shortcut.txt -> 1899 (symlink)
                      |
                      +-- inode 1899 holds the TEXT "draft.txt"  (a stored path)
                          open(shortcut.txt) -> read path -> follow to 1733
硬連結把一個名字加到同一個 inode 上,並把它的連結計數頂到 2。符號連結是一個獨立的 inode,它的資料就只是那條路徑字串;順著它走是第二次查找,而如果目標沒了,這次查找會失敗。

對硬連結的兩條硬性限制,從這同一幅畫面裡掉了出來,兩者都源自 inode 是個「只在單一檔案系統內」才有意義的東西。一個硬連結不能跨越檔案系統,因為 inode 1733 只在它自己的檔案系統內部才有意義——另一顆磁碟上的目錄項目,根本沒辦法指向它。而在幾乎每個系統上,你通常也不能對目錄做硬連結:那樣做可能會在目錄裡織出迴圈,把走訪它的程式困進沒完沒了的繞圈圈裡,還會把連結計數的帳目攪成一團。symlink 之所以閃過這兩條限制,恰恰是因為它存的是一條路徑、而不是一個 inode——而這也正是它為什麼會懸空的原因。

什麼時候用哪一個?

這個選擇不是品味問題;每種連結的機制,決定了它擅長什麼。當你想要好幾個名字、它們在同一顆磁碟上是真正同一個檔案、而且沒有那種會斷掉的脆弱指標時,伸手去拿硬連結——例如一個備份用的名字,它必須讓資料活著、就算原本的名字被移除了也一樣,因為位元組會一直活到連結計數歸零為止。當你需要跨越檔案系統去指、指向一個目錄、或做一個友善的捷徑(它的目標你之後可能會換掉)時,伸手去拿符號連結——比方說一個叫 current 的名字,你把它從一個帶版本的資料夾,重新指到下一個。

  1. 需要在同一顆磁碟上、用兩個名字指同一個檔案,不要會斷的指標、也不要複製位元組?用硬連結:`ln target name`。
  2. 需要橫跨兩個檔案系統、或指向一個目錄、或留一個之後能重新瞄準的捷徑?用符號連結:`ln -s target name`。
  3. 要刪掉一個名字?硬連結的話,資料會一直活到最後一個連結都沒了;symlink 的話,刪掉目標會留下一條懸空的 symlink。