Linux下的連結檔案

來源:互聯網
上載者:User

Linux下的連結檔案
幾個基本概念

Linux下的連結檔案可以分為永久連結(hard link)與軟連結(soft link)。要理解它們,必須先要理解幾個基本概念。

  • inode

檔案除了純資料本身之外,還必須包含有對這些純資料的管理資訊,如檔案名稱、存取權限、檔案的屬主以及該檔案的資料所對應的磁碟塊等等,這些管理資訊稱之為中繼資料(mata data),儲存在檔案的inode節點之中。我們可以通過stat命令查看一個檔案的inode資訊:

$ stat /etc/passwd  File: "/etc/passwd"  Size: 936             Blocks: 8          IO Block: 4096   普通檔案Device: fd00h/64768d    Inode: 137143      Links: 1Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)Access: 2016-08-05 23:01:39.905999995 +0800Modify: 2016-07-15 16:36:12.802999997 +0800Change: 2016-07-15 16:36:12.809000014 +0800$ ls -l /etc/passwd-rw-r--r-- 1 root root 936 7月  15 16:36 /etc/passwd

這裡我們查看了/etc/passwd檔案的中繼資料資訊。ls -l命令也會列出一些檔案的中繼資料資訊(由左至右分別為:許可權、永久連結數、屬主、屬組、檔案大小、最近更改時間、檔案名稱),但相比之下,stat命令輸出的資訊更加完整。我們注意到,stat輸出的資訊中,檔案有三個時間戳記:最近訪問、最近更改和最近改動,對應於英文分別為Access、Modify和Change。 Access time比較好理解,當每次訪問這個檔案的資料(注意,不是中繼資料),這個時間就會更新。比如用cat或者more命令讀取檔案內容時,會更新access time,而用ls或者stat命令,由於只是訪問了檔案的inode,所以不會更新access time值。Modify time是檔案資料最後一次被修改時間,比如用vim編輯檔案後儲存檔案,此時就會更新該檔案modify time。Change time是檔案中繼資料(即inode)最後一次被修改的時間,比如用chown命令修改檔案的屬主,此時就會更新檔案的change time。

其實最初當我們建立分區並用mkfs.ext4等命令建立文系統的時候,就已經在檔案系統的固定地區保留了inode節點區。我們可以通過df -i命令查看某檔案系統inode節點地區的大小及使用方式:

# df -ih /dev/mapper/pdc_bcfaffjfaj2檔案系統                    Inode 已用(I) 可用(I) 已用(I)% 掛載點                                                            /dev/mapper/pdc_bcfaffjfaj2   18M    127K     18M       1% /home   

可以看到,在筆者的Linux Mint17.3系統中,分區/dev/mapper/pdc_bcfaffjfaj2共保留了18M的inode地區,這個地區目前已經使用了127K。有沒有可能出現某分區尚有空間而inode地區已用完的情況呢?有的。當小檔案特別多的時候就會出現這種情況!這個時候即使檔案系統還有空間可用,但我們仍然無法繼續在這個檔案系統內建立新的檔案了。那假如在我的應用程式環境中真的小檔案非常多該怎麼辦?其實我們在建立ext4檔案系統時候是可以手動指定inode地區所佔的比例大小的,可以man mkfs.ext4查看相關的參數和選項,這裡不再詳述。

剛才用stat查看檔案的inode資訊時,我們看到輸出的資訊中有一行Inode: 137143,這個是/etc/passwd檔案的inode號。每個inode都有一個全檔案系統唯一的inode號,作業系統核心正是通過inode號而非檔案名稱來識別不同的檔案。檔案名稱僅僅是為了方便使用者使用而已,核心是通過檔案名稱找到inode,然後通過inode訪問實際檔案資料的。有沒有可能有多個檔案名稱對應於同一個inode呢?有的,這樣就產生了所謂永久連結檔案。

  • dentry

雖然每個檔案對應了唯一的inode號,但inode號是雜亂而毫無意義的,不方面使用者記憶和使用,我們希望對每個檔案取一個有意義的檔案名稱。現代檔案系統提供的一個準系統是按名存取,所以我們還需要建立檔案名稱到inode號的對應,這就引出了目錄項(directory entry即dentry)的概念。在Linux檔案系統中有一類特殊的檔案稱為“目錄”,目錄就儲存了該目錄下所有檔案的檔案名稱到inode號的對應關係,這裡的每個對應關係就稱為一個dentry。而Linux把所有的檔案和目錄構建成了一個倒立的樹狀結構,這樣,我們只要確定了根目錄的inode號,就可以對整個檔案系統進行按名存取了。

  • hard link

