Linux文件系统之查询文件路径

本文深入分析了Linux系统中路径查找的实现原理,重点讲解了__user_walk、getname及do_path_lookup等关键函数的工作流程,介绍了如何从用户空间路径转换为内核空间的dentry结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<p>这篇博客是基于北京大学操作系统B大作业“linux代码阅读”而写成的,我个人暂时负责阅读linux文件系统查询文件路径这一部分,现现将部分阅读成果发布,由于没有进行最后的整理,逻辑相对不是很清晰,我会在期末考试之后进行详细整理,并且将linux文件系统补充完全。</p><p>
</p><p>//开始阅读!</p><div>
</div>
//1、__userwalk函数的分析



int fastcall __user_walk(const char __user *name, unsigned flags, struct nameidata *nd)  //fast call的意思是前两个参数由寄存器传递 %ecx %edx
{
	return __user_walk_fd(AT_FDCWD, name, flags, nd);
}


//下面是__user_walk函数的参数的分析

	//__user_walk调用了__user_walk_fd, 其中AT_FDCWD=-100
	//调用参数:name指向用户空间中的路径名,flags的内容是一些标志位
	//标志位是对于如何寻找目标的提示,其中LOOKUP_FOLLOW需要一直跟着链接到终点

	 #define LOOKUP_FOLLOW (1)
	 #define LOOKUP_DIRECTORY (2)
	 #define LOOKUP_CONTINUE (4)
	 #define LOOKUP_POSITIVE (8)
	 #define LOOKUP_PARENT (16)
	 #define LOOKUP_NOALT (32)

	//nameidata是一个临时结构,其中包含dentry等
//user_walk函数参数分析结束

//user_walk调用了__user_walk_fd(AT_FDCWD, name, flags, nd);
//其中AT_FDCWD是-100,其他参数与__user_walk相同

//__user_walk_fd的定义:


int fastcall __user_walk_fd(int dfd, const char __user *name, unsigned flags,       //dfd=-100
			    struct nameidata *nd)
{
	char *tmp = getname(name);
	int err = PTR_ERR(tmp);

	if (!IS_ERR(tmp)) {
		err = do_path_lookup(dfd, tmp, flags, nd);
		putname(tmp);
	}
	return err;
}

//先调用getname:getname函数分析开始!!!!

	char * getname(const char __user * filename)
	{
		char *tmp, *result;

		result = ERR_PTR(-ENOMEM);
		tmp = __getname();
		if (tmp)  {
			int retval = do_getname(filename, tmp);

			result = tmp;
			if (retval < 0) {
				__putname(tmp);
				result = ERR_PTR(retval);
			}
		}
		audit_getname(result);
		return result;
	}
	//getname的参数是输入name,也就是路径名如/a/b/file.type
	//这个函数返回一个char*,也就是一个字符串数组,给到临时的字符串指针tmp中
	//这个函数首先定义了tmp和result两个char指针,调用ERR_PTR,参数为-ENOMEM,其中ENOMEM是12,代表out of memory;将值返回到result中
	static inline void *ERR_PTR(long error)
	{
		return (void *) error;
	}
	//这个函数的作用主要是将result定为errorptr,就是设置默认值为error,若后面不符合if中的要求,result没有发生更改,则返回result(error)
	//接下来,作者调用__getname(),并且将返回值交给tmp
	#define __getname()	kmem_cache_alloc(names_cachep, GFP_KERNEL)
	//其中names_cachep是一种结构体的对象:
	struct kmem_cache {
		unsigned int size, align;
		const char *name;
		void (*ctor)(void *, struct kmem_cache *, unsigned long);
		void (*dtor)(void *, struct kmem_cache *, unsigned long);
	};
	//该结构体描述的是一种高速缓存的对象
	//GFP_kernel的值为0
	//kmem_cache_alloc的主要目的是为了动态分配内存
	//则tmp为动态分配得到的内存的指针
	//如果tmp不是NULL,则证明动态内存分配正常,执行if中的内容
	//如果tmp不是NULL:
		//int retval等于do_getname的返回值
		static int do_getname(const char __user *filename, char *page)
	{
		int retval;
		unsigned long len = PATH_MAX;

		if (!segment_eq(get_fs(), KERNEL_DS)) {
			if ((unsigned long) filename >= TASK_SIZE)
				return -EFAULT;
			if (TASK_SIZE - (unsigned long) filename < PATH_MAX)
				len = TASK_SIZE - (unsigned long) filename;
		}

		retval = strncpy_from_user(page, filename, len);
		if (retval > 0) {
			if (retval < len)
				return 0;
			return -ENAMETOOLONG;
		} else if (!retval)
			retval = -ENOENT;
		return retval;
	}
	//输入的参数:filename是输入getname()中的name;page是刚刚分配得到的tmp
	//这段函数的作用就是执行strncpy,将filename中的string copy到分配得到的page里面,如果copy不成功,则返回一个retval,retval值的内容是失败原因对应的long int number
	//回头再接着看getname函数:
		//retval小于零,证明在strncpy过程中发生了错误,就对tmp分配得到的内存区域进行释放操作
		//接着将result变成相对于strcpy对应的错误的指针数值,相当于错误码
	//audit_getname是add result中储存的name到一个list中(list是什么????)
