天天看點

linux檔案系統(四)——軟連接配接與硬連接配接

(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節點);

概述:

    在linux系統中有一種比較特殊的檔案,我們稱之為連結(link),通俗地說,連結就是從一個檔案指向另外一個檔案的路徑。linux中連結分為倆種,硬連結和軟連結。簡單來說,硬連結相當于源檔案和連結檔案在磁盤和記憶體中共享一個inode,是以,連結檔案和源檔案有不同的dentry,是以,這個特性決定了硬連結無法跨越檔案系統,而且我們無法為目錄建立硬連結。軟連結和硬連結不同,首先軟連結可以跨越檔案系統,其次,連結檔案和源檔案有着不同的inode和dentry,是以,兩個檔案的屬性和内容也截然不同,軟連結檔案的檔案内容是源檔案的檔案名。

linux檔案系統(四)——軟連接配接與硬連接配接

   看完前面的關于硬連結和軟連結的介紹以後,接下來我們仔細考究下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))  //如果原始檔案是目錄則出錯(是以不能建立目錄的硬連結)

    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;  

    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的目的。

    使用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)  

    char *from;  

    struct dentry *dentry;  

    from = getname(oldname);  

    //查找建立軟連結父目錄結構,存于nd之中  

    //在上面查找的父目錄下建立軟連接配接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);  

通過代碼可以看到,其基本的函數調用流程和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)  

    error = security_inode_symlink(dir, dentry, oldname);  

//調用具體檔案系統的symlink方法(對應inode對象中的symlink方法)  

    error = dir->i_op->symlink(dir, dentry, oldname);  

最終還是調用了具體檔案系統的symlink函數,如ext3_symlink()。

//ext3建立軟連接配接函數  

//@dir:軟連接配接的父目錄的inode  

//@dentry:軟連接配接的dentry結構  

//@symname:源檔案名稱  

static int ext3_symlink (struct inode * dir,  struct dentry *dentry, const char * symname)  

    struct inode * inode;  

    int l, err, retries = 0;  

    l = strlen(symname)+1;  //l為軟連接配接名字長度

    if (l > dir->i_sb->s_blocksize)   //軟連接配接名字超過對應檔案系統支援的最大名字長度

        return -enametoolong;  

retry:  

                    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);  

分析ext3_symlink()的代碼,抛開日志等子產品不談,我們知道:

1. 代碼中會為連結檔案建立一個inode結構,這在函數ext3_new_inode()中實作,這也是硬連結和軟連結的最大不同;

2. 連結檔案的檔案内容是源檔案的檔案名,而且,如果檔案名不是很長(小于60位元組),會将檔案名直接儲存在inode中,無需為其配置設定資料塊;

3. 最後會将連結檔案的inode與dentry建立關聯,并将連結檔案的dentry寫入到父目錄的資料塊中,調用的是函數ext3_add_nondir()。

繼續閱讀