永久連結的實質是現有檔案在分類樹中的另一個入口。也就是說,永久連結與原檔案是分居於不同或相同目錄下的的dentry而已,它們指向同一個inode,對應於相同的磁碟資料區塊(data block),具有相同的存取權限、屬性等。簡而言之,永久連結其實就是給現有的檔案起了一個別名。如果把檔案系統比喻成一本書的話,永久連結就是在書本的目錄中,有兩個目錄項指向了同一頁碼的同一章節。

永久連結的優點是幾乎不佔磁碟空間(因為僅僅是增加了一個目錄項而已),但是這一優點相對於軟連結其實並不明顯(因為軟連結佔用的磁碟空間也很少)。另外,永久連結有以下一些局限:1、不能跨檔案系統建立永久連結。原因很簡單,inode號只有在一個檔案系統內才能保證是唯一的,如果跨越檔案系統則inode號就可能重複。2、不能對目錄建立永久連結。原因我在稍後解釋。正因為永久連結的這些局限,加之軟連結更加易於管理,所以軟連結更加常用。這一點在本文中舉的例子也可以看出,幾乎都是軟連結的例子。

  • soft link

軟連結又稱為符號連結(symbolic link),簡寫為“symlink”。與永久連結僅僅是一個目錄項不同,軟串連本身也是檔案,不過這個檔案的內容是另一個檔案名稱的指標。當Linux訪問軟連結時,它會循著指標找出含有實際資料的目標檔案。我們還以書本來打比方,軟連結是書本裡的某一章節,不過這一章節什麼內容都沒有,只有一行字“轉某某章某某頁”。

軟連結可以跨越檔案系統指向另一個分區的檔案,甚至可以跨越主機指向遠程主機的一個檔案,也可以指向目錄。在ls -l輸出的檔案清單中,第一個欄位有“l”字樣者表示該檔案是符號連結。

$ ls -ltotal 0lrwxrwxrwx 1 wjm wjm 11 Aug 10 00:51 hh -> /etc/passwd

我們看到,軟連結的許可權為777,即所有許可權都是開放的,實際上你也無法使用chmod命令修改其許可權,但是實際檔案的保護許可權仍然起作用。

另外,符號連結可以指向不存在的檔案(可能是原來指向的檔案被刪除了,或者指向的檔案系統尚未掛載,或者最初建立該符號連結的時候就指向了一個不存在的檔案等等),我們稱這種狀態為“斷裂”(broken)。與之相對的是,永久連結是不能指向一個不存在的檔案的。

使用連結有何好處?

我們在此總結使用連結檔案的以下幾個的好處:

  • 保持軟體的相容性

例如,在RHEL6中我們看下面這條命令的輸出:

$ ls -l /bin/shlrwxrwxrwx. 1 root root 4 Jul 15 11:41 /bin/sh -> bash

我們看到,/bin/sh檔案其實是一個指向/bin/bash的符號連結。為什麼要這樣設計?因為幾乎所有的shell script的第一行都是下面這樣:

#!/bin/sh

“#!”符號表示該行指定該指令碼所用的解譯器。#!/bin/sh表示使用Bourne Shell作為解譯器,這是一個早期的Shell。在現代的Linux發行版中通常採用Bourne Again Shell即bash,bash是對sh的改進和增強,而早期的Bourne Shell在系統的中根本不存在。為了能夠順利的運行指令碼而不必修改shell script,我們只需要建立一個軟連結/bin/sh讓其指向/bin/bash。如此一來,就可以讓bash來解釋原本針對Bourne Shell編寫的指令碼了。

  • 方便軟體的使用

比如我們安裝了一個大型軟體Matlab,它可能預設安裝在/usr/opt/Matlab目錄下,它的可執行檔位置在/usr/opt/Matlab/bin目錄下,除非你在這個路徑加入到PATH環境變數裡,否則每次運行這個軟體你都需要輸入一長串的路徑很不方便。你還可以這樣做:

$ ln -s /usr/opt/Matlab/bin/matlab ~/bin/matlab

通過在你的~/bin下建立一個符號連結(~/bin系統預設已經包含在PATH環境變數裡的),今後在命令列下無需輸入完整路徑,只需輸入matlab即可。

  • 維持舊的操作習慣

