linux虚拟文件系统(五)-文件打开操作分析

open分析

大家可以使用以下函数打开一个文件,无论是字符设备文件还是已经挂载好的文件系统中的文件:

int open(const char*pathname,int flags,mode_t mode);

其中open这个函数就是使用了open函数进行系统调用,其系统调用为:

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
	if (force_o_largefile())
		flags |= O_LARGEFILE;

	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

do_sys_open函数参数含义:
pathname:字符串类型的文件名称,比如“./a.txt”
flags:以什么样的方式打开文件
mode:为打开文件时赋予的文件用户、用户组的权限

do_sys_open函数分析,文件在fs/open.c中:

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
	struct open_flags op;

	//根据标志位,填充op结构体
	int fd = build_open_flags(flags, mode, &op);
	struct filename *tmp;

	if (fd)
		return fd;

	//把文件路径从用户空间缓冲区复制到内核空间
	tmp = getname(filename);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	//负责分配文件描述符
	fd = get_unused_fd_flags(flags);
	if (fd >= 0) {
		//解析文件路径,得到文件的索引节点,创建文件结构体
		struct file *f = do_filp_open(dfd, tmp, &op);
		if (IS_ERR(f)) {//创建文件结构体失败
			put_unused_fd(fd);//释放fd
			fd = PTR_ERR(f);
		} else {//创建文件结构体
			fsnotify_open(f);//使进程可以使用notify监视文件系统的事件
			fd_install(fd, f);//把刚刚创建的文件添加到进程的打开文件表中
		}
	}
	putname(tmp);//释放存放文件路径的内存
	return fd;
}

do_sys_open首先使用函数build_open_flags根据参数flags和mode填充open_flags 结构体,用于后面的文件创建,然后使用getname函数把文件路径从用户空间缓冲区复制到内核空间,接着使用get_unused_fd_flags和函数分配文件描述符,最后使用do_filp_open函数解析文件路径,得到文件的索引节点,创建文件结构体。如果创建失败则释放fd返回错误;如果成功则通过fsnotify_open函数使能进程可以使用notify监视文件系统的事件并且通过fd_install函数把刚刚创建的文件添加到进程的打开文件表中,返回fd。所以下面主要分析几个函数:

  1. get_unused_fd_flags
  2. do_filp_open

先看看第一个函数,get_unused_fd_flags这个函数,在fs/file.c文件中:

int get_unused_fd_flags(unsigned flags)
{
	return __alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags);
}
EXPORT_SYMBOL(get_unused_fd_flags);

int __alloc_fd(struct files_struct *files,
	       unsigned start, unsigned end, unsigned flags)
{
	unsigned int fd;
	int error;
	struct fdtable *fdt;

	spin_lock(&files->file_lock);//文件上锁
repeat:
	//一个宏,获取文件描述符位图
	fdt = files_fdtable(files);
	fd = start;
	if (fd < files->next_fd)
		fd = files->next_fd;//获取next_fd,也就是上次分配的fd加上一

	//如果fd比fd表小
	if (fd < fdt->max_fds)
		fd = find_next_fd(fdt, fd);//在文件描述符位图中查找一个空闲的fd

	/*
	 * N.B. For clone tasks sharing a files structure, this test
	 * will limit the total number of files that can be opened.
	 */
	error = -EMFILE;
	//如果fd大于进程打开文件数量上限
	if (fd >= end)
		goto out;

	//到这里表示fd是ok的,需要扩大打开文件表
	error = expand_files(files, fd);
	if (error < 0)
		goto out;

	/*
	 * If we needed to expand the fs array we
	 * might have blocked - try again.
	 */
	//上面返回值小于0说明扩展失败
	//这里返回值等于0说明啥也没做,需要再试一次
	if (error)
		goto repeat;

	//记录下一次分配文件描述符的开始尝试位置
	if (start <= files->next_fd)
		files->next_fd = fd + 1;
	
	//在文件描述位图中记录fd已经分配
	__set_open_fd(fd, fdt);
	
	//如果进程设置了O_CLOEXEC,表示使用系统调用execve装在程序的时候关闭文件
	if (flags & O_CLOEXEC)
		__set_close_on_exec(fd, fdt);//在位图中设置fd对应的位
	else
		__clear_close_on_exec(fd, fdt);//在位图中清除fd对应的位
	error = fd;
#if 1
	/* 合理性检查 */
	if (rcu_access_pointer(fdt->fd[fd]) != NULL) {
		printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);
		rcu_assign_pointer(fdt->fd[fd], NULL);
	}
