Linux open系统调用(三)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u010039418/article/details/88061116

注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4

上回说到".“和”.."两个目录项,如果打开的文件目录是一个正常绝对路径呢,如,/home/test.txt,那这个时候就是普通目录的处理了。

static inline int walk_component(struct nameidata *nd, struct path *path,
		int follow)
{
	struct inode *inode;
	int err;
	//.和..的处理
	if (unlikely(nd->last_type != LAST_NORM))
		return handle_dots(nd, nd->last_type);
	//这里就是普通目录的处理,快通道,其实也就是内存里有该dentry缓存的情况
	err = lookup_fast(nd, path, &inode);
	if (unlikely(err)) {
		if (err < 0)
			goto out_err;
		//如果内存中没找到,就只能老老实实调用对应文件系统的查找函数找了
		err = lookup_slow(nd, path);
		if (err < 0)
			goto out_err;

		inode = path->dentry->d_inode;
	}
    ...
}

快通道的查询实际上就是在内存中查找是否有缓存,有的话检查目标dentry信息合法性,确认在查找期间相关信息未发生改变。

static int lookup_fast(struct nameidata *nd,
		       struct path *path, struct inode **inode)
{
	struct vfsmount *mnt = nd->path.mnt;
	struct dentry *dentry, *parent = nd->path.dentry;
	int need_reval = 1;
	int status = 1;
	int err;

	//使用RCU方式查找dentry
	if (nd->flags & LOOKUP_RCU) {
		unsigned seq;
		//在内存中的散列表中根据字符串查找目标dentry
		dentry = __d_lookup_rcu(parent, &nd->last, &seq);
		if (!dentry)
			//内存里没找到,那就只能通过较慢的方式去查找
			goto unlazy;
		//找到该目录,进行一些合法性检查,
		*inode = dentry->d_inode;
		if (read_seqcount_retry(&dentry->d_seq, seq))
			return -ECHILD;
		//d_seq的值用于保证在上面的查询过程中,父目录没有变化
		if (__read_seqcount_retry(&parent->d_seq, nd->seq))
			return -ECHILD;
		nd->seq = seq;
		//如果需要重新检查dentry合法性
		if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
			status = d_revalidate(dentry, nd->flags);
			if (unlikely(status <= 0)) {
				if (status != -ECHILD)
					need_reval = 0;
				goto unlazy;
			}
		}
		//更新临时变量path
		path->mnt = mnt;
		path->dentry = dentry;
		//和之前类似,有可能找到的目录是个挂载点,因此需要找到真正的目录
		if (likely(__follow_mount_rcu(nd, path, inode)))
			return 0;
unlazy:
		//unlazy_walk主要负责由rcu-walk转为ref-walk模式
		if (unlazy_walk(nd, dentry))
			return -ECHILD;
	} else {
		//非RCU方式查找
		dentry = __d_lookup(parent, &nd->last);
	}
	//没找到dentry,那就是fast方式失败,返回通过慢方式查找
	if (unlikely(!dentry))
		goto need_lookup;//非RCU方式也没找到

	//以下和上面通过RCU方式查找类似,如果找到了目标dentry就进行合法性检查
	//以及挂载点跨越等操作
	if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE) && need_reval)
		status = d_revalidate(dentry, nd->flags);
	if (unlikely(status <= 0)) {
		if (status < 0) {
			dput(dentry);
			return status;
		}
		if (!d_invalidate(dentry)) {
			dput(dentry);
			goto need_lookup;
		}
	}

	path->mnt = mnt;
	path->dentry = dentry;
	err = follow_managed(path, nd->flags);
	if (unlikely(err < 0)) {
		path_put_conditional(path, nd);
		return err;
	}
	if (err)
		nd->flags |= LOOKUP_JUMPED;
	*inode = path->dentry->d_inode;
	return 0;

need_lookup:
	//fast方式失败,返回用慢方式处理
	return 1;
}

如果内存中没找到,那么就进入慢通道了,调用lookup_slow()函数。

static int lookup_slow(struct nameidata *nd, struct path *path)
{
	struct dentry *dentry, *parent;
	int err;

	parent = nd->path.dentry;
	BUG_ON(nd->inode != parent->d_inode);
	//使用可进入睡眠状态的互斥锁,这就是为啥称之为slow
	mutex_lock(&parent->d_inode->i_mutex);
	dentry = __lookup_hash(&nd->last, parent, nd->flags);
	mutex_unlock(&parent->d_inode->i_mutex);
	if (IS_ERR(dentry))
		return PTR_ERR(dentry);
	//更新path临时变量
	path->mnt = nd->path.mnt;
	path->dentry = dentry;
	//同样要考虑跨越挂载点的问题
	err = follow_managed(path, nd->flags);
	if (unlikely(err < 0)) {
		path_put_conditional(path, nd);
		return err;
	}
	if (err)
		nd->flags |= LOOKUP_JUMPED;
	return 0;
}

在慢通道中使用互斥锁保护临界区,因此有可能出现睡眠情况,这样就是为什么称之为慢通道的原因。也正是因为存在睡眠的情况,因此有可能在睡眠的时候有其他进程将目标dentry加载到内存,因此需要在内存中查找一次,万一就中奖了呢,反正内存中的操作耗时很小。

static struct dentry *__lookup_hash(struct qstr *name,
		struct dentry *base, unsigned int flags)
{
	bool need_lookup;
	struct dentry *dentry;
	//由于使用了mutex,有可能在睡眠的时候其他进程将该目录加载到内存
	//因此再到内存里找一次,反正内存里耗时小
	dentry = lookup_dcache(name, base, flags, &need_lookup);
	if (!need_lookup)
		return dentry;
	//内存里没找到,调用文件系统的查找函数
	return lookup_real(base->d_inode, dentry, flags);
}

如果睡眠的时候,nothing happend,那就乖乖的按程序走。调用lookup_real()函数,该函数最终会调用对应文件系统的查找函数。

static struct dentry *lookup_real(struct inode *dir, struct dentry *dentry,
				  unsigned int flags)
{
	struct dentry *old;

	/* Don't create child dentry for a dead directory. */
	if (unlikely(IS_DEADDIR(dir))) {
		dput(dentry);
		return ERR_PTR(-ENOENT);
	}
	//调用对应文件系统的查找函数,有可能会调用到底层驱动,读取硬盘数据
	old = dir->i_op->lookup(dir, dentry, flags);
	if (unlikely(old)) {
		dput(dentry);
		dentry = old;
	}
	return dentry;
}

由上可见,慢的原因不仅在于互斥锁的睡眠,还有可能是查找函数调用底层驱动,获取硬盘数据。

到这,普通目录的情况也走完了,此时就剩最后一种情况——符号链接,下次继续。

猜你喜欢

转载自blog.csdn.net/u010039418/article/details/88061116