具代表性的面試主題

Linux 面試:硬連結與符號連結有什麼差異?

通用中等
OfferCC 編輯團隊發佈 更新

題幹

一次 Linux 發布為同一個設定檔建立了硬連結和相對符號連結,接著在某個行程仍持有檔案描述符時重新命名並刪除路徑。請判斷哪些路徑與描述符仍然可用,解釋硬連結為什麼不能跨檔案系統而符號連結可以,並說明如何驗證每個結論。

題幹與適用情境

某服務發布了 /srv/releases/v1/app.conf,把 /srv/live/pinned.conf 建成硬連結,把 /srv/live/current.conf 建成相對符號連結。一個 worker 先開啟發布檔案,維運人員接著重新命名 並刪除若干目錄項目。

請解釋檔名、硬連結、符號連結、inode 和已開啟檔案描述符分別參照什麼,並預測每一步結果。接著為 不可變快照、可移動發布指標和跨檔案系統參照選擇合適機制。本文限定 Linux 一般檔案;檔案系統特有 快照和 Windows 捷徑不在範圍內。

面試官考察什麼

第一項訊號是物件模型。目錄項目把一個名稱綁定到 inode。硬連結是指向同一 inode 的另一個目錄 項目,不存在特殊的「原始檔名」。符號連結是另一個檔案系統物件,其內容是一段在使用時解析的路徑。

第二項訊號是生命週期推演。unlink 刪除一個目錄項目,不一定立即銷毀檔案物件。只要還有其他硬 連結或已開啟檔案描述,inode 和資料就會繼續存在。符號連結本身可以存在,但它保存的路徑可能已無法 解析。

第三項訊號是能否預測操作,而非背誦比較表。重新命名一個硬連結不影響其他同伴;重新命名符號連結 操作的是連結自身。移動相對符號連結或目標可能改變路徑含義。硬連結不能跨檔案系統,因為 inode 身分只在單一檔案系統內有效。

最後看工程判斷:檢查物件身分與連結計數,需要時進行同檔案系統原子替換,並避免在不受信任路徑上 使用「先檢查、後開啟」的符號連結競態。

回答前需要釐清的問題

  • 來源檔案與新硬連結是否位於同一個掛載檔案系統? 若不是,link 會回傳 EXDEV;複製或

符號連結解決的是另一種需求。

  • 呼叫端需要固定物件或可移動名稱? 硬連結固定一個 inode;發布別名通常應使用可替換目錄項目的

符號連結。

  • 目標是一般檔案或目錄? Linux 通常禁止建立目錄硬連結,符號連結可以指向檔案或目錄。
  • 符號連結保存絕對路徑或相對路徑? 相對目標從符號連結所在目錄開始解析,不是從行程目前工作

目錄開始。

  • 是否有行程已經開啟檔案? 重新命名或刪除路徑不會把既有描述符改指其他物件。
  • 切換對讀取端是否必須原子可見? rename 的原子替換限於同一掛載檔案系統;跨檔案系統需要

不同發布協定。

  • 路徑元件是否由不受信任使用者控制? 若是,先 lstat 再 open 存在競態,必須在開啟

操作本身強制執行遍歷策略。

30 秒回答框架

「硬連結是指向同一 inode 的另一個目錄項目,因此多個名稱共享資料和中繼資料。符號連結擁有自己的 inode,保存一段存取時才解析的路徑。刪除一個硬連結名稱只會減少連結計數;其他硬連結或已開啟描述符 仍可讓物件存活。刪除或重新命名目標則可能讓符號連結懸空。

我會用 ls -li、stat、lstat 或 readlink 以及預先開啟的描述符驗證。需要在同一檔案系統 固定精確物件時用硬連結;需要可替換發布別名或跨檔案系統參照時用符號連結;原子切換別名時先建立暫時 符號連結,再在同一檔案系統內 rename。不受信任路徑不能分兩步檢查,開啟時就要限制符號連結和 目錄越界。」

分步驟深入解答

第一步:建立名稱到物件的模型

對一般檔案而言,目錄保存名稱與 inode 參照。inode 保存檔案類型、擁有者、權限、時間戳記、大小、 資料區塊對應和硬連結計數。檔案內容不屬於某個特殊的「原始檔名」。