#endif

out:
	spin_unlock(&files->file_lock);
	return error;
}

get_unused_fd_flags函数是通过__alloc_fd来申请文件描述符的,在__alloc_fd中主要做了一下几步,首先使用函数files_fdtable获取文件描述符位图,然后通过find_next_fd函数在文件描述符位图中查找一个空闲的fd,接着使用expand_files函数扩大打开文件表,最后在文件描述位图中记录fd。我们看看expand_files函数:

static int expand_files(struct files_struct *files, unsigned int nr)
	__releases(files->file_lock)
	__acquires(files->file_lock)
{
	struct fdtable *fdt;
	int expanded = 0;

repeat:
	fdt = files_fdtable(files);

	/* 判断是否需要扩展,如果不需要,这里返回0,只有这里会返回0 */
	if (nr < fdt->max_fds)
		return expanded;

	/* 判断能不能扩展 */
	if (nr >= sysctl_nr_open)
		return -EMFILE;

	//根据resize_in_progress参数判断是否回到repeat
	if (unlikely(files->resize_in_progress)) {
		spin_unlock(&files->file_lock);
		expanded = 1;
		wait_event(files->resize_wait, !files->resize_in_progress);
		spin_lock(&files->file_lock);
		goto repeat;
	}

	/* 开始扩展 */
	files->resize_in_progress = true;
	expanded = expand_fdtable(files, nr);
	files->resize_in_progress = false;

	wake_up_all(&files->resize_wait);
	return expanded;
}

expand_files函数也就是进行一些情况判断最后还是通过expand_fdtable来扩展:

static int expand_fdtable(struct files_struct *files, unsigned int nr)
	__releases(files->file_lock)
	__acquires(files->file_lock)
{
	struct fdtable *new_fdt, *cur_fdt;

	spin_unlock(&files->file_lock);
	new_fdt = alloc_fdtable(nr);//分配fdtable内存

	/* make sure all __fd_install() have seen resize_in_progress
	 * or have finished their rcu_read_lock_sched() section.
	 */
	//确保文件使用者不大于1
	if (atomic_read(&files->count) > 1)
		synchronize_sched();

	spin_lock(&files->file_lock);
	if (!new_fdt)
		return -ENOMEM;
	/*
	 * extremely unlikely race - sysctl_nr_open decreased between the check in
	 * caller and alloc_fdtable().  Cheaper to catch it here...
	 */
	//检查分配的空间nr是否足够
	if (unlikely(new_fdt->max_fds <= nr)) {
		__free_fdtable(new_fdt);
		return -EMFILE;
	}
	cur_fdt = files_fdtable(files);
	BUG_ON(nr < cur_fdt->max_fds);
	//把旧的文件表的内容复制到新的文件表上
	copy_fdtable(new_fdt, cur_fdt);
	//文件的指针指向新表
	rcu_assign_pointer(files->fdt, new_fdt);
	//释放旧的文件表
	if (cur_fdt != &files->fdtab)
		call_rcu(&cur_fdt->rcu, free_fdtable_rcu);
	/* coupled with smp_rmb() in __fd_install() */
	smp_wmb();
	return 1;
}

expand_fdtable首先使用alloc_fdtable函数分配一个新的文件表,然后确保文件使用者数量不大于1和分配的空间充足,然后把旧的文件表的内容复制到新的文件表上,最后让文件的指针指向新表并且释放旧的文件表。

然后我们看看第二个函数do_filp_open:

