理解 Linux 的硬連結與軟連結

me_lawrence發表於2015-09-23

http://www.ibm.com/developerworks/cn/linux/l-cn-hardandsymb-links/

理解 Linux 的硬連結與軟連結

從 inode 瞭解 Linux 檔案系統

硬連結與軟連結是 Linux 檔案系統中的一個重要概念,其涉及檔案系統中的索引節點 (index node 又稱 inode),而索引節點物件是 Linux 虛擬檔案系統 (VFS) 的四個基本概念之一。通過剖析硬連結與軟連結的聯絡與區別,我們可更好的瞭解 Linux 中 VFS 這一通用檔案模型。並讓 Linux 普通使用者和系統管理員正確使用硬連結與軟連結,幫助檔案系統開發者獲取 inode 的相關知識。

王 華東, 自由職業者

2012 年 12 月 20 日

  • +內容

Linux 的檔案與目錄

現代作業系統為解決資訊能獨立於程式之外被長期儲存引入了檔案,檔案作為程式建立資訊的邏輯單元可被多個程式併發使用。在 UNIX 系統中,作業系統為磁碟上的文字與影像、滑鼠與鍵盤等輸入裝置及網路互動等 I/O 操作設計了一組通用 API,使他們被處理時均可統一使用位元組流方式。換言之,UNIX 系統中除程式之外的一切皆是檔案,而 Linux 保持了這一特性。為了便於檔案的管理,Linux 還引入了目錄(有時亦被稱為資料夾)這一概念。目錄使檔案可被分類管理,且目錄的引入使 Linux 的檔案系統形成一個層級結構的目錄樹。清單 1.所示的是普通 Linux 系統的頂層目錄結構,其中 /dev 是存放了裝置相關檔案的目錄。

清單 1. Linux 系統的頂層目錄結構
 /              根目錄
├── bin     存放使用者二進位制檔案
├── boot    存放核心引導配置檔案
├── dev     存放裝置檔案
├── etc     存放系統配置檔案
├── home    使用者主目錄
├── lib     動態共享庫
├── lost+found 	檔案系統恢復時的恢復檔案
├── media   可解除安裝儲存介質掛載點
├── mnt     檔案系統臨時掛載點
├── opt     附加的應用程式包
├── proc    系統記憶體的對映目錄,提供核心與程式資訊
├── root    root 使用者主目錄
├── sbin    存放系統二進位制檔案
├── srv     存放服務相關資料
├── sys     sys 虛擬檔案系統掛載點
├── tmp     存放臨時檔案
├── usr     存放使用者應用程式
└── var     存放郵件、系統日誌等變化檔案

