硬鏈接和軟鏈接
實例
程序打開一個文件,然后解除它的鏈接。執行該程序的進程然后睡眠30秒,接着就終止。
#include"apue.h"
#include<fcntl.h>
int
main(void)
{
if(open("tempfile", O_RDWR)<0)
{
err_sys("open error");
}
if(unlink("tempfile")<0)
{
err_sys("unlink error");
}
printf("file unlinked\n");
sleep(30);
printf("done\n");
exit(0);
}
運行該程序,其結果如下:
$ ls -l tempfile
-rw-rw-r-- 1 fireway fireway 1344111767 10月 16 21:17 tempfile <-----tempfile大小弄得大一些
$ df /home/
文件系統 1K-blocks 已用 可用 已用% 掛載點
/dev/sda1 151650468 29081848 114842132 21% /
$ ./unlink &
[1] 4456
$ file unlinked
ls -l tempfile
ls: 無法訪問tempfile: 沒有那個文件或目錄
$ df /home
文件系統 1K-blocks 已用 可用 已用% 掛載點
/dev/sda1 151650468 29081848 114842132 21% /
$ done
df /home
文件系統 1K-blocks 已用 可用 已用% 掛載點
/dev/sda1 151650468 27769232 116154748 20% /
進程用open或creat創建一個文件,然后再調用unlink,因為該文件仍舊是打開的,所以不會將其內容刪除掉。
只有當進程關閉該文件或者終止時,該文件的內容才被刪除。
硬鏈接與軟鏈接的聯系與區別
文件都有文件名與數據,在 Linux 上被分成兩個部分:
用戶數據 (user data) 與元數據 (metadata)。
用戶數據,即文件數據塊 (data block),數據塊是記錄文件真實內容的地方
元數據則是文件的附加屬性,如文件類型、文件訪問權限位、文件大小、創建時間、所有者、指向文件數據塊的指針等信息。
在 Linux 中,
元數據中的 inode 號(或稱i節點號)才是文件的唯一標識而非文件名。文件名僅是為了方便人們的記憶和使用,
系統或程序通過 inode 號尋找正確的文件數據塊。
圖 1.展示了程序通過文件名獲取文件內容的過程。

圖1. 通過文件名獲取文件內容
i-node是固定長度的記錄項,它包含了有關文件的大部分信息。