比如在SuSE中,啟動指令碼的位置是放在/etc/init.d目錄下,而在RedHat的發行版中,是放在/etc/init.d/rc.d目錄下。為了避免因為從SuSE轉換到RedHat系統而導致管理員找不到位置的情況,我們可以建立一個符號連結/etc/init.d使其指向/etc/init.d/rc.d即可。事實上,RedHat發行版也正是這樣做的:

$ ls -ld /etc/init.d/lrwxrwxrwx. 1 root root 11 Jul 15 11:41 init.d -> rc.d/init.d
  • 方便系統管理

最讓人印象深刻的一個例子應該是/etc/rc.d/rcX.d目錄下的符號連結了(X為0~7數字)。

$ ls -l /etc/rc.d/total 60drwxr-xr-x. 2 root root  4096 Jul 15 16:36 init.d-rwxr-xr-x. 1 root root  2617 Nov 23  2013 rcdrwxr-xr-x. 2 root root  4096 Jul 15 16:36 rc0.ddrwxr-xr-x. 2 root root  4096 Jul 15 16:36 rc1.ddrwxr-xr-x. 2 root root  4096 Jul 15 16:36 rc2.ddrwxr-xr-x. 2 root root  4096 Jul 15 16:36 rc3.ddrwxr-xr-x. 2 root root  4096 Jul 15 16:36 rc4.ddrwxr-xr-x. 2 root root  4096 Jul 15 16:36 rc5.ddrwxr-xr-x. 2 root root  4096 Jul 15 16:36 rc6.d-rwxr-xr-x. 1 root root   220 Nov 23  2013 rc.local-rwxr-xr-x. 1 root root 19688 Nov 23  2013 rc.sysinit

init.d/目錄下有許多用於啟動、停止系統服務的指令碼,如sshd、crond等。這些指令碼可以接受一個參數,代表要啟動(start)或停止(stop)服務。為了決定在某個運行層級運行哪些指令碼及傳遞給這些指令碼哪些參數,RedHat設計了一個額外的目錄機制,即rc0.d到rc6.d的7個目錄,每個目錄對應一個運行層級。如果在某運行層級下需要啟動某服務或者需要停止某服務,就在對應的rcX.d目錄下建立一個符號連結,指向init.d/目錄下的指令碼。如:

$ ls -l /etc/rc.d/rc3.dtotal 0lrwxrwxrwx. 1 root root 19 Jul 15 11:42 K10saslauthd -> ../init.d/saslauthdlrwxrwxrwx. 1 root root 20 Jul 15 11:42 K50netconsole -> ../init.d/netconsolelrwxrwxrwx. 1 root root 21 Jul 15 11:42 K87restorecond -> ../init.d/restorecondlrwxrwxrwx. 1 root root 15 Jul 15 11:42 K89rdisc -> ../init.d/rdisclrwxrwxrwx. 1 root root 22 Jul 15 11:44 S02lvm2-monitor -> ../init.d/lvm2-monitorlrwxrwxrwx. 1 root root 19 Jul 15 11:42 S08ip6tables -> ../init.d/ip6tableslrwxrwxrwx. 1 root root 18 Jul 15 11:42 S08iptables -> ../init.d/iptableslrwxrwxrwx. 1 root root 17 Jul 15 11:42 S10network -> ../init.d/networklrwxrwxrwx. 1 root root 16 Jul 15 11:44 S11auditd -> ../init.d/auditdlrwxrwxrwx. 1 root root 17 Jul 15 11:42 S12rsyslog -> ../init.d/rsyslog... ....

這裡列出了在運行級3下需要啟動並執行服務指令碼及對應的參數,其中符號連結的第一個字母S和K分別表示傳遞參數startstop,後面跟著的兩位元字表示指令碼啟動並執行先後順序。這樣一來,只要在rcX.d目錄下新增或者移除連結,就可以控制各個runlevel需要運行哪些服務指令碼;而如果需要修改某個服務指令碼,只需要編輯init.d/目錄下的檔案(“本尊”),而它可以影響所有rcX.d目錄下的軟連結(“分身”)。這是多麼簡潔而巧妙的設計!

ln命令

我們用ln命令建立永久連結或者軟連結。其文法為:

  • ln [options] file link

此命令的第一種形式會建立一個指向file的新的連結,其中options選項,我們只記住一個就行,-s表示建立軟連結,而預設會建立永久連結。例如:

# ln -s /usr/src/linux-2.6.32 /usr/src/linux

