前面对linux虚拟文件系统的架构以及设计到的数据结构有了一个整体的认识,这里看看linux内核怎么根据给定的文件路径名在内存中找到和建立代表着目标文件或目录的dentry结构和inode结构。文件路径的搜索是文件系统中最基本也是最重要的一部分之一,后面我们会看到,文件的打开、关闭等等操作都将涉及到文件路径的搜索。下面我们看看linux内核中时怎么实现的。

一、搜索中所用数据结构

[cpp] 
 /*这个数据结构是临时的,只在路径搜索的过程中返回搜索的结果。  
 */   
 struct nameidata {   
     struct path path;/*将目录结构和mount结构封装在path结构中*/   
     struct qstr last;   
     struct path root;   
     unsigned int    flags;/*对应搜索的标志*/   
     int     last_type;   
     unsigned    depth;   
     char *saved_names[MAX_NESTED_LINKS + 1];   
    
     /* Intent data */   
     union {   
         struct open_intent open;   
     } intent;   
 };  
 [cpp] 
 /*用来存放路径名中当前节点的杂凑值以及节点名的长度*/   
 struct qstr {   
     unsigned int hash;   
     unsigned int len;   
     const unsigned char *name;   
 };


二、搜索

[cpp] 
 /*name指向在用户空间的路径名;  
 flag为一些标志位,nd为搜索返回值  
 */   
 int path_lookup(const char *name, unsigned int flags,   
             struct nameidata *nd)   
 {   
     return do_path_lookup(AT_FDCWD, name, flags, nd);   
 }


实际工作都是由上面的do_path_lookup()函数实现的,在这里我们就他进行分析。

[cpp] 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */   
 static int do_path_lookup(int dfd, const char *name,   
                 unsigned int flags, struct nameidata *nd)   
 {   /*找到搜索的起点,保存在nd中*/   
     int retval = path_init(dfd, name, flags, nd);   
     if (!retval)   
             /*一旦找到了搜索的起点,从起点开始路径的搜索  
         其中nd用来返回搜索结果*/   
         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;   
 }


2.1 初始化阶段

初始化阶段是由函数path_init()函数实现

[cpp] 
 /*path_init主要是初始化查询,设置nd结构指向查询开始处的文件,这里分两种情况:  
     a,绝对路径(以/开始),获得根目录的dentry。它存储在task_struct中fs指向的fs_struct结构中。  
     b,相对路径,直接从当前进程task_struct结构中的获得指针fs,它指向的一个fs_struct,  
     fs_struct中有一个指向“当前工作目录”的dentry。  
 */   
 static int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd)   
 {   
     int retval = 0;   
     int fput_needed;   
     struct file *file;   
     /*在搜索的过程中,这个字段的值会随着路径名当前搜索结果而变;  
     例如,如果成功找到目标文件,那么这个字段的值就变成了LAST_NORM  
     而如果最后停留在了一个.上,则变成LAST_DOT(*/   
     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的root为当前进程fs的root*/   
         nd->path = nd->root;/*保存根目录*/   
         path_get(&nd->root);/*递增引用计数*/   
     } else if (dfd == AT_FDCWD) {/*相对路径*/   
         struct fs_struct *fs = current->fs;   
         read_lock(&fs->lock);   
         nd->path = fs->pwd;/*保存当前路径*/   
         path_get(&fs->pwd);/*递增引用计数*/   
         read_unlock(&fs->lock);   
     } else {/*???*/   
         struct dentry *dentry;   
          /*fget_light在当前进程的struct files_struct中根据所谓的用户空间  
         文件描述符fd来获取文件描述符。另外,根据当前fs_struct  
         是否被多各进程共享来判断是否需要对文件描述符进行加  
          锁,并将加锁结果存到一个int中返回  
         */   
         file = fget_light(dfd, &fput_needed);   
         retval = -EBADF;   
         if (!file)   
             goto out_fail;   
    
         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;   
         /*获得path*/   
         nd->path = file->f_path;   
         path_get(&file->f_path);   
         /*解锁*/   
         fput_light(file, fput_needed);   
     }   
     return 0;   
    
 fput_fail:   
     fput_light(file, fput_needed);   
 out_fail:   
     return retval;   
 }


2.2 实际搜索操作

