Linux硬链接和软连接实现剖析

概述

    在linux系统中有一种比较特殊的文件,我们称之为链接(link),通俗地说,链接就是从一个文件指向另外一个文件的路径。linux中链接分为俩种,硬链接和软链接。简单来说,硬链接相当于源文件和链接文件在磁盘和内存中共享一个inode,因此,链接文件和源文件有不同的dentry,因此,这个特性决定了硬链接无法跨越文件系统,而且我们无法为目录创建硬链接。软链接和硬链接不同,首先软链接可以跨越文件系统,其次,链接文件和源文件有着不同的inode和dentry,因此,两个文件的属性和内容也截然不同,软链接文件的文件内容是源文件的文件名。

Linux硬链接和软连接实现剖析_第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;

	if ((flags & ~AT_SYMLINK_FOLLOW) != 0)
		return -EINVAL;

	error = user_path_at(olddfd, oldname,
			     flags & AT_SYMLINK_FOLLOW ? LOOKUP_FOLLOW : 0,
			     &old_path);
	if (error)
		return error;

	//这个是用来查找目的链接名的父目录的dentry
	error = user_path_parent(newdfd, newname, &nd, &to);
	if (error)
		goto out;
	error = -EXDEV;
	//如果源和目的不是同一个文件系统,则返回错误	
	if (old_path.mnt != nd.path.mnt)
		goto out_release;
	//为链接文件创建一个dentry结构
	new_dentry = lookup_create(&nd, 0);
	error = PTR_ERR(new_dentry);
	if (IS_ERR(new_dentry))
		goto out_unlock;
	error = mnt_want_write(nd.path.mnt);
	if (error)
		goto out_dput;
	error = security_path_link(old_path.dentry, &nd.path, new_dentry);
	if (error)
		goto out_drop_write;
	error = vfs_link(old_path.dentry, nd.path.dentry->d_inode, new_dentry);
out_drop_write:
	mnt_drop_write(nd.path.mnt);
out_dput:
	dput(new_dentry);
out_unlock:
	mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
out_release:
	path_put(&nd.path);
	putname(to);
out:
	path_put(&old_path);

	return error;
}

