声明:本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()。