struct file *do_filp_open(int dfd, struct filename *pathname,
		const struct open_flags *op)
{
	struct nameidata nd;//用来解析函数传递参数
	int flags = op->lookup_flags;
	struct file *filp;

	//分配内存,并且根据参数设置nd
	set_nameidata(&nd, dfd, pathname);
	
	//LOOKUP_RCU说明使用RCU方式查找,
	filp = path_openat(&nd, op, flags | LOOKUP_RCU);
	//RCU方式查找不到便使用普通查找
	if (unlikely(filp == ERR_PTR(-ECHILD)))
		filp = path_openat(&nd, op, flags);
	//如果还找不到就要使用LOOKUP_REVAL查找,
	if (unlikely(filp == ERR_PTR(-ESTALE)))
		filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
	restore_nameidata();//
	return filp;
}

do_filp_open主要是通过path_openat进行解析文件路径,得到文件的索引节点,创建文件结构体的,其三次调用path_openat分别是1.优先使用RCU方式查找,如果之前使用过一般RCU就可以找到,找不到再使用普通查找,一般第一次使用都是普通查找,如果还找不到说明本地的目录项已经过期了,需要真正的去到硬件查找,一般是网络文件系统中读取的资源远程被修改了。我们看看path_openat函数,文件位于fs/namei.c中:

static struct file *path_openat(struct nameidata *nd,
			const struct open_flags *op, unsigned flags)
{
	struct file *file;
	int error;

	file = alloc_empty_file(op->open_flag, current_cred());
	if (IS_ERR(file))
		return file;

	//临时文件的打开使用do_tmpfile函数
	if (unlikely(file->f_flags & __O_TMPFILE)) {
		error = do_tmpfile(nd, flags, op, file);
	//O_PATH表示仅仅获取fd,没有真正打开文件,则调用do_o_path
	} else if (unlikely(file->f_flags & O_PATH)) {
		error = do_o_path(nd, flags, file);//
	} else {//普通打开文件的做法
		//初始化nd中的其他参数,通过开头是不是"/"和dfd是否是AT_FDCWD,识别是绝对路径还是相对路径,设置RCU
		const char *s = path_init(nd, flags);
		//link_path_walk:命名解析函数,针对路径循环搜索,直到找到最后一个不是目录的文件
		//do_last:处理打开文件的最后操作
		while (!(error = link_path_walk(s, nd)) &&
			(error = do_last(nd, file, op)) > 0) {
			nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
			s = trailing_symlink(nd);//查看是否为链接文件,链接文件处理
		}
		terminate_walk(nd);//查找完成操作,包括解RCU锁
	}
	if (likely(!error)) {
		if (likely(file->f_mode & FMODE_OPENED))
			return file;
		WARN_ON(1);
		error = -EINVAL;
	}
	fput(file);//初始化一个任务,关闭的时候回调用到____fput
	if (error == -EOPENSTALE) {
		if (flags & LOOKUP_RCU)
			error = -ECHILD;
		else
			error = -ESTALE;
	}
	return ERR_PTR(error);
}

path_openat首先根据文件类型进行文件查找,我们一般的打开操作是要通过path_init初始化路径,然后通过link_path_walk函数针对路径循环搜索,直到找到文件,接着使用do_last进行文件的打开,最后terminate_walk表示查找工作完成了,把path_init过程中分配的一些解析路径的资源释放掉和一些锁解锁了。在返回前会使用fput初始化一个任务,这个任务是____fput,我们会在关闭的时候回调____fput。
fput函数,文件在fs/file_table.c:

void fput(struct file *file)
{
	if (atomic_long_dec_and_test(&file->f_count)) {
		struct task_struct *task = current;

		if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) {
			init_task_work(&file->f_u.fu_rcuhead, ____fput);
			if (!task_work_add(task, &file->f_u.fu_rcuhead, true))
				return;
			/*
			 * After this task has run exit_task_work(),
			 * task_work_add() will fail.  Fall through to delayed
			 * fput to avoid leaking *file.
			 */
		}

		if (llist_add(&file->f_u.fu_llist, &delayed_fput_list))
			schedule_delayed_work(&delayed_fput_work, 1);
	}
}