Linux 與其他類 UNIX 系統一樣並不區分檔案與目錄:目錄是記錄了其他檔名的檔案。使用命令 mkdir 建立目錄時,若期望建立的目錄的名稱與現有的檔名(或目錄名)重複,則會建立失敗。

 # ls -F /usr/bin/zi* 
 /usr/bin/zip*       /usr/bin/zipgrep*  /usr/bin/zipnote* 
 /usr/bin/zipcloak*  /usr/bin/zipinfo*  /usr/bin/zipsplit* 

 # mkdir -p /usr/bin/zip 
 mkdir: cannot create directory `/usr/bin/zip': File exists

Linux 將裝置當做檔案進行處理,清單 2.展示瞭如何開啟裝置檔案 /dev/input/event5 並讀取檔案內容。檔案 event5 表示一種輸入裝置,其可能是滑鼠或鍵盤等。檢視檔案 /proc/bus/input/devices 可知 event5 對應裝置的型別。裝置檔案 /dev/input/event5 使用 read() 以字元流的方式被讀取。結構體 input_event 被定義在核心標頭檔案 linux/input.h 中。

清單 2. 開啟並讀取裝置檔案
 int fd; 
 struct input_event ie; 
 fd = open("/dev/input/event5", O_RDONLY); 
 read(fd, &ie, sizeof(struct input_event)); 
 printf("type = %d  code = %d  value = %d\n", 
			 ie.type, ie.code, ie.value); 
 close(fd);

硬連結與軟連結的聯絡與區別

我們知道檔案都有檔名與資料,這在 Linux 上被分成兩個部分:使用者資料 (user data) 與後設資料 (metadata)。使用者資料,即檔案資料塊 (data block),資料塊是記錄檔案真實內容的地方;而後設資料則是檔案的附加屬性,如檔案大小、建立時間、所有者等資訊。在 Linux 中,後設資料中的 inode 號(inode 是檔案後設資料的一部分但其並不包含檔名,inode 號即索引節點號)才是檔案的唯一標識而非檔名。檔名僅是為了方便人們的記憶和使用,系統或程式通過 inode 號尋找正確的檔案資料塊。圖 1.展示了程式通過檔名獲取檔案內容的過程。

圖 1. 通過檔名開啟檔案
圖 1. 通過檔名開啟檔案
清單 3. 移動或重新命名檔案
 # stat /home/harris/source/glibc-2.16.0.tar.xz 
  File: `/home/harris/source/glibc-2.16.0.tar.xz'
  Size: 9990512   	 Blocks: 19520      IO Block: 4096   regular file 
 Device: 807h/2055d 	 Inode: 2485677     Links: 1 
 Access: (0600/-rw-------)  Uid: ( 1000/  harris)   Gid: ( 1000/  harris) 
 ... 
 ... 
 # mv /home/harris/source/glibc-2.16.0.tar.xz /home/harris/Desktop/glibc.tar.xz 
 # ls -i -F /home/harris/Desktop/glibc.tar.xz 
 2485677 /home/harris/Desktop/glibc.tar.xz

在 Linux 系統中檢視 inode 號可使用命令 stat 或 ls -i(若是 AIX 系統,則使用命令 istat)。清單 3.中使用命令 mv 移動並重新命名檔案 glibc-2.16.0.tar.xz,其結果不影響檔案的使用者資料及 inode 號,檔案移動前後 inode 號均為:2485677。

為解決檔案的共享使用,Linux 系統引入了兩種連結:硬連結 (hard link) 與軟連結(又稱符號連結,即 soft link 或 symbolic link)。連結為 Linux 系統解決了檔案的共享使用,還帶來了隱藏檔案路徑、增加許可權安全及節省儲存等好處。若一個 inode 號對應多個檔名,則稱這些檔案為硬連結。換言之,硬連結就是同一個檔案使用了多個別名(見 圖 2.hard link 就是 file 的一個別名,他們有共同的 inode)。硬連結可由命令 link 或 ln 建立。如下是對檔案 oldfile 建立硬連結。

 link oldfile newfile 
 ln oldfile newfile

由於硬連結是有著相同 inode 號僅檔名不同的檔案,因此硬連結存在以下幾點特性:

  • 檔案有相同的 inode 及 data block;
  • 只能對已存在的檔案進行建立;
  • 不能交叉檔案系統進行硬連結的建立;
  • 不能對目錄進行建立,只可對檔案建立;
  • 刪除一個硬連結檔案並不影響其他有相同 inode 號的檔案。