圖2. 磁盤、分區和文件系統
在 Linux 系統中查看 inode 號可使用命令 stat 或 ls -i
$ stat chown_restricted.c
文件:"chown_restricted.c"
大小:1625 塊:8 IO 塊:4096 普通文件
設備:801h/2049d Inode:1707228 硬鏈接:1
權限:(0777/-rwxrwxrwx) Uid:( 1000/ fireway) Gid:( 1000/ fireway)
最近訪問:2016-10-23 15:48:00.106111057 +0800
最近更改:2016-10-23 15:47:56.638093860 +0800
最近改動:2016-10-23 15:47:56.686094098 +0800
創建時間:-
$ mv chown_restricted.c mychown_restricted.c
$ stat mychown_restricted.c
文件:"mychown_restricted.c"
大小:1625 塊:8 IO 塊:4096 普通文件
設備:801h/2049d Inode:1707228 硬鏈接:1
權限:(0777/-rwxrwxrwx) Uid:( 1000/ fireway) Gid:( 1000/ fireway)
最近訪問:2016-10-23 15:48:00.106111057 +0800
最近更改:2016-10-23 15:47:56.638093860 +0800
最近改動:2016-10-23 16:39:02.469296496 +0800
創建時間:-
使用命令 mv 移動並重命名文件,其結果不影響文件的用戶數據及inode號。下文會有詳細原因。
為解決文件的共享使用,Linux 系統引入了兩種鏈接:
硬鏈接 (hard link) 與軟鏈接(又稱符號鏈接,symbolic link)。
鏈接為 Linux 系統解決了文件的共享使用,還帶來了隱藏文件路徑、增加權限安全及節省存儲等好處。
硬鏈接
若一個 inode 號對應多個文件名,則稱這些文件為硬鏈接。換言之,硬鏈接就是同一個文件使用了多個別名。
硬鏈接可由命令
link 或
ln 創建。如下是對文件 oldfile 創建硬鏈接。
link oldfile newfile
ln oldfile newfile
由於硬鏈接是有着相同 inode 號僅文件名不同的文件,因此硬鏈接存在以下幾點特性:
- 文件有相同的 inode 及 data block;
- 只能對已存在的文件進行創建;
- 不能交叉文件系統進行硬鏈接的創建;
- 不能對目錄進行創建,只可對文件創建;
- 刪除一個硬鏈接文件並不影響其他有相同 inode 號的文件。
$ ls -li
總用量 0
$ link old.file hard.link <------------------- 只能對已存在的文件創建硬連接
link: 無法創建指向"old.file" 的鏈接"hard.link": 沒有那個文件或目錄
$ echo "This is an original file" > old.file
$ stat old.file
文件:"old.file"
大小:25 塊:8 IO 塊:4096 普通文件
設備:801h/2049d Inode:2371939 硬鏈接:1
權限:(0664/-rw-rw-r--) Uid:( 1000/ fireway) Gid:( 1000/ fireway)
最近訪問:2016-10-23 16:52:51.873409289 +0800
最近更改:2016-10-23 16:52:51.873409289 +0800
最近改動:2016-10-23 16:52:51.873409289 +0800
創建時間:-
$ link old.file hard.link | ls -li <---------- 文件有相同的 inode 號以及 data block
總用量 8
2371939 -rw-rw-r-- 2 fireway fireway 25 10月 23 16:52 hard.link
2371939 -rw-rw-r-- 2 fireway fireway 25 10月 23 16:52 old.file
文件 old.file 與 hard.link 有着相同的 inode 號:
2371939
及文件權限,inode 是隨着文件的存在而存在,因此只有當文件存在時才可創建硬鏈接。
$ ln /dev/input/event5 mylink <---------- 不能交叉文件系統
ln: 無法創建硬鏈接"mylink" => "/dev/input/event5": 無效的跨設備連接
$ ln temp/ mylink <--------------- 不能對目錄進行創建硬連接
ln: "temp/": 不允許將硬鏈接指向目錄
inode 是隨着文件的存在而存在,因此只有當文件存在時才可創建硬鏈接
在stat結構中,鏈接計數包含在st_nlink成員中,其基本的系統數據類型是nlink_t
struct stat
{
mode_t st_mode;/* file type & mode (permissions) */
ino_t st_ino;/* i-node number (serial number) */
dev_t st_dev;/* device number (file system) */
dev_t st_rdev;/* device number for special files */
nlink_t st_nlink;/* number of links */
uid_t st_uid;/* user ID of owner */
gid_t st_gid;/* group ID of owner */
off_t st_size;/* size in bytes, for regular files */
struct timespec st_atim;/* time of last access */
struct timespec st_mtim;/* time of last modification */
struct timespec st_ctim;/* time of last file status change */
blksize_t st_blksize;/* best I/O block size */
blkcnt_t st_blocks;/* number of disk blocks allocated */
};
當 inode 存在且鏈接計數器(link count)不為 0 時。inode 號僅在各文件系統下是唯一的,當 Linux 掛載多個文件系統后將出現 inode 號重復的現象,
因為目錄項中的i節點編號指向同一文件系統中的相應的i節點,一個目錄項不能指向另一個文件系統的i節點。這就是為什么ln(1)命令不能跨越文件系統的原因。
只有當鏈接計數減少至0時,才可刪除該文件(也就是可以釋放該文件占用的數據塊),這也是為什么刪除一個目錄項的函數被稱之為
unlink而不是
delete的原因。
當在不更換文件系統時,為一個文件重命名
mv(1)時,該文件的實際內容並未移動,只需要構造一個指向現有i節點的新目錄項,並刪除老的目錄項。鏈接數不會改變。
POSIX.1常量LINK_MAX指定了一個文件連接數的最大值。
stat結構中的大多數信息都取自i節點。只有兩項重要數據存放在目錄項中:文件名和i節點編號,其他的數據項並不是我們本篇內容關心的。i節點編號的數據類型是
ino_t。
以一個目錄文件的鏈接計數為例子,假定我們在工作目錄中構造了一個目錄
$ mkdir testdir
1) 編號為2549的i節點,其類型字段表示它是一個目錄,鏈接計數為2。
2) 編號為1267的i節點,其類型字段表示它是一個目錄,鏈接數大於或等於3。

