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,因此,两个文件的属性和内容也截然不同,软链接文件的文件内容是源文件的文件名。

 linux文件系统(四)——软连接与硬连接

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()。

上一篇:【阿里云镜像】使用阿里巴巴开源镜像站镜像——Kubernetes 镜像


下一篇:OpenStack混合云的集成问题如何克服?