//getname函数分析结束!!!



//int 了一个err,首先调用PTR_ERR,定义为result指针强制转换为long之后的值:如果result是代表错误的指针,则该err代表错误码
//如果tmp不是一个错误码(tmp得到了getname函数的返回值):

//执行do_path_lookup函数:
	static int fastcall do_path_lookup(int dfd, const char *name,
				unsigned int flags, struct nameidata *nd)                   //err = do_path_lookup(dfd, tmp, flags, nd);这是do_path_lookup函数的调用语句,dfd=AT_FDCWD
	{
		int retval = 0;
		int fput_needed;
		struct file *file;													//定义结构体file和fs_struct
		struct fs_struct *fs = current->fs;									//设置一个fs_struct,是当前路径的fs结构,
																			//当前文件系统包含了三个dentry,它们分别指向根dentry、当前dentry(即pwd命令显示的当前目录)和替换根dentry

		nd->last_type = LAST_ROOT; /* if there are only slashes... */		//首先将last_type设定为LAST_ROOT,这个值会随着路径名的当前搜索结果发生改变
		nd->flags = flags;													//nd的flag得到了flag位的值
		nd->depth = 0;														//depth缺省值为0

																			//下面的工作是通过通过判断绝对路径或者是相对路径来初始化nd(nameidata)
		if (*name=='/') {													//判断name是不是从'/'开始,从'/'开始证明是从根目录开始
			read_lock(&fs->lock);											//要进行dentry的操作!!!先加锁
			if (fs->altroot && !(nd->flags & LOOKUP_NOALT)) {				//如果当前进程文件系统存在替换根dentry且打开文件的时候不设置LOOKUP_NOALT标志(noalt意思是寻路径时不替换根)
																			//文件名是一个绝对路径,因此需要优先使用文件系统的根目录作为查找起始点
																			//接下是两个getmnt和getdentry的函数:
				nd->mnt = mntget(fs->altrootmnt);							//这两个函数将绝对路径的mount和dentry赋值到nd当中
				nd->dentry = dget(fs->altroot);
				read_unlock(&fs->lock);
				if (__emul_lookup_dentry(name,nd))							//调用__emul_lookup_dentry函数,这个函数判断:如果按照路径的指引进行的搜索失败(涉及到调用path_walk函数),则返回0,说明有错误发生!
					goto out; /* found in altroot */						//如果没有问题,搜索成功,则nd得到了新的mnt和dentry,执行out步骤
				read_lock(&fs->lock);
			}
			nd->mnt = mntget(fs->rootmnt);									//如果并没有搜索成功,则需要将nd的mnt和dentry恢复到当前的根mnt和根dentry
			nd->dentry = dget(fs->root);
			read_unlock(&fs->lock);											//解开锁
		} else if (dfd == AT_FDCWD) {										//如果name的第一个字符不是'/',并且dfd等于ATFDCWD,也就是说,是从当前节点开始的:
			read_lock(&fs->lock);
			nd->mnt = mntget(fs->pwdmnt);									//把nd的mnt和dentry转变为当前dentry
			nd->dentry = dget(fs->pwd);
			read_unlock(&fs->lock);
		} else {															//最后一种情况:do_path_lookup第三部分的前提是前两个部分的条件都不成立,这个文件已经打开过,输入参数是文件的ID号。这种情况是在进程的已打开文件结构里面根据用户态的ID号查找文件。
			struct dentry *dentry;

			file = fget_light(dfd, &fput_needed);							//轻量级的寻找文件的方法,要求必须有进程调用文件
			retval = -EBADF;												//EBADF:bad file number
			if (!file)
				goto out_fail;												//如果文件没有找到,直接goto到失败处理

			dentry = file->f_path.dentry;									//把找到的file的dentry复制到dentry里面来

			retval = -ENOTDIR;												//not a directory,后面进行是否是dir的判断
			if (!S_ISDIR(dentry->d_inode->i_mode))							//判断是不是dir,如果不是,走到fput_fail
				goto fput_fail;

			retval = file_permission(file, MAY_EXEC);						//get permission
			if (retval)
				goto fput_fail;

			nd->mnt = mntget(file->f_path.mnt);								//get相应的dentry和mount
			nd->dentry = dget(dentry);

			fput_light(file, fput_needed);									//fput更新文件的引用计数
		}
		current->total_link_count = 0;											
		retval = link_path_walk(name, nd);									//再执行一次link_path_walk,不再依赖dentry cache,而是强迫文件系统执行自己的查找功能
		/*link_path_walk两次执行了__link_path_walk函数。原因是第一次查找有可能失败,文件系统返回了ESTALE失败标识码。
			这种情况下nd的flag成员要加上LOOKUP_REVAL标志,作用是不再依赖dentry cache,而是强迫文件系统执行自己的查找功能。
			对于硬盘文件系统,文件系统自己的查找功能通常要读硬盘上文件系统的元数据来获取文件信息*/
	out:
		if (likely(retval == 0)) {
			if (unlikely(!audit_dummy_context() && nd && nd->dentry &&
					nd->dentry->d_inode))
			audit_inode(name, nd->dentry->d_inode);
		}
	out_fail:
		return retval;

	fput_fail:
		fput_light(file, fput_needed);										//失败了?!再put一次(增加inode的link计数),然后返回错误信息
		goto out_fail;
	}