圖3. 創建了目錄testdir后的文件系統實例
軟鏈接
若文件用戶數據塊中存放的內容是另一文件的路徑名的指向,則該文件就是軟連接(也叫符號鏈接 symbolic link)
軟鏈接就是一個普通文件,只是數據塊內容有點特殊。
軟鏈接有着自己的 inode 號以及用戶數據塊。
軟 鏈接一般用於將一個文件或整個目錄結構移到系統中另一個位置
軟鏈接有如下的特性:
- 軟鏈接有自己的文件屬性及權限等;
- 可對不存在的文件或目錄創建軟鏈接;
- 軟鏈接可交叉文件系統;
- 軟鏈接可對文件或目錄創建;
- 創建軟鏈接時,鏈接計數 link count 不會增加;
- 刪除軟鏈接並不影響被指向的文件,但若被指向的原文件被刪除,則相關軟連接被稱為死鏈接(即 dangling link,若被指向路徑文件被重新創建,死鏈接可恢復為正常的軟鏈接)。
$ ls -li
總用量 0
$ ln -s old.file soft.link
$ ls -liF
總用量 0
2371941 lrwxrwxrwx 1 fireway fireway 8 10月 23 17:13 soft.link -> old.file
$ cat soft.link <-------- 由於被指向的文件不存在,此時的軟鏈接 soft.link 就是死鏈接
cat: 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 <-------------------- 對不存在的目錄創建軟鏈接
$ ll
總用量 8
drwxrwxr-x 2 fireway fireway 4096 10月 23 17:18 ./
drwxr-xr-x 16 fireway fireway 4096 10月 23 17:18 ../
lrwxrwxrwx 1 fireway fireway 7 10月 23 17:18 soft.link.dir -> old.dir
$ tree . -F --inodes
.
├── [2371945] old.dir/
│ └── [2371946] test/
└── [2371945] soft.link.dir -> old.dir/
3 directories, 0 files
當使用以名字引用文件的函數時,應當了解該函數是否處理軟鏈接。如若該函數具有處理軟鏈接的功能,則其路徑名參數引用由軟鏈接指向的文件。否則,一個路徑名參數引用軟鏈接本身。
函數 | 不跟隨符號鏈接 | 跟隨符號鏈接 |
access | • | |
chdir | • | |
chmod | • | |
chown | • | |
creat | • | |
exec | • | |
lchown | • | |
link | • | |
lstat | • | |
open | • | |
opendir | • | |
pathconf | • | |
readlink | • | |
remove | • | |
rename | • | |
stat | • | |
truncate | • | |
unlink | • |
表1 各個函數對符號鏈接的處理
上面的表中有一個例外,同時用O_CREAT和O_EXCL兩者調用
open函數。在此情況下,若路徑名是軟鏈接,
open將出錯返回,
errno設置為EEXIST。這種處理方式的意圖是堵塞一個安全性漏洞,以防止具有特權的進程被誘騙寫錯誤的文件。
用
open打開文件時,如果傳遞給
open函數的路徑名指定一個符號鏈接,那么
open跟隨此鏈接到達所指定的文件。若此符號鏈接所指向的文件並不存在,則
open返回出錯,表示它不能打開該文件。這可能會是不熟悉符號鏈接的用戶感到迷惑。
鏈接相關命令
在 Linux 中查看當前系統已掛着的文件系統類型,除上述使用的命令 df,還可使用命令 mount 或查看文件 /proc/mounts。
$ mount
/dev/sda1 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)
.......
.......
gvfsd-fuse on /run/user/112/gvfs type fuse.gvfsd-fuse (rw,nosuid,nodev,user=lightdm)
命令
ls 或
stat 可幫助我們區分軟鏈接與其他文件並查看文件 inode 號,但較好的方式還是使用 find 命令,其不僅可查找某文件的軟鏈接,還可以用於查找相同 inode 的所有硬鏈接
$ find ~ -lname old.file <------------- 查找在路徑~下的文件old.file的軟鏈接
/home/fireway/study/temp2/soft.link
$ find ~ -samefile ~/study/temp/old.file <----------- 查看路徑~有相同inode的所有硬鏈接
/home/fireway/study/temp/old.file
/home/fireway/study/temp/hard.link
$ find /home -inum 2371939
/home/fireway/study/temp/old.file
/home/fireway/study/temp/hard.link
$ find /home/fireway/study/temp2 -type l -ls <---- 列出路徑/home/fireway/study/temp2下的所有軟鏈接文件
2371941 0 lrwxrwxrwx 1 fireway fireway 8 10月 23 17:13 /home/fireway/study/temp2/soft.link -> old.file
Linux VFS
Linux 有着極其豐富的文件系統,大體上可分如下幾類:
- 網絡文件系統,如 nfs、cifs 等;
- 磁盤文件系統,如 ext4、ext3 等;
- 特殊文件系統,如 proc、sysfs、ramfs、tmpfs 等。
實現以上這些文件系統並在 Linux 下共存的基礎就是Linux VFS(Virtual File System)
VFS 作為一個通用的文件系統,抽象了文件系統的四個基本概念:文件、目錄項 (dentry)、索引節點 (inode) 及掛載點,其在內核中為用戶空間層的文件系統提供了相關的接口,如圖4

