Linux下的鏈接文件可以分為硬鏈接(hard link)與軟鏈接(soft link)。要理解它們,必須先要理解幾個基本概念。
文件除了純數據本身之外,還必須包含有對這些純數據的管理信息,如文件名、訪問權限、文件的屬主以及該文件的數據所對應的磁盤塊等等,這些管理信息稱之為元數據(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呢?有的,這樣就產生了所謂硬鏈接文件。
雖然每個文件對應了唯一的inode號,但inode號是雜亂而毫無意義的,不方面用戶記憶和使用,我們希望對每個文件取一個有意義的文件名。現代文件系統提供的一個基本功能是按名存取,所以我們還需要建立文件名到inode號的對應,這就引出了目錄項(directory entry即dentry)的概念。在Linux文件系統中有一類特殊的文件稱為“目錄”,目錄就保存了該目錄下所有文件的文件名到inode號的對應關系,這裡的每個對應關系就稱為一個dentry。而Linux把所有的文件和目錄構建成了一個倒立的樹狀結構,這樣,我們只要確定了根目錄的inode號,就可以對整個文件系統進行按名存取了。
硬鏈接的實質是現有文件在目錄樹中的另一個入口。也就是說,硬鏈接與原文件是分居於不同或相同目錄下的的dentry而已,它們指向同一個inode,對應於相同的磁盤數據塊(data block),具有相同的訪問權限、屬性等。簡而言之,硬鏈接其實就是給現有的文件起了一個別名。如果把文件系統比喻成一本書的話,硬鏈接就是在書本的目錄中,有兩個目錄項指向了同一頁碼的同一章節。
硬鏈接的優點是幾乎不占磁盤空間(因為僅僅是增加了一個目錄項而已),但是這一優點相對於軟鏈接其實並不明顯(因為軟鏈接占用的磁盤空間也很少)。另外,硬鏈接有以下一些局限:1、不能跨文件系統創建硬鏈接。原因很簡單,inode號只有在一個文件系統內才能保證是唯一的,如果跨越文件系統則inode號就可能重復。2、不能對目錄創建硬鏈接。原因我在稍後解釋。正因為硬鏈接的這些局限,加之軟鏈接更加易於管理,所以軟鏈接更加常用。這一點在本文中舉的例子也可以看出,幾乎都是軟鏈接的例子。
軟鏈接又稱為符號鏈接(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分別表示傳遞參數start和stop,後面跟著的兩位數字表示腳本運行的先後順序。這樣一來,只要在rcX.d目錄下新增或者移除鏈接,就可以控制各個runlevel需要運行哪些服務腳本;而如果需要修改某個服務腳本,只需要編輯init.d/目錄下的文件(“本尊”),而它可以影響所有rcX.d目錄下的軟鏈接(“分身”)。這是多麼簡潔而巧妙的設計!
我們用ln命令創建硬鏈接或者軟鏈接。其語法為:
此命令的第一種形式會創建一個指向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文件的數據了,因為軟鏈接實質上是一個指向目標文件的全路徑,這個路徑中任何一個環節斷裂,都會使這個軟鏈接失效。
自從了軟連接,當你要備份、復制或者移動目錄或者文件時候,會出現是否要“追隨鏈接”的問題。如果是,則會復制鏈接所指向的對象;如果不是,則僅僅操作鏈接本身。
通常如tar或cp之類的命令工具會給出是否追隨鏈接的選項。如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,而且根目錄下的指向其父目錄的隱藏硬鏈接(..)也指向了自身。
http://xxxxxx/Linuxjc/1155985.html TechArticle