清單 4. 硬連結特性展示
 # ls -li 
 total 0 

 // 只能對已存在的檔案建立硬連線
 # link old.file hard.link 
 link: cannot create link `hard.link' to `old.file': No such file or directory 

 # echo "This is an original file" > old.file 
 # cat old.file 
 This is an original file 
 # stat old.file 
  File: `old.file'
  Size: 25        	 Blocks: 8          IO Block: 4096   regular file 
 Device: 807h/2055d 	 Inode: 660650      Links: 2 
 Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root) 
 ... 
 // 檔案有相同的 inode 號以及 data block 
 # link old.file hard.link | ls -li 
 total 8 
 660650 -rw-r--r-- 2 root root 25 Sep  1 17:44 hard.link 
 660650 -rw-r--r-- 2 root root 25 Sep  1 17:44 old.file 

 // 不能交叉檔案系統
 # ln /dev/input/event5 /root/bfile.txt 
 ln: failed to create hard link `/root/bfile.txt' => `/dev/input/event5': 
 Invalid cross-device link 

 // 不能對目錄進行建立硬連線
 # mkdir -p old.dir/test 
 # ln old.dir/ hardlink.dir 
 ln: `old.dir/': hard link not allowed for directory 
 # ls -iF 
 660650 hard.link  657948 old.dir/  660650 old.file

檔案 old.file 與 hard.link 有著相同的 inode 號:660650 及檔案許可權,inode 是隨著檔案的存在而存在,因此只有當檔案存在時才可建立硬連結,即當 inode 存在且連結計數器(link count)不為 0 時。inode 號僅在各檔案系統下是唯一的,當 Linux 掛載多個檔案系統後將出現 inode 號重複的現象(如 清單 5.所示,檔案 t3.jpg、sync 及 123.txt 並無關聯,卻有著相同的 inode 號),因此硬連結建立時不可跨檔案系統。裝置檔案目錄 /dev 使用的檔案系統是 devtmpfs,而 /root(與根目錄 / 一致)使用的是磁碟檔案系統 ext4。清單 5.展示了使用命令 df 檢視當前系統中掛載的檔案系統型別、各檔案系統 inode 使用情況及檔案系統掛載點。

清單 5. 查詢有相同 inode 號的檔案
 # df -i --print-type 
 Filesystem     Type       Inodes  IUsed    IFree IUse% Mounted on 
 /dev/sda7      ext4      3147760 283483  2864277   10% / 
 udev           devtmpfs   496088    553   495535    1% /dev 
 tmpfs          tmpfs      499006    491   498515    1% /run 
 none           tmpfs      499006      3   499003    1% /run/lock 
 none           tmpfs      499006     15   498991    1% /run/shm 
 /dev/sda6      fuseblk  74383900   4786 74379114    1% /media/DiskE 
 /dev/sda8      fuseblk  29524592  19939 29504653    1% /media/DiskF 

 # find / -inum 1114 
 /media/DiskE/Pictures/t3.jpg 
 /media/DiskF/123.txt 
 /bin/sync

值得一提的是,Linux 系統存在 inode 號被用完但磁碟空間還有剩餘的情況。我們建立一個 5M 大小的 ext4 型別的 mo.img 檔案,並將其掛載至目錄 /mnt。然後我們使用一個 shell 指令碼將掛載在 /mnt 下 ext4 檔案系統的 indoe 耗盡(見清單 6.)。

清單 6. 測試檔案系統 inode 耗盡但仍有磁碟空間的情景
 # dd if=/dev/zero of=mo.img bs=5120k count=1 
 # ls -lh mo.img 
 -rw-r--r-- 1 root root 5.0M Sep  1 17:54 mo.img 
 # mkfs -t ext4  -F ./mo.img 
 ... 
 OS type: Linux 
 Block size=1024 (log=0) 
 Fragment size=1024 (log=0) 
 Stride=0 blocks, Stripe width=0 blocks 
 1280 inodes, 5120 blocks 
 256 blocks (5.00%) reserved for the super user 
 ... 
 ... 
 Writing superblocks and filesystem accounting information: done 

 # mount -o loop ./mo.img /mnt 
 # cat /mnt/inode_test.sh 
 #!/bin/bash 

 for ((i = 1; ; i++)) 
 do 
    if [ $? -eq 0 ]; then 
        echo  "This is file_$i" > file_$i 
    else 
        exit 0 
    fi 
 done 

 # ./inode_test.sh 
 ./inode_test.sh: line 6: file_1269: No space left on device 

 # df -iT /mnt/; du -sh /mnt/ 
 Filesystem     Type Inodes IUsed IFree IUse% Mounted on 
 /dev/loop0     ext4   1280  1280     0  100% /mnt 
 1.3M 	 /mnt/

硬連結不能對目錄建立是受限於檔案系統的設計(見 清單 4.對目錄建立硬連結將失敗)。現 Linux 檔案系統中的目錄均隱藏了兩個個特殊的目錄:當前目錄(.)與父目錄(..)。檢視這兩個特殊目錄的 inode 號可知其實這兩目錄就是兩個硬連結(注意目錄 /mnt/lost+found/ 的 inode 號)。若系統允許對目錄建立硬連結,則會產生目錄環。

 # ls -aliF /mnt/lost+found 
 total 44 
 11 drwx------ 2 root root 12288 Sep  1 17:54 ./ 
 2 drwxr-xr-x 3 root root 31744 Sep  1 17:57 ../ 

 # stat  /mnt/lost+found/ 
  File: `/mnt/lost+found/'
  Size: 12288     	 Blocks: 24         IO Block: 1024   directory 
 Device: 700h/1792d 	 Inode: 11          Links: 2 
 Access: (0700/drwx------)  Uid: (    0/    root)   Gid: (    0/    root) 
 Access: 2012-09-01 17:57:17.000000000 +0800 
 Modify: 2012-09-01 17:54:49.000000000 +0800 
 Change: 2012-09-01 17:54:49.000000000 +0800 
 Birth: -