圖 4. VFS 在系統中的架構
VFS 實現了 open()、read() 等系統調並使得 cp 等用戶空間程序可跨文件系統。VFS 真正實現了上述內容中:
在 Linux 中除進程之外一切皆是文件。
VFS 存在四個基本對象:超級塊對象 (superblock object)、索引節點對象 (inode object)、目錄項對象 (directory entry object, 或dentry boject) 及文件對象 (file object)。
- 超級塊對象代表一個已安裝的文件系統;
- 索引節點對象代表一個文件;
- 目錄項對象代表一個目錄項,如設備文件 event5 在路徑 /dev/input/event5 中,其存在四個目錄項對象:/ 、dev/ 、input/ 及 event5。
- 文件對象代表由進程打開的文件。
這四個對象與進程及磁盤文件間的關系如圖3.所示,其中 d_inode 即為硬鏈接。為文件路徑的快速解析,Linux VFS 設計了目錄項緩存(Directory Entry Cache,即 dcache)。

圖5. VFS 的對象之間的處理
函數link、linkat、unlink、unlinkat和remove用法
創建一個指向現有文件的鏈接的方法是使用link函數或linkat函數。
#include <unistd.h> int link(const char *existingpath, const char *newpath); int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);
Both return: 0 if OK, −1 on error
|
對於
linkat函數,我們需要注意幾點:
- 現有文件是通過efd和existingpath參數指定的,新的路徑名是通過nfd和newpath參數指定的。
- 默認情況下,如果兩個路徑名中的任一個是相對路徑,那么它需要通過相對於相應的文件描述符進行計算。
- 如果兩個文件描述符中的任一個設置了AT_FDCWD,那么相應的路徑名就通過相對於當前目錄進行計算。
- 如果任一路徑名是絕對路徑,相應的文件描述符參數就會被忽略。
當現有文件是符號鏈接時,由flag參數決定linkat函數是創建指向現有符號鏈接的鏈接還是創建指向現有符號鏈接所指向的文件的鏈接。
- flag設置了AT_SYMLINK_FOLLOW標志,就創建指向符號鏈接目標的鏈接。
- flag沒有設置這個標志,則創建一個指向符號鏈接本身的鏈接。
為了刪除一個現有的目錄項,可以調用unlink函數。
#include <unistd.h> int unlink(const char *pathname); int unlinkat(int fd, const char *pathname, int flag);
Both return: 0 if OK, −1 on error
|
如果對該文件還有其他的鏈接,則仍可通過其他的鏈接訪問該文件的數據。
如果出錯,則不對該文件做任何更改。
如果pathname是符號鏈接,那么unlink刪除該符號鏈接,而不是刪除由該鏈接所引用的文件。給出符號鏈接名的情況下,沒有一個函數能刪除由該鏈接所引用的文件。
如果文件系統支持的話,超級用戶可以調用
unlink,其參數
pathname指定一個目錄。但是通常應當使用
rmdir函數。
我們也可以使用
remove函數解除對一個文件或目錄的鏈接。對於文件,
remove的功能與
unlink相同。對於目錄,
remove的功能與
rmdir相同。
#include <stdio.h> int remove(const char *pathname);
Returns: 0 if OK, −1 on error
|
創建和讀取軟鏈接
可以使用
symlink或者
symlinkat函數創建一個符號鏈接。
#include <unistd.h> int symlink(const char *actualpath, const char *sympath); int symlinkat(const char *actualpath, int fd, const char *sympath);
Both return: 0 if OK, −1 on error
|
函數創建了一個指向了
actualpath的新目錄項
sympath。
symlinkat函數跟symlink函數類似,但
sympath參數根據相對於打開文件描述符fd引用的目錄進行計算。如果
sympath參數指定的是絕對路徑或者fd參數設置了AT_FDCWD值,那么symlinkat就等同於symlink函數。
要打開軟鏈接本身,並讀取鏈接中的名字,使用readlink和readlinkat函數,而open函數會跟隨軟鏈接。
#include <unistd.h> ssize_t readlink(const char* restrict pathname, char *restrict buf, size_t bufsize); ssize_t readlinkat(int fd, const char* restrict pathname, char *restrict buf, size_t bufsize);
Both return: number of bytes read if OK, −1 on error
|
兩個函數組合了open、read和close的所有操作。如果函數成功執行,則返回讀入buf的字節數。在buf中返回的軟鏈接的內容不以null字節終止。
當pathname參數指定的是絕對路徑或者fd參數設置AT_FDCWD,readlinkat函數和readlink相同。
當fd參數是一個打開目錄的有效文件描述符並且pathname參數是相對路徑,則readlinkat函數計算相對於由fd代表的打開目錄的路徑名。
參考
理解 Linux 的硬鏈接與軟鏈接
http://www.ibm.com/developerworks/cn/linux/l-cn-hardandsymb-links/#fig2
UNIX環境高級編程(第三版)- 4.14 文件系統
UNIX環境高級編程(第三版)- 4.15 函數link、linkat、unlink、unlinkat和remove
UNIX環境高級編程(第三版)- 4.17 符號鏈接
UNIX環境高級編程(第三版)- 4.18 創建和讀取符號鏈接