硬連結為同一 inode 增加另一個目錄項目。透過任意名稱修改位元組或權限,其他名稱都會看到變化。 符號連結則有自己的 inode,內容是一段如 ../releases/v1/app.conf 的字串;一般路徑查找會繼續 解析這段字串指向的名稱。

第二步:執行具體實驗

以下命令建立題設,Shell 檔案描述符會故意保持開啟:

bash
mkdir -p /srv/releases/v1 /srv/live
printf 'version=1\n' > /srv/releases/v1/app.conf
ln /srv/releases/v1/app.conf /srv/live/pinned.conf
ln -s ../releases/v1/app.conf /srv/live/current.conf
exec 3< /srv/releases/v1/app.conf

ls -li /srv/releases/v1/app.conf /srv/live/pinned.conf
readlink /srv/live/current.conf
stat -L -c '%F %i %h' /srv/live/current.conf
stat -c '%F %i %h' /srv/live/current.conf

mv /srv/releases/v1/app.conf /srv/releases/v1/app.conf.moved
cat /srv/live/pinned.conf
cat /srv/live/current.conf
cat <&3

rm /srv/releases/v1/app.conf.moved /srv/live/pinned.conf
cat <&3
exec 3<&-

重新命名前,發布路徑和 pinned.conf 顯示相同 inode,硬連結計數為二。readlink 輸出符號連結 保存的字串。一般 stat 跟隨符號連結查看目標,範例中的不跟隨檢查則查看符號連結物件自身。

第三步:預測重新命名行為

同檔案系統內的 mv 使用重新命名操作處理來源目錄項目。inode 沒有移動,所以 pinned.conf 和 描述符 3 繼續可用。current.conf 仍保存 ../releases/v1/app.conf;舊名稱消失後,解除參照會 失敗。若重新命名符號連結本身,移動的是連結物件,不是目標;由於相對路徑從連結新的所在目錄解析, 含義還可能改變。

若發布需要切換 current 且不能出現名稱缺失空窗,應先在同一目錄建立完整的暫時符號連結,再用 rename 替換 current。替換後才開啟的讀取端會解析舊或新目錄項目;已經開啟舊目標的行程繼續 使用舊描述符。

第四步:預測刪除與空間回收

刪除 app.conf.moved 只減少一個硬連結,pinned.conf 仍指向 inode,資料繼續存在。再刪除 pinned.conf 後,最後一個目錄項目消失,但描述符 3 仍持有開啟參照,cat <&3 仍能讀取。只有 最後一個硬連結和最後一個開啟參照都消失,儲存空間才可回收。

這也解釋了為什麼刪除正在寫入的大型日誌不一定釋放空間,但不能因此任意截斷未知描述符。應先確認所屬 行程及其日誌輪替契約,必要時依安全流程傳送訊號或重新啟動。

第五步:套用檔案系統與目錄邊界

inode 編號只在所屬檔案系統內唯一。硬連結無法從另一個檔案系統命名該 inode,link 會回傳 EXDEV。符號連結保存路徑,存取時可以跨掛載點解析,也可以指向目錄或尚不存在的目標。

Linux 阻止一般使用者建立目錄硬連結,以避免檔案樹出現環和遍歷歧義。目錄符號連結允許存在,但移動 相對符號連結可能使其失效,不同遞迴工具也可能選擇不同的跟隨策略。備份、刪除和發布工具需要明確這些 策略。

第六步:依不變條件選擇

若不變條件是「新增名稱必須持續指向同一個 inode」,物件位於同一檔案系統且確實需要共享中繼資料, 選擇硬連結。若不變條件是「這個別名解析到目前保存的路徑」,尤其目標是目錄、發布指標或跨檔案系統 名稱,選擇符號連結。若需要獨立位元組、中繼資料、保留週期或檔案系統位置,選擇複製。

兩種連結都不是備份。硬連結名稱共享同一物件的修改與損壞;符號連結不保存目標資料。備份需要獨立故障 邊界、獨立保留邊界和還原測試。

第七步:驗證身分與安全路徑解析

比較裝置號碼與 inode,不要只比較 inode,因為不同檔案系統可能重複使用相同編號。用 stat 檢查 硬連結計數,用不跟隨連結的檢查查看符號連結自身,用 readlink 查看其內容,並在描述符保持開啟時 實際執行重新命名與刪除序列。