static void ____fput(struct callback_head *work)
{
	__fput(container_of(work, struct file, f_u.fu_rcuhead));
}

fput函数会调用____fput,而最终调用的是__fput函数。

static void __fput(struct file *file)
{
	struct dentry *dentry = file->f_path.dentry;
	struct vfsmount *mnt = file->f_path.mnt;
	struct inode *inode = file->f_inode;

	if (unlikely(!(file->f_mode & FMODE_OPENED)))
		goto out;

	might_sleep();

	fsnotify_close(file);//通知关闭文件事件,进程可以使用inotify监视文件的事件
	/*
	 * The function eventpoll_release() should be the first called
	 * in the file cleanup chain.
	 */
	//如果进程使用eventpoll监听文件系统的事件,那么把文件从eventpoll中删除
	eventpoll_release(file);
	locks_remove_file(file);//如果进程持有文件锁,那么释放文件锁

	ima_file_free(file);
	if (unlikely(file->f_flags & FASYNC)) {
		if (file->f_op->fasync)
			file->f_op->fasync(-1, file, 0);
	}
	
	//调用具体文件系统类型的release函数
	if (file->f_op->release)
		file->f_op->release(inode, file);
	if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL &&
		     !(file->f_mode & FMODE_PATH))) {
		cdev_put(inode->i_cdev);
	}
	fops_put(file->f_op);//把文件操作结合结构体引用计数减一
	
	//解除file实例和目录项,挂载描述符以及索引节点的关联
	put_pid(file->f_owner.pid);
	if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
		i_readcount_dec(inode);
	if (file->f_mode & FMODE_WRITER) {
		put_write_access(inode);
		__mnt_drop_write(mnt);
	}
	dput(dentry);//释放目录项
	mntput(mnt);//释放挂载描述符
out:
	file_free(file);
}

我们看看path_init函数:

static const char *path_init(struct nameidata *nd, unsigned flags)
{
	const char *s = nd->name->name;

	if (!*s)
		flags &= ~LOOKUP_RCU;
	if (flags & LOOKUP_RCU)
		rcu_read_lock();

	nd->last_type = LAST_ROOT; /* if there are only slashes... */
	nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
	nd->depth = 0;
	
	//flags中表明是从根目录搜寻,
	if (flags & LOOKUP_ROOT) {
		struct dentry *root = nd->root.dentry;
		struct inode *inode = root->d_inode;
		//查看文件权限上是否允许
		if (*s && unlikely(!d_can_lookup(root)))
			return ERR_PTR(-ENOTDIR);
		nd->path = nd->root;
		nd->inode = inode;
		//对nd、RCU和顺序锁进行初始化
		if (flags & LOOKUP_RCU) {
			nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
			nd->root_seq = nd->seq;
			nd->m_seq = read_seqbegin(&mount_lock);
		} else {
			path_get(&nd->path);
		}
		return s;
	}

	nd->root.mnt = NULL;
	nd->path.mnt = NULL;
	nd->path.dentry = NULL;

	nd->m_seq = read_seqbegin(&mount_lock);
	
	//通过字符的开头是"/",也可以表示绝对路径,则也要做相关初始化
	if (*s == '/') {
		set_root(nd);
		if (likely(!nd_jump_root(nd)))
			return s;
		return ERR_PTR(-ECHILD);
	} else if (nd->dfd == AT_FDCWD) {
		if (flags & LOOKUP_RCU) {
			struct fs_struct *fs = current->fs;
			unsigned seq;

			do {
				seq = read_seqcount_begin(&fs->seq);
				nd->path = fs->pwd;
				nd->inode = nd->path.dentry->d_inode;
				nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
			} while (read_seqcount_retry(&fs->seq, seq));
		} else {
			get_fs_pwd(current->fs, &nd->path);
			nd->inode = nd->path.dentry->d_inode;
		}
		return s;
	} else {//开头不是"/",表示使用相对路径
		/* Caller must check execute permissions on the starting path component */
		struct fd f = fdget_raw(nd->dfd);
		struct dentry *dentry;

		if (!f.file)
			return ERR_PTR(-EBADF);

		dentry = f.file->f_path.dentry;

		if (*s && unlikely(!d_can_lookup(dentry))) {
			fdput(f);
			return ERR_PTR(-ENOTDIR);
		}

		nd->path = f.file->f_path;
		if (flags & LOOKUP_RCU) {
			nd->inode = nd->path.dentry->d_inode;
			nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
		} else {
			path_get(&nd->path);
			nd->inode = nd->path.dentry->d_inode;
		}
		fdput(f);
		return s;
	}
}