//do_path_lookup函数结束!!!
//其中调用了__emul_lookup_dentry(name,nd)函数:
	static int __emul_lookup_dentry(const char *name, struct nameidata *nd)
	{
		if (path_walk(name, nd))													//函数使用了path_walk函数,path_walk函数调用了link_path_walk函数,如果path_walk返回的是一个失败码,则return 0,代表按照路径指引的搜索发生失败
			return 0;		/* something went wrong... */

		if (!nd->dentry->d_inode || S_ISDIR(nd->dentry->d_inode->i_mode)) {			//如果没有失败:如果
			struct dentry *old_dentry = nd->dentry;
			struct vfsmount *old_mnt = nd->mnt;
			struct qstr last = nd->last;
			int last_type = nd->last_type;
			struct fs_struct *fs = current->fs;

			/*
			 * NAME was not found in alternate root or it's a directory.
			 * Try to find it in the normal root:
			 */
			nd->last_type = LAST_ROOT;
			read_lock(&fs->lock);
			nd->mnt = mntget(fs->rootmnt);
			nd->dentry = dget(fs->root);
			read_unlock(&fs->lock);
			if (path_walk(name, nd) == 0) {
				if (nd->dentry->d_inode) {
					dput(old_dentry);
					mntput(old_mnt);
					return 1;
				}
				path_release(nd);
			}
			nd->dentry = old_dentry;
			nd->mnt = old_mnt;
			nd->last = last;
			nd->last_type = last_type;
		}
		return 1;
	}

//其中调用了path_walk函数
		int fastcall path_walk(const char * name, struct nameidata *nd)
		{
			current->total_link_count = 0;
			return link_path_walk(name, nd);
		}
