根据这个主线分析一下路径查找的过程;
首先看看do_path_lookup()代码的片段:
static int do_path_lookup(int dfd, const char *name,
unsigned int flags, struct nameidata *nd)
{
int retval = 0;
int fput_needed;
struct file *file;
struct fs_struct *fs = current->fs; //取得当前进程的fs
nd->last_type = LAST_ROOT; /* if there are only slashes... */
nd->flags = flags;
//这个flag有如下几个宏定义:
//LOOKUP_FOLLOW 路径最后一个分量是符号链接,则追踪它
//LOOKUP_DIRECTORY 路径最后一个分量为目录
//LOOKUP_CONTINUE 路径中还有文件名要检查
//LOOKUP_PARENT 查找路径中最后一个分量所在的目录
nd->depth = 0;
if (*name=='/') { //查找需要从当前根目录开始(绝对路径)
read_lock(&fs->lock);
if (fs->altroot.dentry && !(nd->flags & LOOKUP_NOALT)) {
nd->path = fs->altroot;
path_get(&fs->altroot);
read_unlock(&fs->lock);
if (__emul_lookup_dentry(name,nd))
goto out; /* found in altroot */
read_lock(&fs->lock);
}
nd->path = fs->root; //取得根目录的dentry和vfsmnt
path_get(&fs->root);
read_unlock(&fs->lock);
} else if (dfd == AT_FDCWD) { //查找需要从当前工作目录开始(相对路径)
read_lock(&fs->lock);
nd->path = fs->pwd;
path_get(&fs->pwd);
read_unlock(&fs->lock);
} else {
struct dentry *dentry;
file = fget_light(dfd, &fput_needed);
retval = -EBADF;
if (!file)
goto out_fail;
dentry = file->f_path.dentry; //取得当前目录的dentry
retval = -ENOTDIR;
if (!S_ISDIR(dentry->d_inode->i_mode)) //判断文件是否为目录
goto fput_fail;
retval = file_permission(file, MAY_EXEC); //文件权限
if (retval)
goto fput_fail;
nd->path = file->f_path; //在经过上面判断后成功取得进程当前目录的dentry和mnt
path_get(&file->f_path);
fput_light(file, fput_needed);
}
retval = path_walk(name, nd); //此时路径第一个分量的dentry和mnt 已经保存在了nd->path中,继续查找
………………….
}
在这里我们可以看出来nd其实就像是一个”路标” , 它指向了我们查找到路径的什么位置.
继续跟踪代码path_walk(),这个函数调用了函数link_path_walk(name, nd),最终调用函数__link_path_walk(),来看一下代码片段:
static int __link_path_walk(const char *name, struct nameidata *nd)
{
struct path next;
struct inode *inode;
int err;
unsigned int lookup_flags = nd->flags; //这个flag很重要,决定后面查找方式
while (*name=='/') //如果路径第一个字符为”/”,则跳过
name++;
if (!*name)
goto return_reval;
inode = nd->path.dentry->d_inode; //取得inode是为了后面判断该目录或文件的操作权限
if (nd->depth)
lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);
/* 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;
this.name = name;
c = *(const unsigned char *)name;
hash = init_name_hash();
do {
name++;
hash = partial_name_hash(c, hash); //计算路径hash值
c = *(const unsigned char *)name;
} while (c && (c != '/'));
this.len = name - (const char *) this.name;
this.hash = end_name_hash(hash); //到这里路径中的一个分量(目录名)的散列值就计算完了
//此时在this中保存了保存了目录名,目录名字长度和该目录散列值
if (!c)
goto last_component;
while (*++name == '/');
if (!*name)
goto last_with_slashes;
//上面的代码就已经解析出了路径名中的一个分量,并保存在this中
if (this.name[0] == '.') switch (this.len) { 如果名字第一个字符是”.”
default: //路径名出错
break;
case 2:
if (this.name[1] != '.') //表示上级目录
break;
follow_dotdot(nd);
inode = nd->path.dentry->d_inode;
/* fallthrough */
case 1: //表示当前目录, 立刻继续查找下个路径分量
continue;
}
…………………
/* This does the actual lookups.. */
err = do_lookup(nd, &this, &next); //在cache中查找该分量是否存在,如果不存在会建立一个dentry
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->path.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 */
…………………..
}
可以看到这个函数实现很烦琐,简单的说这个函数就是依次解析路径中的每个分量,如果该目录项不在目录项高速缓存中那么就在目录项高速缓存中建立对于的dentry ,并将最后一个分量的父目录的dentry和mnt赋给路标nd,.
下面看一下怎么在cache中查找该目录项对象:
static int do_lookup(struct nameidata *nd, struct qstr *name,
struct path *path)
{
struct vfsmount *mnt = nd->path.mnt;
struct dentry *dentry = __d_lookup(nd->path.dentry, name);//在父目录所在的散列表中查找该目录项对象
if (!dentry)
goto need_lookup;
………………
need_lookup:
dentry = real_lookup(nd->path.dentry, name, nd); //如果在__d_lookup中未找到,需要进一步查找
if (IS_ERR(dentry))
goto fail;
goto done;
……….
}
进一步看看real_lookup()的代码:
static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, struct nameidata *nd)
{
struct dentry * result;
struct inode *dir = parent->d_inode; //获取父目录对于的inode
mutex_lock(&dir->i_mutex);
result = d_lookup(parent, name); //在这里又进行了一次查找,
if (!result) { //如果确认未找到,则需要在内存中建立一个目录项对象了
struct dentry * dentry = d_alloc(parent, name); //分配一个dentry
result = ERR_PTR(-ENOMEM);
if (dentry) {
result = dir->i_op->lookup(dir, dentry, nd); //这是一个很中要的回调函数了,下面介绍
if (result)
dput(dentry);
else
result = dentry;
}
mutex_unlock(&dir->i_mutex);
return result;
}
……………..
}
在这个函数中又一次调用了d_lookup()对dentry进行查找 ,这里为什么先后两次进行相同的查找操作呢?难道仅仅是为了使查找dentry更加准确?为啥子又要加这样的安全套呢?
好了,如果这次也没在目录项高速缓存中找到dentry,那么就需要在目录项高速缓存中新建一个dentry了.
这里需要关注一个回调函数dir->i_op->lookup(dir, dentry, nd); 这个函数在不同的文件系统实现是不同的, 在以后分析其他文件系统的时候再来感受, 这里只看一下rootfs中这个回调函数是咋个定义的:
其实在rootfs中创建inode时定义了inode操作方法(可参见上文),lookup实现函数为simple_lookup();
下面就来看看这个函数都干了些啥子:
struct dentry *simple_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
static struct dentry_operations simple_dentry_operations = {
.d_delete = simple_delete_dentry,
};
if (dentry->d_name.len > NAME_MAX) //linux下文件名字长度不得超过255
return ERR_PTR(-ENAMETOOLONG);
dentry->d_op = &simple_dentry_operations; //定义新建dentry操作方法
d_add(dentry, NULL);
return NULL;
}
d_add(dentry, NULL)函数调用了d_instantiate(entry, inode)其实就关联dentry和inode了,不过因为这里的inode还没有创建所以就为空了, 另外还调用了一个函数d_rehash(entry);这个函数比较简单,就是计算出dentry对应的散列值,然后把它加到对应的散列表中 ,这样在查找的时候就可以快速的找到这个dentry了,而不会再去新建一个了.
至此路径查找的流程已经简单的跑了一遍了,根据查找的flags, 查找的结果放在了nd中,下面就可以用这个”路标”nd做具体的事情了^^^^^^^^^^^。
三: 建立目录文件