其实,我们仔细思考+上面的图示可以明白,创建硬链接所做的事情主要包含:为链接文件创建一个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)
{
	struct inode *inode = old_dentry->d_inode;
	int error;

	if (!inode)
		return -ENOENT;
	//检查是否有创建文件目录项权限
	error = may_create(dir, new_dentry);
	if (error)
		return error;

	if (dir->i_sb != inode->i_sb)
		return -EXDEV;

	/*
	 * A link to an append-only or immutable file cannot be created.
	 */
	if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
		return -EPERM;
	//调用具体文件系统的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);
	if (error)
		return error;

	mutex_lock(&inode->i_mutex);
	error = dir->i_op->link(old_dentry, dir, new_dentry);
	mutex_unlock(&inode->i_mutex);
	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)
{
	handle_t *handle;
	struct inode *inode = old_dentry->d_inode;
	int err, retries = 0;

	//如果文件上的链接数过多,返回Too many links
	if (inode->i_nlink >= EXT3_LINK_MAX)
		return -EMLINK;

	dquot_initialize(dir);

	/*
	 * Return -ENOENT if we've raced with unlink and i_nlink is 0.  Doing
	 * otherwise has the potential to corrupt the orphan inode list.
	 */
	if (inode->i_nlink == 0)
		return -ENOENT;

retry:
	handle = ext3_journal_start(dir, EXT3_DATA_TRANS_BLOCKS(dir->i_sb) +
					EXT3_INDEX_EXTRA_TRANS_BLOCKS);
	if (IS_ERR(handle))
		return PTR_ERR(handle);

	if (IS_DIRSYNC(dir))
		handle->h_sync = 1;

	inode->i_ctime = CURRENT_TIME_SEC;
	//将源文件inode上的链接数 + 1
	inc_nlink(inode);
	atomic_inc(&inode->i_count);

	//将链接文件的dentry写入到其父目录的数据块中
	err = ext3_add_entry(handle, dentry, inode);
	if (!err) {
		ext3_mark_inode_dirty(handle, inode);
		d_instantiate(dentry, inode);
	} else {
		drop_nlink(inode);
		iput(inode);
	}
	ext3_journal_stop(handle);
	if (err == -ENOSPC && ext3_should_retry_alloc(dir->i_sb, &retries))
		goto retry;
	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)
{
	int error;
	char *from;
	char *to;
	struct dentry *dentry;
	struct nameidata nd;

	from = getname(oldname);
	if (IS_ERR(from))
		return PTR_ERR(from);
	//查找软链接父目录结构,存于nd之中
	error = user_path_parent(newdfd, newname, &nd, &to);
	if (error)
		goto out_putname;
	//在上面查找的父目录下创建软连接dentry,作为返回值
	dentry = lookup_create(&nd, 0);
	error = PTR_ERR(dentry);
	if (IS_ERR(dentry))
		goto out_unlock;

	error = mnt_want_write(nd.path.mnt);
	if (error)
		goto out_dput;
	error = security_path_symlink(&nd.path, dentry, from);
	if (error)
		goto out_drop_write;
	//实参意义:
	//d_inode:链接文件父目录inode结构
	//dentry:链接文件的dentry结构
	//from:源文件名
	error = vfs_symlink(nd.path.dentry->d_inode, dentry, from);
out_drop_write:
	mnt_drop_write(nd.path.mnt);
out_dput:
	dput(dentry);
out_unlock:
	mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
	path_put(&nd.path);
	putname(to);
out_putname:
	putname(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);

	if (error)
		return error;

	if (!dir->i_op->symlink)
		return -EPERM;

	error = security_inode_symlink(dir, dentry, oldname);
	if (error)
		return error;

	error = dir->i_op->symlink(dir, dentry, oldname);
	if (!error)
		fsnotify_create(dir, dentry);
	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;
	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));
	if (IS_ERR(handle))
		return PTR_ERR(handle);

	if (IS_DIRSYNC(dir))
		handle->h_sync = 1;
	//为软连接创建一个新的inode结构
	inode = ext3_new_inode (handle, dir, S_IFLNK|S_IRWXUGO);
	err = PTR_ERR(inode);
	if (IS_ERR(inode))
		goto out_stop;

	if (l > sizeof (EXT3_I(inode)->i_data)) {
		inode->i_op = &ext3_symlink_inode_operations;
		ext3_set_aops(inode);
		/*
		 * page_symlink() calls into ext3_prepare/commit_write.
		 * We have a transaction open.  All is sweetness.  It also sets
		 * i_size in generic_commit_write().
		 */
		err = __page_symlink(inode, symname, l, 1);
		if (err) {
			drop_nlink(inode);
			unlock_new_inode(inode);
			ext3_mark_inode_dirty(handle, inode);
			iput (inode);
			goto out_stop;
		}
	} 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);
out_stop:
	ext3_journal_stop(handle);
	if (err == -ENOSPC && ext3_should_retry_alloc(dir->i_sb, &retries))
		goto retry;
	return err;
}
分析ext3_symlink()的代码,抛开日志等模块不谈,我们知道:
1. 代码中会为链接文件创建一个inode结构,这在函数ext3_new_inode()中实现,这也是硬链接和软链接的最大不同;
2. 链接文件的文件内容是源文件的文件名,而且,如果文件名不是很长(小于60字节),会将文件名直接保存在inode中,无需为其分配数据块;
3. 最后会将链接文件的inode与dentry建立关联,并将链接文件的dentry写入到父目录的数据块中,调用的是函数ext3_add_nondir()。

你可能感兴趣的:(linux,文件系统,硬链接,软链接)