//path_walk函数调用了link_path_walk函数
			int fastcall link_path_walk(const char *name, struct nameidata *nd)
			{
			  struct nameidata save = *nd;							//save的作用是临时保存nd数据结构
			  int result;

			  /* make sure the stuff we saved doesn't go away */
			  /*增加mnt和dentry的引用计数*/
			  dget(save.dentry);									//增加引用计数,防止因为没有引用计数而被删除掉
			  mntget(save.mnt);

			  result = __link_path_walk(name, nd);					//执行__link_path_walk函数,这个函数是最基础的寻址函数,将路径名转换为最后的dentry,返回值中,result=0证明查找成功
			  if (result == -ESTALE) {								//ESTALE是指修改了NFS系统的正在使用的内容
			      *nd = save;										//把nd回复到以前存的save
			      dget(nd->dentry);									//get一个到dentry的引用,本来返回值应该是struct dentry,这里主要是增加dentry的引用计数
			      mntget(nd->mnt);
			      nd->flags |= LOOKUP_REVAL;						//cache不可信,强制进行文件系统自己的查找功能
			      result = __link_path_walk(name, nd);				//继续进行查找
			  }

			  dput(save.dentry);									//删除save下面的dentry结构和mnt,对dentry解引用
			  mntput(save.mnt);

			  return result;
			}
			

			//下面是__link_path_walk函数:
				static fastcall int __link_path_walk(const char * name, struct nameidata *nd)
				{
					struct path next;
					struct inode *inode;
					int err;
					unsigned int lookup_flags = nd->flags;
					
					while (*name=='/')
						name++;
					if (!*name)
						goto return_reval;									//如果路径名全部是"/////////",那么直接到return_reval

					inode = nd->dentry->d_inode;
					if (nd->depth)
						lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);        //检查深度

					/*第一部分是将文件名字符串的最前面的斜杠符去掉。因为斜杠符可能是多个,所以有一个while循环。
					 
					 * At this point we know we have a real path component. */
					
					for(;;) {											//这个循环遍历名字字符的每一轮。就是以“/”字符分隔的每一层字符
						unsigned long hash;
						struct qstr this;
						unsigned int c;
																		//权限检查
						nd->flags |= LOOKUP_CONTINUE;
						err = exec_permission_lite(inode, nd);
						if (err == -EAGAIN)
							err = vfs_permission(nd, MAY_EXEC);
				 		if (err)
							break;										//不符合权限,则break

						this.name = name;								//计算name的哈希值
						c = *(const unsigned char *)name;				//c代表当前name指针所在地方的字符的acsII码

						hash = init_name_hash();						//哈希值初始化,相当于hash=0;
						do {
							name++;
							hash = partial_name_hash(c, hash);			//计算哈希值的函数:(prevhash + (c << 4) + (c >> 4)) * 11   (233333333333333)
							c = *(const unsigned char *)name;
						} while (c && (c != '/'));						//如果碰到了'/'分割的字符,则证明这一轮的名字到了结束位置
						this.len = name - (const char *) this.name;
						this.hash = end_name_hash(hash);				//完成hash值的计算

						/* remove trailing slashes? */
						if (!c)											//如果c是'\0',证明完成了路径的查找,则应该跳转到last_componet那里去
							goto last_component;
						while (*++name == '/');							//继续排除多余的'/'
						if (!*name)
							goto last_with_slashes;						//排除过后,发现没东西了~证明这个结尾就是'/',跳转到last_with_slashes

						/*
						 * "." and ".." are special - ".." especially so because it has
						 * to be able to know about the current root directory and
						 * parent relationships.
						 */
						if (this.name[0] == '.') switch (this.len) {	//如果是'.',则需要特殊处理:
							default:
								break;									//如果不在'.'和".."两种情况之内,则需要break,发生了错误!
							case 2:	
								if (this.name[1] != '.')				//如果字符串长度为2,并且第二个不是'.',也发生了错误
									break;
								follow_dotdot(nd);						//执行follow_dotdot函数,跑到父节点去
								inode = nd->dentry->d_inode;
								/* fallthrough */
							case 1:
								continue;								//当前节点!不操作,继续当前节点的搜索
						}
						/*
						 * See if the low-level filesystem might want
						 * to use its own hash..
						 */
						if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {			//如果文件系统提供了自己的hash函数,则使用它计算hash
							err = nd->dentry->d_op->d_hash(nd->dentry, &this);
							if (err < 0)
								break;
						}
						/* This does the actual lookups.. */			//开始搜索
						err = do_lookup(nd, &this, &next);				//do_lookup执行搜索
						if (err)
							break;

						err = -ENOENT;
						inode = next.dentry->d_inode;
						if (!inode)
							goto out_dput;
						err = -ENOTDIR; 
						if (!inode->i_op)
							goto out_dput;

						if (inode->i_op->follow_link) {
							err = do_follow_link(&next, nd);
							if (err)
								goto return_err;
							err = -ENOENT;
							inode = nd->dentry->d_inode;
							if (!inode)
								break;
							err = -ENOTDIR; 
							if (!inode->i_op)
								break;
						} else
							path_to_nameidata(&next, nd);
						err = -ENOTDIR; 
						if (!inode->i_op->lookup)
							break;
						continue;
						/* here ends the main loop */

				last_with_slashes:
						lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
				last_component:
						/* Clear LOOKUP_CONTINUE iff it was previously unset */
						nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
						if (lookup_flags & LOOKUP_PARENT)
							goto lookup_parent;
						if (this.name[0] == '.') switch (this.len) {
							default:
								break;
							case 2:	
								if (this.name[1] != '.')
									break;
								follow_dotdot(nd);
								inode = nd->dentry->d_inode;
								/* fallthrough */
							case 1:
								goto return_reval;
						}
						if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {
							err = nd->dentry->d_op->d_hash(nd->dentry, &this);
							if (err < 0)
								break;
						}
						err = do_lookup(nd, &this, &next);
						if (err)
							break;
						inode = next.dentry->d_inode;
						if ((lookup_flags & LOOKUP_FOLLOW)
						    && inode && inode->i_op && inode->i_op->follow_link) {
							err = do_follow_link(&next, nd);
							if (err)
								goto return_err;
							inode = nd->dentry->d_inode;
						} else
							path_to_nameidata(&next, nd);
						err = -ENOENT;
						if (!inode)
							break;
						if (lookup_flags & LOOKUP_DIRECTORY) {
							err = -ENOTDIR; 
							if (!inode->i_op || !inode->i_op->lookup)
								break;
						}
						goto return_base;
				lookup_parent:
						nd->last = this;
						nd->last_type = LAST_NORM;
						if (this.name[0] != '.')
							goto return_base;
						if (this.len == 1)
							nd->last_type = LAST_DOT;
						else if (this.len == 2 && this.name[1] == '.')
							nd->last_type = LAST_DOTDOT;
						else
							goto return_base;
				return_reval:
						/*
						 * We bypassed the ordinary revalidation routines.
						 * We may need to check the cached dentry for staleness.
						 */
						if (nd->dentry && nd->dentry->d_sb &&
						    (nd->dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {
							err = -ESTALE;
							/* Note: we do not d_invalidate() */
							if (!nd->dentry->d_op->d_revalidate(nd->dentry, nd))
								break;
						}
				return_base:
						return 0;
				out_dput:
						dput_path(&next, nd);
						break;
					}
					path_release(nd);
				return_err:
					return err;
				}
				//函数结束了!!!!


				//调用了do_lookup函数
				static int do_lookup(struct nameidata *nd, struct qstr *name,
						     struct path *path)
				{
					struct vfsmount *mnt = nd->mnt;
					struct dentry *dentry = __d_lookup(nd->dentry, name);

					if (!dentry)
						goto need_lookup;
					if (dentry->d_op && dentry->d_op->d_revalidate)
						goto need_revalidate;
				/*do_lookup函数第一部分以nd的dentry为父目录,调用__d_lookup函数查找name所代表文件的dentry。
				 *name代表的文件既可能是一个普通文件,也可能是一个目录文件。
				 *do_lookup函数第二部分是两个分支。一个是done分支,另一个是need_lookup分支。
				 */
				done:
					path->mnt = mnt;
					path->dentry = dentry;
					__follow_mount(path);
					return 0;

				need_lookup:
					dentry = real_lookup(nd->dentry, name, nd);
					if (IS_ERR(dentry))
						goto fail;
					goto done;

				need_revalidate:
					dentry = do_revalidate(dentry, nd);
					if (!dentry)
						goto need_lookup;
					if (IS_ERR(dentry))
						goto fail;
					goto done;

				fail:
					return PTR_ERR(dentry);
				}
				/*如果第一部分的查找成功了,则进入done分支设置vfsmount对象和dentry,然后检查dentry是否一个挂载点。
				 *如果查找未成功,则进入need_lookup分支。第一部分的查找是在dentry cache里面进行,
				 *如果进入need_lookup分支,说明在dentry cache中找不到指定名字文件的dentry。
				 *对于建立在硬盘之上的文件系统,这时候要调用文件系统提供的lookup函数在硬盘上搜索文件。
				 *完成文件系统的lookup之后,仍然需要进入done分支,处理挂载点。*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值