Linux open系统调用流程(3)

本文详细剖析了Linux内核中open系统调用的流程,特别是__link_path_walk函数的作用,包括解析文件路径、处理符号链接及权限检查等步骤。通过对do_lookup、d_alloc、iget等关键函数的分析,揭示了目录项对象、索引节点和缓存的交互过程,以及在处理创建文件和打开文件时的不同之处。

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

1. 闲言少叙,继续分析__link_path_walk函数:

/*
 * Name resolution.
 * This is the basic name resolution function, turning a pathname into
 * the final dentry. We expect 'base' to be positive and a directory.
 *
 * Returns 0 and nd will have valid dentry and mnt on success.
 * Returns error and drops reference to input namei data on failure.
 */
/**
处理三种情形:
(1)正在解析路径名
(2)解析父目录
(3)解析符号链接(第一次找出符号链接对应的文件路径,第二次解析文件路径)
**/
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;
	/*得到索引节点,第一次是开始目录的索引节点,以后就是上一次目录的索引节点*/
	inode = nd->dentry->d_inode;
	/*设置符号链接*/
	if (nd->depth)
		lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);

	/* At this point we know we have a real path component. */
	for(;;) {
		/*hash值*/
		unsigned long hash;
		/*包括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;
		/*name指的是第一个分量的第一个字符的地址*/
		this.name = name;
		/*取得第一个字符,如/proc,那么c='p'*/
		c = *(const unsigned char *)name;
		/*初始化hash值*/
		hash = init_name_hash();
		do {
			name++;
		/*计算部分hash,直到结尾,如/proc,那么计算的hash值就是proc*/	
			hash = partial_name_hash(c, hash);
			c = *(const unsigned char *)name;
		} while (c && (c != '/'));
		/*计算每个分量的长度*/
		this.len = name - (const char *) this.name;
		/*this.hash赋上hash值*/
		this.hash = end_name_hash(hash);

		/* remove trailing slashes? */
		/*到达最后一个分量*/
		if (!c)
			goto last_component;
		while (*++name == '/');
		/*最后一个字符是/*/
		if (!*name)
			goto 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;
			case 2:	/*并且第二个字符不是.,那么可能是隐藏文件,即不影响*/
				if (this.name[1] != '.')
					break;
				/*如果第二个字符也是.,需要回溯到父目录*/
				follow_dotdot(nd);
				inode = nd->dentry->d_inode;
				/* fallthrough */
			case 1:
				continue;
		}
		/*
		 * See if the low-level filesystem might want
		 * to use its own hash..
		 如果底层文件系统具有计算hash值的函数,则使用
		 */
		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;
		}
		/* This does the actual lookups..真正的查找函数*/
		/*nd结构体,this包含了分量名,next指向分量的目录项对象和安装点对象*/
		err = do_lookup(nd, &this, &next);
		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
			/*将目录项对象和安装点对象赋值给nd*/
			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 解析到最后一项,清除掉LOOKUP_CONTINUE*/
		nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
		/*有些情况下,不需要找到最后一个分量,例如创建一个文件/foo/bar,此时bar文件不存在,则应该找到foo的目录项对象*/
		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;
		}
		/*如果底层文件系统定义了计算hash值的方法,则使用它*/
		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;
		}
		/*查询最后一个component的hash值*/
		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
			/*设置nameidata的mnt和dentry*/
			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;/*正常返回0,则nd包含了最后一个分量的目录项对象和所属的文件系统安装点*/
lookup_parent:/*创建一个文件时需要父目录项对象*/
		/*最后一个分量名*/
		nd->last = this;
		/*最后一个分量类型*/
		nd->last_type = LAST_NORM;
		/*不是.代表文件*/
		if (this.name[0] != '.')
			goto return_base;
		/*如果长度为1,代表当前目录*/
		if (this.len == 1)
			nd->last_type = LAST_DOT;
		/*长度为2,代表父目录*/
		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;
}

这个函数主要做三件事:

(1)解析已经存在的文件路径,即打开标志

(2)解析不存在的文件路径,即创建文件标志,这样,需要得到父目录项对象和安装点对象

(3)解析符号链接,第一次找到符号链接的文件路径,第二次解析路径名

第23-26行,只有/,跳至return_reval. 这里多个根当作一个根处理,如//

第31-32行,设置符号链接标志。

第39行,定义qstr结构,这个结构包括hash值,分量长度和分量名。

第43-46行,进行权限检查,遍厍目录,必须具有执行权限。

第55-60行,计算每个分量的hash值。

第68行,如果解析到最后一个分量,跳至last_component.

第72行,如果遇到类似/proc/的目录,跳至last_with_slashes.

第80行,如果分量的第一个字符是.,但第二个字符不是.,则正常解析。

第88行,当第二个字符也是. ,说明是父目录,调用follow_dotdot进行回溯。

我们分析一下这个函数:

static __always_inline void follow_dotdot(struct nameidata *nd)
{
	/*得到fs_struct结构体*/
	struct fs_struct *fs = current->fs;

	while(1) {
		struct vfsmount *parent;
		/*上一次的目录项对象*/
		struct dentry *old = nd->dentry;
                read_lock(&fs->lock);
		/*如果回溯的目录是进程的根目录,则不允许,调用follow_mount函数*/
		if (nd->dentry == fs->root &&
		    nd->mnt =&#
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值