對不受信任路徑,lstat(path) 後再 open(path) 會讓攻擊者有機會替換中間元件。Linux 上應以 可信目錄描述符為起點開啟,並透過 openat2 的解析限制禁止符號連結或離開目錄。安全規則必須由最終 回傳描述符的那次路徑查找執行。

高品質示範回答

「我先從目錄項目解釋。app.conf 與 pinned.conf 是同一 inode 的兩個平等名稱,所以共享內容、 權限、擁有者和時間戳記。current.conf 是獨立的符號連結 inode,保存 ../releases/v1/app.conf,相對路徑從 /srv/live 開始解析。

發布路徑重新命名後,硬連結與已經開啟的描述符仍指向原檔案物件。符號連結保存的仍是舊路徑,所以變成 懸空連結。刪除移動後的名稱時,硬連結繼續讓物件存活;再刪除硬連結後,開啟描述符依然可用,核心要等 它關閉後才能回收物件。

我會用硬連結固定同檔案系統內的精確成品,用符號連結作為可移動的 current 別名,需要獨立保留時 建立真正副本。更新 current 時,先建立暫時連結,再在同一目錄原子重新命名。我會用裝置號碼與 inode、連結計數、readlink、懸空存取和開啟描述符測試證明結果。不受信任路徑則在開啟操作內強制 禁止符號連結與目錄越界。」

常見錯誤

  • 把硬連結稱為指向原始檔名的指標 → 所有硬連結都是同一 inode 的平等目錄項目 → **說明名稱、

inode 身分與連結計數。**

  • 聲稱刪除一定立即銷毀檔案 → 其他名稱或開啟參照可能仍然存在 → **同時追蹤連結計數與檔案

描述符。**

  • 把符號連結當成保存 inode 參照 → 它保存的路徑日後可能解析到別處或失敗 → **檢查連結內容與

解析起點。**

  • 只比較 inode 編號 → inode 編號僅在檔案系統內唯一 → 比較裝置號碼加 inode。
  • 用硬連結跨掛載點或指向目錄 → Linux 會拒絕這些情境 → 依獨立性需求選擇符號連結或複製。
  • 移動相對符號連結卻不重新核對 → 連結所在目錄決定解析起點 → 移動後重新計算或產生目標。
  • 把任一種連結當作備份 → 一個共享物件,另一個只保存路徑 → 建立可獨立還原的副本。
  • 先檢查路徑、稍後再開啟 → 攻擊者可以在兩步之間替換符號連結 → 開啟時原子執行遍歷限制。

追問及應對

追問一:不同硬連結有各自的權限和擁有者嗎?

沒有。權限、擁有者、大小和時間戳記屬於共享 inode,透過一個名稱修改後,所有同伴都會看到。目錄項目 名稱和父目錄權限是另一層;重新命名或刪除名稱由所在目錄的權限決定。

追問二:相對符號連結移動後為什麼可能失效?

其內容從符號連結所在目錄開始解析。移動連結會改變這個起點,卻不改變保存的字串。絕對符號連結雖不受 該移動影響,但在容器、chroot、不同掛載配置或其他主機中可能指錯。應依搬移需求選擇。

追問三:rename 能否跨檔案系統原子替換 current?

不能。Linux rename 跨掛載檔案系統會回傳 EXDEV。應先在目標檔案系統內發布完整物件,再在目標 目錄建立暫時別名,並於同一目錄重新命名覆蓋 current;先前的複製階段需要另外定義清理與復原。

追問四:最後一個路徑已刪除,磁碟空間為什麼仍被占用?

某個開啟檔案描述可能仍參照 inode。先定位描述符及所屬行程,再使用應用程式安全的輪替或重新啟動 流程。只有最後一個名稱和最後一個開啟參照都消失,空間才會回收;刪除路徑只證明該名稱不存在。

追問五:stat、lstat 與 readlink 有何差異?

stat 通常跟隨最終符號連結並報告目標;lstat 報告連結物件;readlink 回傳連結保存的路徑, 但不解析它。驗證時要同時證明別名自身和它目前指向的物件。

追問六:上傳目錄如何防止符號連結競態?

以可信目錄描述符為起點開啟,讓路徑查找本身強制停留在該目錄下並禁止不允許的符號連結,再用基於描述 符的操作檢查結果。先做路徑檢查、再另外開啟會留下被替換空窗。

公開來源

同類題目