感谢支持
我们一直在努力

Linux虚拟文件系统–文件路径名的解析(1)–整体过程

文件路径名的解析是VFS中最基本也是最频繁用到的一个部分,它的代码实现还是十分繁杂的,主要是因为除了普通文件名的解析,内核还要考虑各种可能出现的情况,如一个目录下挂载了多个文件系统,路径中的符号链接等等……后面我会分几次将整个过程进行一个尽量仔细的分析,其中所涉及到的各种数据结构在ULK等相关内核书籍上都有比较详细的介绍,就不列出来了

相关阅读:Linux虚拟文件系统–文件路径名的解析(2)–回退父目录 http://www.linuxidc.com/Linux/2012-11/74575.htm

文件路径名的解析路口函数为path_lookup(),如下:

int path_lookup(const char *name, unsigned int flags, 
            struct nameidata *nd) 

    return do_path_lookup(AT_FDCWD, name, flags, nd); 

name:路径名

flags:查找操作的标识

struct nameidata:用于保存当前的相关查找结果

这里可以看到path_lookup()只是对do_path_lookup()的一层封装

static int do_path_lookup(int dfd, const char *name, 
                unsigned int flags, struct nameidata *nd) 

    /*path_init进行一些搜索前的初始化工作,主要是确定起始搜索的起点并保存在nd中*/ 
    int retval = path_init(dfd, name, flags, nd); 
     
    if (!retval)//初始化没问题的话就开始解析 
        retval = path_walk(name, nd); 
    if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry && 
                nd->path.dentry->d_inode)) 
        audit_inode(name, nd->path.dentry); 
    if (nd->root.mnt) { 
        path_put(&nd->root); 
        nd->root.mnt = NULL; 
    } 
    return retval; 
}

 

static int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd)
{
int retval = 0;
int fput_needed;
struct file *file;
nd->last_type = LAST_ROOT; /* if there are only slashes… */
nd->flags = flags;
nd->depth = 0;
nd->root.mnt = NULL;
if (*name==’/’) {//如果文件路径是以绝对路径的形式给出
set_root(nd);//设置nd的根目录
nd->path = nd->root;//当前的path从根目录开始
path_get(&nd->root);
} else if (dfd == AT_FDCWD) {
struct fs_struct *fs = current->fs;
read_lock(&fs->lock);
nd->path = fs->pwd;//获取当前目录的path
path_get(&fs->pwd);
read_unlock(&fs->lock);
} else {
struct dentry *dentry;
/*根据dfd,从当前进程的fs_struct结构的fdtable中取出文件描述符指针*/
file = fget_light(dfd, &fput_needed);
retval = -EBADF;
if (!file)
goto out_fail;
//从文件描述符中获取dentry
dentry = file->f_path.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;//获取path
path_get(&file->f_path);
fput_light(file, fput_needed);
}
return 0;
fput_fail:
fput_light(file, fput_needed);
out_fail:
return retval;

注意之前传递进来的dfd为AT_FDCWD,因此path_init中只有可能出现前两种情况:1.路径以绝对路径的方式给出 2.路径以相对路径的方式给出。此时nd中的path保存了查找的起始目录,对于第一种情况,即为’/’,第二种情况起始目录要从当前进程的fs结构中提取。

下面通过path_walk()开始进行解析,我们直接进入path_walk()–>link_path_walk()–>__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; 
     
    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);//进行访问权限的检查 
        if (err) 
            break; 
         
        this.name = name;//当前进行解析的文件名分量 
        c = *(const unsigned char *)name; 
 
        hash = init_name_hash();//hash值初始化为0 
        /*计算当前文件名分量的hash值*/ 
        do { 
            name++; 
            hash = partial_name_hash(c, hash); 
            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)//文件名解析完毕 
            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://文件名长度为2并且下一个字符也为’.’则表明要退回到上级目录   
                if (this.name[1] != ‘.’) 
                    break; 
                follow_dotdot(nd); 
                inode = nd->path.dentry->d_inode; 
                //注意这里没有break,因此将通过后面的continue直接回到循环开头 
            case 1://长度为1表示当前目录,则直接忽略即可 
                continue; 
        } 
 
        /*下面的部分用来处理普通的文件路径分量(不为.和..)*/ 
         
        /*如果底层文件系统的dentry定义了d_op和d_hash则调用文件系统中的d_hash进行hash值的计算*/ 
        if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) { 
            err = nd->path.dentry->d_op->d_hash(nd->path.dentry, 
                                &this); 
            if (err < 0) 
                break; 
        } 
         
        //do_lookup执行实际的查找,注意nd中的path对应的是父目录,而this是对应的当前解析的路径分量 
        err = do_lookup(nd, &this, &next); 
        if (err) 
            break; 
 
        err = -ENOENT; 
        inode = next.dentry->d_inode;//获取刚刚解析的路径分量的inode 
        if (!inode) 
            goto out_dput; 
 
        if (inode->i_op->follow_link) {//如果刚刚解析的路径为符号链接,则通过do_follow_link进行处理 
            err = do_follow_link(&next, nd); 
            if (err) 
                goto return_err; 
            err = -ENOENT; 
            inode = nd->path.dentry->d_inode; 
            if (!inode) 
                break; 
        } else//不为符号链接,则路径分量解析完毕,根据next更新nd中的path准备解析下一个分量 
            path_to_nameidata(&next, nd); 
        err = -ENOTDIR; 
        if (!inode->i_op->lookup)//如果刚刚解析的路径分量对应的inode没有定义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->path.dentry->d_inode; 
                /* fallthrough */ 
            case 1: 
                goto return_reval; 
        } 
        if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) { 
            err = nd->path.dentry->d_op->d_hash(nd->path.dentry, 
                                &this); 
            if (err < 0) 
                break; 
        } 
        err = do_lookup(nd, &this, &next); 
        if (err) 
            break; 
        inode = next.dentry->d_inode; 
        if (follow_on_final(inode, lookup_flags)) { 
            err = do_follow_link(&next, nd); 
            if (err) 
                goto return_err; 
            inode = nd->path.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->lookup) 
                break; 
        } 
        goto return_base; 
lookup_parent://如果目标是最后一个分量的父目录,则不用进行查找了, 
                //因为nd的path总是指向当前要分析的路径的父目录的 
                //下面只需要对nd的last_tpye字段进行处理,表示最后一个分量的类型 
        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->path.dentry && nd->path.dentry->d_sb && 
            (nd->path.dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) { 
            err = -ESTALE; 
            /* Note: we do not d_invalidate() */ 
            if (!nd->path.dentry->d_op->d_revalidate( 
                    nd->path.dentry, nd)) 
                break; 
        } 
return_base: 
        return 0; 
out_dput: 
        path_put_conditional(&next, nd); 
        break; 
    } 
    path_put(&nd->path); 
return_err: 
    return err; 

其中几个比较重要的函数,follow_dotdot()–退回到上级目录, do_lookup()–普通文件名的实际查找工作, do_follow_link()–追踪符号链接,在下篇文章再拿出来进行具体分析。

赞(0) 打赏
转载请注明出处:服务器评测 » Linux虚拟文件系统–文件路径名的解析(1)–整体过程
分享到: 更多 (0)

听说打赏我的人,都进福布斯排行榜啦!

支付宝扫一扫打赏

微信扫一扫打赏