閱讀57 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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,因此,這個特性決定了硬鏈接無法跨越文件係統,而且我們無法為目錄創建硬鏈接。軟鏈接和硬鏈接不同,首先軟鏈接可以跨越文件係統,其次,鏈接文件和源文件有著不同的inodedentry,因此,兩個文件的屬性和內容也截然不同,軟鏈接文件的文件內容是源文件的文件名。

 

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 {  

        //如果源文件名稱不夠長  

        //那麼直接將其保存在inodei_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;  

    //將鏈接文件的inodedentry關聯並與其父目錄建立關聯  

    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

  上一篇:go linux rpm 卸載,簡單說明
  下一篇:go 使用ViewPager+Fragment來實現帶滾動條的多屏滑動-IndicatorFragmentActivity