軟連結與硬連結不同,若檔案使用者資料塊中存放的內容是另一檔案的路徑名的指向,則該檔案就是軟連線。軟連結就是一個普通檔案,只是資料塊內容有點特殊。軟連結有著自己的 inode 號以及使用者資料塊(見 圖 2.)。因此軟連結的建立與使用沒有類似硬連結的諸多限制:

  • 軟連結有自己的檔案屬性及許可權等;
  • 可對不存在的檔案或目錄建立軟連結;
  • 軟連結可交叉檔案系統;
  • 軟連結可對檔案或目錄建立;
  • 建立軟連結時,連結計數 i_nlink 不會增加;
  • 刪除軟連結並不影響被指向的檔案,但若被指向的原檔案被刪除,則相關軟連線被稱為死連結(即 dangling link,若被指向路徑檔案被重新建立,死連結可恢復為正常的軟連結)。
圖 2. 軟連結的訪問
圖 2. 軟連結的訪問
清單 7. 軟連結特性展示
 # ls -li 
 total 0 

 // 可對不存在的檔案建立軟連結
 # ln -s old.file soft.link 
 # ls -liF 
 total 0 
 789467 lrwxrwxrwx 1 root root 8 Sep  1 18:00 soft.link -> old.file 

 // 由於被指向的檔案不存在,此時的軟連結 soft.link 就是死連結
 # cat soft.link 
 cat: soft.link: No such file or directory 

 // 建立被指向的檔案 old.file,soft.link 恢復成正常的軟連結
 # echo "This is an original file_A" >> old.file 
 # cat soft.link 
 This is an original file_A 

 // 對不存在的目錄建立軟連結
 # ln -s old.dir soft.link.dir 
 # mkdir -p old.dir/test 
 # tree . -F --inodes 
 . 
├── [ 789497]  old.dir/ 
│   └── [ 789498]  test/ 
├── [ 789495]  old.file 
├── [ 789495]  soft.link -> old.file 
└── [ 789497]  soft.link.dir -> old.dir/

當然軟連結的使用者資料也可以是另一個軟連結的路徑,其解析過程是遞迴的。但需注意:軟連結建立時原檔案的路徑指向使用絕對路徑較好。使用相對路徑建立的軟連結被移動後該軟連結檔案將成為一個死連結(如下所示的軟連結 a 使用了相對路徑,因此不宜被移動),因為連結資料塊中記錄的亦是相對路徑指向。

 $ ls -li 
 total 2136 
 656627 lrwxrwxrwx 1 harris harris       8 Sep  1 14:37 a -> data.txt
 656662 lrwxrwxrwx 1 harris harris       1 Sep  1 14:37 b -> a 
 656228 -rw------- 1 harris harris 2186738 Sep  1 14:37 data.txt 6

連結相關命令

在 Linux 中檢視當前系統已掛著的檔案系統型別,除上述使用的命令 df,還可使用命令 mount 或檢視檔案 /proc/mounts。

 # mount 
 /dev/sda7 on / type ext4 (rw,errors=remount-ro) 
 proc on /proc type proc (rw,noexec,nosuid,nodev) 
 sysfs on /sys type sysfs (rw,noexec,nosuid,nodev) 
 ... 
 ... 
 none on /run/shm type tmpfs (rw,nosuid,nodev)