path_init初始化路径也就是根据nd计算出文件的路径而已。计算出文件的路径后是调用link_path_walk解析文件路径,针对路径循环搜索,直到找到最后一个不是目录的文件,现在看看:

static int link_path_walk(const char *name, struct nameidata *nd)
{
	int err;

	if (IS_ERR(name))
		return PTR_ERR(name);
	while (*name=='/')//如果是/开头,这需要将其+到不是"/"为止
		name++;
	if (!*name)//如果字符是无效值,则异常退出
		return 0;

	/* 到这里,我们可以开始搜寻循环 */
	for(;;) {
		u64 hash_len;
		int type;

		err = may_lookup(nd);//查询文件权限是否允许访问
		if (err)
			return err;
		
		//算出该文件名的哈希值,和文件名长度
		hash_len = hash_name(nd->path.dentry, name);

		type = LAST_NORM;
		//判断文件名是否使用了"."或者"..",是则标明type
		if (name[0] == '.') switch (hashlen_len(hash_len)) {
			case 2:
				if (name[1] == '.') {
					type = LAST_DOTDOT;
					nd->flags |= LOOKUP_JUMPED;
				}
				break;
			case 1:
				type = LAST_DOT;
		}
		
		//文件名没有"."或者"..",则是普通文件或目录,则查看是否有hash缓存表,有表示内存中存在缓存,没有直接退出
		if (likely(type == LAST_NORM)) {
			struct dentry *parent = nd->path.dentry;
			nd->flags &= ~LOOKUP_JUMPED;
			if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
				struct qstr this = { { .hash_len = hash_len }, .name = name };
				err = parent->d_op->d_hash(parent, &this);
				if (err < 0)
					return err;
				hash_len = this.hash_len;
				name = this.name;
			}
		}

		nd->last.hash_len = hash_len;
		nd->last.name = name;
		nd->last_type = type;

		name += hashlen_len(hash_len);
		if (!*name)
			goto OK;
		/*
		 * If it wasn't NUL, we know it was '/'. Skip that
		 * slash, and continue until no more slashes.
		 */
		//当有多个"/"时,则搜索时去掉
		do {
			name++;
		} while (unlikely(*name == '/'));
		if (unlikely(!*name)) {
OK:
			/* 深度为0则文件名字解析结束返回 */
			if (!nd->depth)
				return 0;
			/* 字符串结束则文件名字解析结束返回 */
			name = nd->stack[nd->depth - 1].name;
			if (!name)
				return 0;
			/* 依据刚刚识别的类型,做不同的操作 */
			err = walk_component(nd, WALK_FOLLOW);
		} else {
			/* 依据刚刚识别的类型,做不同的操作 */
			err = walk_component(nd, WALK_FOLLOW | WALK_MORE);
		}
		if (err < 0)
			return err;
		
		//下面是符号连接处理
		if (err) {
			const char *s = get_link(nd);//根据符号链接获取文件名

			if (IS_ERR(s))
				return PTR_ERR(s);
			err = 0;
			if (unlikely(!s)) {
				/* jumped */
				put_link(nd);
			} else {
				nd->stack[nd->depth - 1].name = name;
				name = s;
				continue;
			}
		}
		//普通文件的搜索,搜索失败则返回错误
		if (unlikely(!d_can_lookup(nd->path.dentry))) {
			if (nd->flags & LOOKUP_RCU) {
				if (unlazy_walk(nd))
					return -ECHILD;
			}
			return -ENOTDIR;
		}
	}
}