這裡,我們建立了一個符號連結/usr/src/linux,指向真實的Linux原始碼目錄/usr/src/linux-2.6.32。

我們再舉一個例子,示範一下軟連結與永久連結的區別,我們建立一個myfile檔案,然後再建立一個指向該檔案的軟連結myslink和永久連結myhlink:

$ $ echo "an example." > myfile$ ln -s myfile myslink$ ls myfile myhlink

使用stat檢查前述檔案:

$ stat my*  File: `myfile'  Size: 12          Blocks: 8          IO Block: 4096   regular fileDevice: fd00h/64768d    Inode: 11552       Links: 2Access: (0664/-rw-rw-r--)  Uid: (  500/     wjm)   Gid: (  500/     wjm)Access: 2016-08-10 03:59:54.421017669 +0800Modify: 2016-08-10 03:59:54.421017669 +0800Change: 2016-08-10 04:00:08.689000105 +0800  File: `myhlink'  Size: 12          Blocks: 8          IO Block: 4096   regular fileDevice: fd00h/64768d    Inode: 11552       Links: 2Access: (0664/-rw-rw-r--)  Uid: (  500/     wjm)   Gid: (  500/     wjm)Access: 2016-08-10 03:59:54.421017669 +0800Modify: 2016-08-10 03:59:54.421017669 +0800Change: 2016-08-10 04:00:08.689000105 +0800  File: `myslink' -> `myfile'  Size: 6           Blocks: 0          IO Block: 4096   symbolic linkDevice: fd00h/64768d    Inode: 11553       Links: 1Access: (0777/lrwxrwxrwx)  Uid: (  500/     wjm)   Gid: (  500/     wjm)Access: 2016-08-10 04:00:03.784997923 +0800Modify: 2016-08-10 04:00:03.784997923 +0800Change: 2016-08-10 04:00:03.784997923 +0800

仔細觀察myfile和myhlink,發現它們指向同一個inode(inode號同為11552)。永久連結數(Links欄位)同為2,這表示有兩個目錄項指向該inode,每增加一個永久連結Links欄位值就會增加1。而myslink檔案,我們發現它的inode號與前兩個不同,其存取權限為0777。我們刪除myhlink這個永久連結,看看會出現什麼變化?這次我們用ls -il命令來查看:

$ rm myfile$ ll -litotal 411552 -rw-rw-r-- 1 wjm wjm 12 Aug 10 03:59 myhlink11553 lrwxrwxrwx 1 wjm wjm  6 Aug 10 04:00 myslink -> myfile$ cat myhlinkan example.$ cat myslinkcat: myslink: No such file or directory

ls命令的-i選項也可以輸出檔案的inode號。輸出資訊的第三列為永久連結數,我們發現刪除了myfile檔案後,myhlink的永久連結數已經由2變為1了,但是原myfile檔案的資料依然可以通過myhlink這個永久連結訪問,因為永久連結是通過檔案的inode號來訪問檔案資料的。然而通過myslink軟連結卻無法再訪問原myfile檔案的資料了,因為軟連結實質上是一個指向目標檔案的全路徑,這個路徑中任何一個環節斷裂,都會使這個軟連結失效。

追隨連結

自從了軟串連,當你要備份、複製或者移動目錄或者檔案時候,會出現是否要“追隨連結”的問題。如果是,則會複製連結所指向的對象;如果不是,則僅僅操作鏈結接本身。

通常如tarcp之類的命令工具會給出是否追隨連結的選項。如cp,你可以使用-L選項表示追隨連結(複製連結所指向的目標),或者用-P表示不追隨連結(複製連結本身)。如下例:

$ mkdir dir1$ ln -s /tmp/a.txt dir1/slink$ cp -rL dir1 dir2$ ls -l dir2total 0-rw-rw-r-- 1 wjm wjm 0 Aug  6 17:02 slink

這裡我們在dir1目錄下建立了一個軟連結,當用-L選項將其複製到dir2目錄下時,我們看到dir2目錄下的slink現在成為一個普通檔案。如果使用-P選項(儲存連結)複製,則複製後的檔案依然是一個軟連結:

$ cp -rP dir1 dir3$ ls -l dir3total 0lrwxrwxrwx 1 wjm wjm 10 Aug  6 17:07 slink -> /tmp/a.txt

假如沒有明確指定-L或者-P選項,則cp的預設行為將隨版本而定。

目錄的永久連結

前文提到過,無法對一個目錄建立永久連結。但其實目錄是存在永久連結的,只是這個永久連結是系統自動建立的,而我們不能手動建立。當我們用mkdir建立一個空目錄時,會發現這個目錄的永久連結數為2,例如:

$ ls -dl ~drwx------. 6 wjm wjm 4096 Aug 10 04:25 /home/wjm$ cd ~$ mkdir mydir$ ls -dli ~8605 drwx------. 7 wjm wjm 4096 Aug 10 04:25 /home/wjm$ ls -dli mydir11556 drwxrwxr-x 2 wjm wjm 4096 Aug 10 04:25 mydir

原先/home/wjm目錄的永久連結數量為6,當在/home/wjm下建立了一個空目錄mydir後,它的永久連結數量變成了7,而這個空目錄mydir的永久連結數為2。這是為什麼呢?原因是任何一個目錄下,都有兩個隱藏的永久連結:

ls -ali mydirtotal 811556 drwxrwxr-x  2 wjm wjm 4096 Aug 10 04:25 . 8605 drwx------. 7 wjm wjm 4096 Aug 10 04:25 ..

我們看到mydir目錄下有兩個隱藏的永久連結,使用ls的-a選項才能使其列出來。其中一個永久連結是“.”,指向的inode號為11556,就是mydir這個目錄本身的inode號;另一個是“..”,通過inode號我們發現它指向了其父目錄/home/wjm。因此,當建立了空目錄mydir後,mydir的永久連結數為2,而其父目錄的永久連結數加1。所以一個目錄的永久連結數=其子目錄數+2。

這種永久連結是系統自動為我們建立的,而當你試圖手動建立一個指向目錄的永久連結時,系統一定會報錯阻止你這樣做。為什麼呢?

其實在UNIX作業系統的曆史上,對目錄建立永久連結曾經是允許的。但人們發現,這樣做會出現很多問題,尤其是一些對分類樹進行遍曆操作的如fsck、find等命令無法正確執行。在《Unix進階環境編程》中提到作者Steven在自己的系統上做過實驗,結果是:建立目錄永久連結後,檔案系統變得錯誤百出。因為這樣做會破壞檔案系統的樹形結構,可能會使目錄之間出現環。例如:

$ ln ~ ~/mydir/myhdir_linkln: `/home/wjm': hard link not allowed for directory$ ln -s ~ ~/mydir/myhdir_link

