linux文件係統(四)——軟連接與硬連接
聲明:本Linux文件係統博客,共分四節,是根據網上多個相關博客,以及自己的理解加上相關資料總結而成。(作者:lvyilong316)
1. 特點概述
(1)軟連接可以 跨文件係統 ,硬連接不可以 。實踐的方法就是用共享文件把windows下的 aa.txt文本文檔連接到linux下/root目錄 下 bb,cc . ln -s aa.txt /root/bb 連接成功 。ln aa.txt /root/bb 失敗 。
(2)關於 I節點的問題 。硬連接不管有多少個,都指向的是同一個I節點,會把結點連接數增加 ,隻要結點的連接數不是 0,文件就一直存在 ,不管你刪除的是源文件還是連接的文件 。隻要有一個存在 ,文件就存在 (其實也不分什麼源文件連接文件的 ,因為他們指向都是同一個 I節點)。 當你修改源文件或者連接文件任何一個的時候 ,其他的 文件都會做同步的修改 。軟鏈接不直接使用i節點號作為文件指針,而是使用文件路徑名作為指針。所以 刪除連接文件對源文件無影響,但是 刪除 源文件,連接文件就會找不到要指向的文件 。軟鏈接有自己的inode,並在磁盤上有一小片空間存放路徑名.
(3)軟連接可以對一個不存在的文件名進行連接 。(看到了,上麵的那個閃爍的圖標)
(4)軟連接可以對目錄進行連接。而硬鏈接不能.
(5)硬鏈接和軟連接可類比C++中的引用和指針的區別,前者相當於多了一個名字,直接訪問,後者是間接訪問,本身也占用空間(指針空間,inode節點);
2. 軟連接與硬連接實現剖析:
概述:
在linux係統中有一種比較特殊的文件,我們稱之為鏈接(link),通俗地說,鏈接就是從一個文件指向另外一個文件的路徑。linux中鏈接分為倆種,硬鏈接和軟鏈接。簡單來說,硬鏈接相當於源文件和鏈接文件在磁盤和內存中共享一個inode,因此,鏈接文件和源文件有不同的dentry,因此,這個特性決定了硬鏈接無法跨越文件係統,而且我們無法為目錄創建硬鏈接。軟鏈接和硬鏈接不同,首先軟鏈接可以跨越文件係統,其次,鏈接文件和源文件有著不同的inode和dentry,因此,兩個文件的屬性和內容也截然不同,軟鏈接文件的文件內容是源文件的文件名。
2.1硬鏈接實現
看完前麵的關於硬鏈接和軟鏈接的介紹以後,接下來我們仔細考究下linux內核中對硬鏈接和軟鏈接的實現。
使用strace工具,可以發現建立硬鏈接調用的函數是link(),該函數的內核入口為SYSCALL_DEFINE2(),其實就是sys_link()。我們就從這個入口開始一步步跟蹤sys_link()的實現原理。
SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname)
{
return sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0);
}
sys_link()其實調用了函數sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0)。而sys_linkat()的源代碼如下(隻保留最主要代碼):
SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname,
int, newdfd, const char __user *, newname, int, flags)
{
struct dentry *new_dentry;
struct nameidata nd;
struct path old_path;
int error;
char *to;
//1.這個是用來查找目的鏈接名的父目錄的dentry
error = user_path_parent(newdfd, newname, &nd, &to);
//2.如果源和目的不是同一個文件係統,則返回錯誤
if (old_path.mnt != nd.path.mnt)
goto out_release;
//3.為鏈接文件創建一個dentry結構
new_dentry = lookup_create(&nd, 0);
error = mnt_want_write(nd.path.mnt);
error = security_path_link(old_path.dentry, &nd.path, new_dentry);
error = vfs_link(old_path.dentry, nd.path.dentry->d_inode, new_dentry);
}
其實,我們仔細思考+上麵的圖示可以明白,創建硬鏈接所做的事情主要包含:為鏈接文件創建一個dentry,初始化(主要是指初始化新建dentry的inode號);將鏈接文件的dentry寫入父目錄的數據塊中。因此,上麵的代碼頁就顯得一目了然,代碼主要做的事情有:
1. 合法性檢查,前麵我們說硬鏈接不可跨越文件係統,這是因為鏈接文件和源文件共用一個inode,而inode號在同一個文件係統內才有意義;
2. 獲取鏈接文件父目錄的inode結構;
3. 為鏈接文件創建一個dentry結構;
4. 等到一切準備工作就緒以後,初始化鏈接文件dentry結構中的inode號,並添加到父目錄的數據塊中。
上述步驟中的1,2,3在上麵的函數中均有對應,而4的主要工作則是在vfs_link()中進行,其傳入的實參的意義也在代碼中作了較為詳細的說明,vfs_link()的實現如下(簡化代碼):
int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
{ //該函數的第一個參數是原始文件的dentry,第二個參數原始文件所在目錄的inode,第三個參數是新鏈接文件的dentry(包含硬鏈接名字)
struct inode *inode = old_dentry->d_inode; //inode指向原始文件的inode
//檢查是否有創建文件目錄項權限
error = may_create(dir, new_dentry);
if (error)
return error;
//inode中的i_sb指向所在文件係統的超級塊對象
if (dir->i_sb != inode->i_sb)
return -EXDEV;
//是否實現了具體文件係統的link,如ext3_link()
if (!dir->i_op->link)
return -EPERM;
if (S_ISDIR(inode->i_mode)) //如果原始文件是目錄則出錯(所以不能創建目錄的硬鏈接)
return -EPERM;
error = security_inode_link(old_dentry, dir, new_dentry);
//調用具體文件係統的link,如ext3_link()
error = dir->i_op->link(old_dentry, dir, new_dentry);
if (!error)
fsnotify_link(dir, inode, new_dentry);
return error;
}
vfs_link()中主要完成一些參數檢查的任務,最終調用的是具體文件係統的link方法,如ext3文件係統的ext3_link()。
static int ext3_link (struct dentry * old_dentry,
struct inode * dir, struct dentry *dentry)
{ //第一個參數是原始文件的dentry,第二個參數原始文件所在目錄的inode,第三個參數是新鏈接文件的dentry(包含硬鏈接名字),對應inode對象中的link方法
handle_t *handle;
struct inode *inode = old_dentry->d_inode; //inode指向原始文件的inode
int err, retries = 0;
//如果文件上的鏈接數過多,返回Too many links
if (inode->i_nlink >= EXT3_LINK_MAX)
return -EMLINK;
dquot_initialize(dir);
retry:
handle = ext3_journal_start(dir, EXT3_DATA_TRANS_BLOCKS(dir->i_sb) +
EXT3_INDEX_EXTRA_TRANS_BLOCKS);
inode->i_ctime = CURRENT_TIME_SEC;
//將源文件inode上的鏈接數 + 1
inc_nlink(inode);
atomic_inc(&inode->i_count); //原子操作
//將鏈接文件的dentry寫入到其父目錄的數據塊中*********
/*之前一直有一個疑問,硬鏈接的建立就是dentry的建立,而dentry是一個表示關係的動態對象,在硬盤上沒有對應數據,那麼當係統重啟時候已經建立的硬鏈接信息是怎麼維護的呢?看到這裏明白了,硬鏈接的dentry信息被寫到了父目錄的數據塊中*/
err = ext3_add_entry(handle, dentry, inode);
if (!err) {
ext3_mark_inode_dirty(handle, inode);
//將源文件的inode號記錄在鏈接文件dentry中
d_instantiate(dentry, inode);
}
return err;
}
在ext3_link()中完成鏈接的具體工作,拋開一些與日誌相關的內容,我們可以看到主要調用了ext3_add_entry()來將鏈接文件的dentry添加到父目錄的數據塊中,與此同時也會將源文件的inode號記錄在鏈接文件dentry中,這樣便達到了源文件和鏈接文件有著不同的dentry結構,卻共享inode的目的。
2.2 軟鏈接實現
使用strace工具,可以發現建立軟鏈接調用的函數是symlink(),該函數的內核入口為SYSCALL_DEFINE2(symlink,...),其實就是sys_symlink()。我們就從這個入口開始一步步跟蹤sys_symlink()的實現原理。
SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newname)
{
return sys_symlinkat(oldname, AT_FDCWD, newname);
}
sys_symlink()其實調用了函數sys_symlinkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0)。而sys_symlinkat()的源代碼(簡化後)如下:
SYSCALL_DEFINE3(symlinkat, const char __user *, oldname,
int, newdfd, const char __user *, newname)
{
int error;
char *from;
char *to;
struct dentry *dentry;
struct nameidata nd;
from = getname(oldname);
//查找新建軟鏈接父目錄結構,存於nd之中
error = user_path_parent(newdfd, newname, &nd, &to);
//在上麵查找的父目錄下創建軟連接dentry,作為返回值
dentry = lookup_create(&nd, 0); //dentry為新建軟連接父目錄的dentry
error = security_path_symlink(&nd.path, dentry, from);
//實參意義:
//d_inode:鏈接文件父目錄inode結構
//dentry:鏈接文件的dentry結構
//from:源文件名
error = vfs_symlink(nd.path.dentry->d_inode, dentry, from);
return error;
}
通過代碼可以看到,其基本的函數調用流程和sys_linkat()一模一樣,隻是最後調用的是vfs_symlinkat()。而且,參數的意義稍有不談,可參見代碼注釋,vfs_symlinkat()代碼如下:
//建立軟鏈接
//@dir:軟連接父目錄inode
//@dentry:軟連接的dentry
//@oldname:源文件或目錄的名字
int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
{
int error = may_create(dir, dentry);
//新建軟連接所在目錄對應的inode是否實現了具體文件係統的symlink方法
if (!dir->i_op->symlink)
return -EPERM;
error = security_inode_symlink(dir, dentry, oldname);
//調用具體文件係統的symlink方法(對應inode對象中的symlink方法)
error = dir->i_op->symlink(dir, dentry, oldname);
return error;
}
最終還是調用了具體文件係統的symlink函數,如ext3_symlink()。
//ext3建立軟連接函數
//@dir:軟連接的父目錄的inode
//@dentry:軟連接的dentry結構
//@symname:源文件名稱
static int ext3_symlink (struct inode * dir, struct dentry *dentry, const char * symname)
{
handle_t *handle;
struct inode * inode;
int l, err, retries = 0;
l = strlen(symname)+1; //l為軟連接名字長度
if (l > dir->i_sb->s_blocksize) //軟連接名字超過對應文件係統支持的最大名字長度
return -ENAMETOOLONG;
dquot_initialize(dir);
retry:
handle = ext3_journal_start(dir, EXT3_DATA_TRANS_BLOCKS(dir->i_sb) +
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 5 +
EXT3_MAXQUOTAS_INIT_BLOCKS(dir->i_sb));
//為軟連接創建一個新的inode結構
inode = ext3_new_inode (handle, dir, S_IFLNK|S_IRWXUGO);
//如果軟連接名字過長則需要存放在單獨的數據塊中
if (l > sizeof (EXT3_I(inode)->i_data)) {
inode->i_op = &ext3_symlink_inode_operations;
ext3_set_aops(inode);
err = __page_symlink(inode, symname, l, 1);
} else {
//如果源文件名稱不夠長
//那麼直接將其保存在inode的i_data中
inode->i_op = &ext3_fast_symlink_inode_operations;
memcpy((char*)&EXT3_I(inode)->i_data,symname,l);
inode->i_size = l-1;
}
EXT3_I(inode)->i_disksize = inode->i_size;
//將鏈接文件的inode和dentry關聯並與其父目錄建立關聯
err = ext3_add_nondir(handle, dentry, inode);
return err;
}
分析ext3_symlink()的代碼,拋開日誌等模塊不談,我們知道:
1. 代碼中會為鏈接文件創建一個inode結構,這在函數ext3_new_inode()中實現,這也是硬鏈接和軟鏈接的最大不同;
2. 鏈接文件的文件內容是源文件的文件名,而且,如果文件名不是很長(小於60字節),會將文件名直接保存在inode中,無需為其分配數據塊;
3. 最後會將鏈接文件的inode與dentry建立關聯,並將鏈接文件的dentry寫入到父目錄的數據塊中,調用的是函數ext3_add_nondir()。
最後更新:2017-04-03 12:55:33