link_path_walk其实就是在for循环里面解析路径,步骤是这样的,首先在文件的每一层目录中查询文件的访问权限,然后判断每一次层的目录名字,进入下一个循环,直到解析深度为0或者文件字符串结束表示解析完毕。再判断该文件是否为连接文件,如果是连接文件,需要继续解析。还要对每一层的目录使用walk_component函数进行处理:

static int walk_component(struct nameidata *nd, int flags)
{
	struct path path;
	struct inode *inode;
	unsigned seq;
	int err;
	/*
	 * "." and ".." are special - ".." especially so because it has
	 * to be able to know about the current root directory and
	 * parent relationships.
	 */
	if (unlikely(nd->last_type != LAST_NORM)) {
		//处理"."和".."文件名,成功则设置nd->path.dentry和nd->inode,如果父目录是挂载点找到挂载点目录再处理。
		err = handle_dots(nd, nd->last_type);
		if (!(flags & WALK_MORE) && nd->depth)
			put_link(nd);
		return err;
	}
	/*  快速的文件搜索:RCU  */
	err = lookup_fast(nd, &path, &inode, &seq);
	if (unlikely(err <= 0)) {
		if (err < 0)
			return err;
		//慢速的搜索
		path.dentry = lookup_slow(&nd->last, nd->path.dentry,
					  nd->flags);
		if (IS_ERR(path.dentry))
			return PTR_ERR(path.dentry);

		path.mnt = nd->path.mnt;
		err = follow_managed(&path, nd);//dentry管理相关
		if (unlikely(err < 0))
			return err;

		if (unlikely(d_is_negative(path.dentry))) {
			path_to_nameidata(&path, nd);
			return -ENOENT;
		}

		seq = 0;	/* we are already out of RCU mode */
		inode = d_backing_inode(path.dentry);
	}

	return step_into(nd, &path, flags, inode, seq);
}

walk_component首先使用handle_dots处理文件名的.和…,然后通过lookup_fast使用RCU方式快速搜索文件,失败则使用lookup_slow进行慢速的搜索。我们先看看lookup_fast:

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

	/*
	 * Rename seqlock is not required here because in the off chance
	 * of a false negative due to a concurrent rename, the caller is
	 * going to fall back to non-racy lookup.
	 */
	//文件可能存在RCU锁,这表示可能有别的进程使用,则有可能被加载到 dcache 中
	if (nd->flags & LOOKUP_RCU) {
		unsigned seq;
		bool negative;
		//RCU方式搜索,只使用内存屏障作为防护手段
		dentry = __d_lookup_rcu(parent, &nd->last, &seq);
		if (unlikely(!dentry)) {
			if (unlazy_walk(nd))
				return -ECHILD;
			return 0;
		}

		/*
		 * This sequence count validates that the inode matches
		 * the dentry name information from lookup.
		 */
		//此序列计数验证inode是否与查找中的dentry名称信息相匹配
		*inode = d_backing_inode(dentry);
		negative = d_is_negative(dentry);
		
		//在子目录的read_seqcount_begin的内存屏障就足够,这里判断父目录和子目录的seq是否一致
		if (unlikely(read_seqcount_retry(&dentry->d_seq, seq)))
			return -ECHILD;

		/*
		 * This sequence count validates that the parent had no
		 * changes while we did the lookup of the dentry above.
		 *
		 * The memory barrier in read_seqcount_begin of child is
		 *  enough, we can use __read_seqcount_retry here.
		 */
		if (unlikely(__read_seqcount_retry(&parent->d_seq, nd->seq)))
			return -ECHILD;

		*seqp = seq;
		//找到的目录是否需要重新生效,需要就执行denter的对应函数
		status = d_revalidate(dentry, nd->flags);
		if (likely(status > 0)) {
			/*
			 * Note: do negative dentry check after revalidation in
			 * case that drops it.
			 */
			if (unlikely(negative))
				return -ENOENT;
			path->mnt = mnt;
			path->dentry = dentry;
			if (likely(__follow_mount_rcu(nd, path, inode, seqp)))
				return 1;
		}
		if (unlazy_child(nd, dentry, seq))
			return -ECHILD;
		if (unlikely(status == -ECHILD))
			/* we'd been told to redo it in non-rcu mode */
			status = d_revalidate(dentry, nd->flags);
	} else {//普通查找
		//普通查找抵用函数__d_lookup
		dentry = __d_lookup(parent, &nd->last);
		if (unlikely(!dentry))
			return 0;
		//找到的目录是否需要重新生效,需要就执行denter的对应函数
		status = d_revalidate(dentry, nd->flags);
	}
	if (unlikely(status <= 0)) {
		if (!status)
			d_invalidate(dentry);
		dput(dentry);
		return status;
	}
	if (unlikely(d_is_negative(dentry))) {
		dput(dentry);
		return -ENOENT;
	}

	path->mnt = mnt;
	path->dentry = dentry;
	err = follow_managed(path, nd);//dentry管理相关
	if (likely(err > 0))
		*inode = d_backing_inode(path->dentry);
	return err;
}