這裡第一條命令我們試圖在mydir目錄下建立一個永久連結指向其父目錄,然而失敗了。因為這使得/home/wjm和/home/wjm/mydir兩個目錄之間形成一個環,我們無法再區分這兩者誰是父目錄誰是子目錄了。然而第二條命令建立一個指向其父目錄的軟連結卻可以成功,難道這樣不是同樣形成了一個環嗎?

為什麼軟連結可以指向目錄而永久連結不行呢?根本原因在於軟連結實質上是一個檔案,而永久連結實質上是一個目錄項(dentry)。在linux系統中,每個檔案(目錄也是檔案,軟連結也是檔案)都對應著一個inode結構,其中inode資料結構中包含了檔案類型(目錄,普通檔案,符號串連檔案等等)的資訊,也就是說作業系統在遍曆目錄時可以判斷出符號串連。既然可以判斷出符號串連當然就可以採取一些措施來防範進入死迴圈了,系統在連續遇到8個符號串連後就停止遍曆,這就是為什麼對目錄符號串連不會進入死迴圈的原因了。而“永久連結”本質上是“目錄項”的同義字。當一個目標第一次被建立,就會為它建立一個目錄項,這其實就是永久連結。大多數人常常把“永久連結”聯想成為一個已有的對象建立一個額外的目錄項,但其實是原來的目錄項沒有任何特殊,所有的永久連結都是平等的,所以Linux核心沒有方法能識別出哪個是“原檔案”哪個是“永久連結”。這樣對於由於目錄永久連結而形成的環就無法進行合適的處理。

但是根目錄是一個特例。我們觀察:

$ ls -dli /2 dr-xr-xr-x. 22 root root 4096 Aug 10 00:50 /$ ls -ali /total 102     2 dr-xr-xr-x. 22 root root  4096 Aug 10 00:50 .     2 dr-xr-xr-x. 22 root root  4096 Aug 10 00:50 ..... ...

可見這雷根目錄的inode號為2,而且根目錄下的指向其父目錄的隱藏永久連結(..)也指向了自身。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.