命令 ls 或 stat 可幫助我們區分軟連結與其他檔案並檢視檔案 inode 號,但較好的方式還是使用 find 命令,其不僅可查詢某檔案的軟連結,還可以用於查詢相同 inode 的所有硬連結。(見清單 8.)

清單 8. 使用命令 find 查詢軟連結與硬連結
 // 查詢在路徑 /home 下的檔案 data.txt 的軟連結
 # find /home -lname data.txt 
 /home/harris/debug/test2/a 

 // 檢視路徑 /home 有相同 inode 的所有硬連結
 # find /home -samefile /home/harris/debug/test3/old.file 
 /home/harris/debug/test3/hard.link 
 /home/harris/debug/test3/old.file 

 # find /home -inum 660650 
 /home/harris/debug/test3/hard.link 
 /home/harris/debug/test3/old.file 

 // 列出路徑 /home/harris/debug/ 下的所有軟連結檔案
 # find /home/harris/debug/ -type l -ls 
 656662 0 lrwxrwxrwx 1 harris harris 1 Sep 1 14:37 /home/harris/debug/test2/b -> a
 656627 0 lrwxrwxrwx 1 harris harris 8 Sep 1 14:37 /home/harris/debug/test2/a -> 
 data.txt
 789467 0 lrwxrwxrwx 1 root root 8 Sep 1 18:00 /home/harris/debug/test/soft.link -> 
 old.file 
 789496    0 lrwxrwxrwx   1 root     root            7 Sep  1 18:01 
 /home/harris/debug/test/soft.link.dir -> old.dir

系統根據磁碟的大小預設設定了 inode 的值(見清單 9.),如若必要,可在格式檔案系統前對該值進行修改。如鍵入命令 mkfs -t ext4 -I 512/dev/sda4,將使磁碟裝置 /dev/sda4 格式成 inode 大小是 512 位元組的 ext4 檔案系統。

清單 9. 檢視系統的 inode 值
 // 檢視磁碟分割槽 /dev/sda7 上的 inode 值
 # dumpe2fs -h /dev/sda7 | grep "Inode size"
 dumpe2fs 1.42 (29-Nov-2011) 
 Inode size: 	          256 

 # tune2fs -l /dev/sda7 | grep "Inode size"
 Inode size: 	          256

Linux VFS

Linux 有著極其豐富的檔案系統,大體上可分如下幾類:

  1. 網路檔案系統,如 nfs、cifs 等;
  2. 磁碟檔案系統,如 ext4、ext3 等;
  3. 特殊檔案系統,如 proc、sysfs、ramfs、tmpfs 等。

實現以上這些檔案系統並在 Linux 下共存的基礎就是 Linux VFS(Virtual File System 又稱 Virtual Filesystem Switch),即虛擬檔案系統。VFS 作為一個通用的檔案系統,抽象了檔案系統的四個基本概念:檔案、目錄項 (dentry)、索引節點 (inode) 及掛載點,其在核心中為使用者空間層的檔案系統提供了相關的介面(見 圖 3.所示 VFS 在 Linux 系統的架構)。VFS 實現了 open()、read() 等系統調並使得 cp 等使用者空間程式可跨檔案系統。VFS 真正實現了上述內容中:在 Linux 中除程式之外一切皆是檔案。

圖 3. VFS 在系統中的架構
圖 3. VFS 在系統中的架構

Linux VFS 存在四個基本物件:超級塊物件 (superblock object)、索引節點物件 (inode object)、目錄項物件 (dentry object) 及檔案物件 (file object)。超級塊物件代表一個已安裝的檔案系統;索引節點物件代表一個檔案;目錄項物件代表一個目錄項,如裝置檔案 event5 在路徑 /dev/input/event5 中,其存在四個目錄項物件:/ 、dev/ 、input/ 及 event5。檔案物件代表由程式開啟的檔案。這四個物件與程式及磁碟檔案間的關係如圖 4. 所示,其中 d_inode 即為硬連結。為檔案路徑的快速解析,Linux VFS 設計了目錄項快取(Directory Entry Cache,即 dcache)。

圖 4. VFS 的物件之間的處理
圖 4. VFS 的物件之間的處理

