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 =&#