[cpp] 
 static int path_walk(const char *name, struct nameidata *nd)   
 {   
     current->total_link_count = 0;   
     return link_path_walk(name, nd);   
 }  
 [cpp] 
 /*  
  * Wrapper to retry pathname resolution whenever the underlying  
  * file system returns an ESTALE.  
  *  
  * Retry the whole path once, forcing real lookup requests  
  * instead of relying on the dcache.  
  */   
 static __always_inline int link_path_walk(const char *name, struct nameidata *nd)   
 {   
     struct path save = nd->path;   
     int result;   
    
     /* make sure the stuff we saved doesn't go away */   
     path_get(&save);/*递增path的引用计数*/   
     /*实际的工作*/   
     result = __link_path_walk(name, nd);   
     if (result == -ESTALE) {   
         /* nd->path had been dropped */   
         nd->path = save;   
         path_get(&nd->path);   
         nd->flags |= LOOKUP_REVAL;   
         result = __link_path_walk(name, nd);   
     }   
    
     path_put(&save);   
    
     return result;   
 }  
 [cpp] 
 /*  
  * 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.  
  */   
 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;   
     /*如果路径名以'/'开头,就把他跳过去,因为在这种情况下nd中  
     path已经指向本进程的根目录了,注意,这里多个连续的'/'与一个  
     ‘/’是等价的,如果路径名中仅仅包含有'/'字符的话,那么其  
     目标就是根目录,所以任务完成,不然需要继续搜索*/   
     while (*name=='/')   
         name++;   
     if (!*name)   
         goto return_reval;   
     /*作为path_walk起点的节点必定是一个目录,一定有相应的索引节点  
     存在,所以指针inode一定是有效的,而不可能是空指针*/   
     inode = nd->path.dentry->d_inode;   
     /*进程的task_struct结构中有个计数器link_count.在搜索过程中有可能  
     碰到一个节点(目录项)只是指向另一个节点的链接,此时就用这个计数器来对  
     链的长度进行计数,这样,当链的长度达到某一个值时就可以终止搜索而失败  
     返回,以防陷入循环。另一方面,当顺着符号链接进入另一个设备上的文件系统  
     时,有可能会递归地调用path_walk。所以,进入path_walk后,如果发现这个  
     计数器值非0,就表示正在顺着符号链接递归调用path_walk往前搜索过程中,  
     此时不管怎样都把LOOKUP_FOLLOW标志位设成1.*/   
     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;   
         /*检查当前进程对当前节点的访问权限,这里所检查的是相对路径中  
         的各层目录(而不是目标文件)的访问权限。注意,对于中间节点所需  
         的权限为执行权,即MAY_EXEC*/   
              err = exec_permission_lite(inode);   
         if (err)   
             break;   
    
         this.name = name;   
         c = *(const unsigned char *)name;   
    
         hash = init_name_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);   
    
         /* remove trailing slashes? */   
         if (!c)/*最后一个字符为'\0',就是说当前节点已经是路径名中的最后一节*/   
             goto last_component;/*跳转*/   
         /*循环跳过'/'*/   
         while (*++name == '/');   
         /*当前节点实际上已经是路径名的最后一个节点,只不过在此后面又多添加了  
         若干个'/'字符,这种情况常常发生在用户界面上,特别是在shell的命令中  
         当然这种情况要求最后的节点必须是个目录*/   
         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->path.dentry->d_inode;   
                 /* fallthrough */   
             /*2中没有break语句,也就是所继续执行1中的语句,  
             将会跳到for语句的开头处理路径中的下一个节点*/   
             case 1:   
                 continue;   
         }   
         /*  
          * See if the low-level filesystem might want  
          * to use its own 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;   
         }   
         /* This does the actual lookups.. */   
         /*实际的搜索工作*/   
              err = do_lookup(nd, &this, &next);   
         if (err)   
             break;   
    
         err = -ENOENT;   
         inode = next.dentry->d_inode;   
         if (!inode)   
             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;   
         } else/*将path中的相关内容转化到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 */   
         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;   
         }/*顺次查找路径节点,下一个存放在next中*/   
         err = do_lookup(nd, &this, &next);   
         if (err)   
             break;   
         inode = next.dentry->d_inode;   
         if ((lookup_flags & LOOKUP_FOLLOW)/*当终点为符号链接时*/   
             && inode && inode->i_op->follow_link) {   
             err = do_follow_link(&next, nd);   
             if (err)   
                 goto return_err;   
             inode = nd->path.dentry->d_inode;   
         } else   
             /*path转化为nd*/   
             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->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;   
 }