Linux 檔案系統中的 inode

在 Linux 中,索引節點結構存在於系統記憶體及磁碟,其可區分成 VFS inode 與實際檔案系統的 inode。VFS inode 作為實際檔案系統中 inode 的抽象,定義了結構體 inode 與其相關的操作 inode_operations(見核心原始碼 include/linux/fs.h)。

清單 10. VFS 中的 inode 與 inode_operations 結構體
 struct inode { 
    ... 
    const struct inode_operations   *i_op; // 索引節點操作
    unsigned long           i_ino;      // 索引節點號
    atomic_t                i_count;    // 引用計數器
    unsigned int            i_nlink;    // 硬連結數目
    ... 
 } 

 struct inode_operations { 
    ... 
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *); 
    int (*link) (struct dentry *,struct inode *,struct dentry *); 
    int (*unlink) (struct inode *,struct dentry *); 
    int (*symlink) (struct inode *,struct dentry *,const char *); 
    int (*mkdir) (struct inode *,struct dentry *,int); 
    int (*rmdir) (struct inode *,struct dentry *); 
    ... 
 }

如清單 10. 所見,每個檔案存在兩個計數器:i_count 與 i_nlink,即引用計數與硬連結計數。結構體 inode 中的 i_count 用於跟蹤檔案被訪問的數量,而 i_nlink 則是上述使用 ls -l 等命令檢視到的檔案硬連結數。或者說 i_count 跟蹤檔案在記憶體中的情況,而 i_nlink 則是磁碟計數器。當檔案被刪除時,則 i_nlink 先被設定成 0。檔案的這兩個計數器使得 Linux 系統升級或程式更新變的容易。系統或程式可在不關閉的情況下(即檔案 i_count 不為 0),將新檔案以同樣的檔名進行替換,新檔案有自己的 inode 及 data block,舊檔案會在相關程式關閉後被完整的刪除。

清單 11. 檔案系統 ext4 中的 inode
 struct ext4_inode { 
    ... 
    __le32  i_atime;        // 檔案內容最後一次訪問時間
    __le32  i_ctime;        // inode 修改時間
    __le32  i_mtime;        // 檔案內容最後一次修改時間
    __le16  i_links_count;  // 硬連結計數
    __le32  i_blocks_lo;    // Block 計數
    __le32  i_block[EXT4_N_BLOCKS];  // 指向具體的 block 
    ... 
 };

清單 11. 展示的是檔案系統 ext4 中對 inode 的定義(見核心原始碼 fs/ext4/ext4.h)。其中三個時間的定義可對應與命令 stat 中檢視到三個時間。i_links_count 不僅用於檔案的硬連結計數,也用於目錄的子目錄數跟蹤(目錄並不顯示硬連結數,命令 ls -ld 檢視到的是子目錄數)。由於檔案系統 ext3 對 i_links_count 有限制,其最大數為:32000(該限制在 ext4 中被取消)。嘗試在 ext3 檔案系統上驗證目錄子目錄及普通檔案硬連結最大數可見清單 12. 的錯誤資訊。因此實際檔案系統的 inode 之間及與 VFS inode 相較是有差異的。

清單 12. 檔案系統 ext3 中 i_links_count 的限制
 # ./dirtest.sh 
 mkdir: cannot create directory `dir_31999': Too many links 

 # ./linkcount.sh 
 ln: failed to create hard link to `old.file': Too many links

結束語

本文最初描述了 Linux 系統中檔案與目錄被引入的原因及 Linux 處理檔案的方式,然後我們通過區分硬連結與軟連結的不同,瞭解 Linux 中的索引節點的相關知識,並以此引出了 inode 的結構體。索引節點結構體存在在於 Linux VFS 以及實際檔案系統中,VFS 作為通用檔案模型是 Linux 中“一切皆是檔案”實現的基礎。文章並未深入 Linux VFS,也沒涉及實際檔案系統的實現,文章只是從 inode 瞭解 Linux 的檔案系統的相關內容。若想深入檔案系統的內容,檢視核心文件 Documentation/filesystems/ 是一個不錯的方式。

參考資料

學習

討論


相關文章