调用__d_lookup_rcu函数在哈希桶中找到对应的dentry,如果找到了就返回dentry,如果没找到就切换到ref-walk模式继续查)。根据这个dentry得到对应的inode,进行一系列的检查操作,这样是为了确保在读取的时候,并没有其他进程对这些结构进行修改操作(rcu-walk模式并没有加锁),更新的临时变量path,这时候不能直接修改nd变量,因为不能确定这个分量是不是目录,nd记录的信息必须是目录,然后结束。
我们再看看那慢速模式:

static struct dentry *lookup_slow(const struct qstr *name,
				  struct dentry *dir,
				  unsigned int flags)
{
	struct inode *inode = dir->d_inode;
	struct dentry *res;
	inode_lock_shared(inode);//上锁
	res = __lookup_slow(name, dir, flags);
	inode_unlock_shared(inode);//解锁
	return res;
}

static struct dentry *__lookup_slow(const struct qstr *name,
				    struct dentry *dir,
				    unsigned int flags)
{
	struct dentry *dentry, *old;
	struct inode *inode = dir->d_inode;
	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);

	/* Don't go there if it's already dead */
	if (unlikely(IS_DEADDIR(inode)))
		return ERR_PTR(-ENOENT);
again:
	//分配内存
	dentry = d_alloc_parallel(dir, name, &wq);
	if (IS_ERR(dentry))
		return dentry;
	if (unlikely(!d_in_lookup(dentry))) {//普通查找方式成功(第二种方式)
		if (!(flags & LOOKUP_NO_REVAL)) {
			int error = d_revalidate(dentry, flags);
			if (unlikely(error <= 0)) {
				if (!error) {
					d_invalidate(dentry);
					dput(dentry);
					goto again;
				}
				dput(dentry);
				dentry = ERR_PTR(error);
			}
		}
	} else {//普通查找失败,需要进入硬件查找(第三种方式)
		//调用文件系统的lookup函数
		old = inode->i_op->lookup(inode, dentry, flags);
		d_lookup_done(dentry);
		if (unlikely(old)) {
			dput(dentry);
			dentry = old;
		}
	}
	return dentry;
}

看到这里,大家应该就明白了,为什么这种查找方法很慢呢,因为这种查找是互斥操作,进程可能会阻塞。而且进入普通查找可能会失败,失败就要进行硬件上的查找了,也就是网络文件系统遇到比较多那种的情况,当前文件信息失效,远程文件信息被修改了。这个情况还要调用到根据文件系统类型调用相关的lookup函数,这个文件的inode的操作函数初始化是在文件系统类型注册的时候做的。

你可能感兴趣的:(Linux,kernel,linux,open)