天天看点

路径名查找

当进程必须识别一个文件时,就把它的文件路径名传递给某个VFS系统调用,如open()、mkdir()、rename()或stat()。我们这里要说明VFS如何实现路径名查找,也就是说如何从文件路径名导出相应的索引节点。

执行这一任务的标准过程就是分析路径名并把它拆分成一个文件名序列。除了最后一个文件名以外,所有的文件名都必定是目录。

如果路径名的第一个字符是“/”,例如:/usr/share/system-configure-ext2/test.conf,那么这个路径名是绝对路径,因此从current->fs->root(进程的根目录)所标识的目录开始搜索。否则,路径名是相对路径,因此从currrent->fs->pwd(进程的当前目录)所标识的目录开始搜索。

在对第一个目录的索引节点进行处理的过程中,代码要检查与第一个名字匹配的目录项,以获得相应的索引节点。然后,从缓存或磁盘读出包含那个索引节点的目录文件,并检查与第二个名字匹配的目录项,以获得相应的索引节点。对于包含在路径中的每个名字,这个过程反复执行。

目录项高速缓存极大地加速了这一过程,因为它把最近最常使用的目录项对象保留在内存中。正如我们以前看到的,每个这样的对象使特定目录中的一个文件名与它相应的索引节点相联系。因此在很多情况下,路径名的分析可以避免从磁盘读取中间目录。

但是,事情并不像看起来那么简单,因为必须考虑如下的Unix和VFS文件系统的特点:

- 对每个目录的访问权必须进行检查,以验证是否允许进程读取这一目录的内容。

- 文件名可能是与任意一个路径名对应的符号链接;在这种情况下,分析必须扩展到那个路径名的所有分量。

- 符号链接可能导致循环引用;内核必须考虑这个可能性,并能在出现这种情况时将循环终止。

- 文件名可能是一个已安装文件系统的安装点。这种情况必须检测到,这样,查找操作必须延伸到新的文件系统。

- 路径名查找应该在发出系统调用的进程的命名空间中完成。由具有不同命名空间的两个进程使用的相同路径名,可能指定了不同的文件。

路径名查找是由path_lookup()函数执行的,它接收三个参数:

name:指向要解析的文件路径名的指针。

flags:标志的值,表示将会怎样访问查找的文件。

nd:nameidata数据结构的地址,这个结构存放了查找操作的结果。

当path_lookup()返回时,结果参数nd指向的nameidata结构用与路径名查找操作有关的数据来填充:

struct nameidata {

     struct dentry     *dentry;                   /* 目录项对象的地址 */

     struct vfsmount *mnt;                       /* 已安装文件系统对象的地址 */

     struct qstr     last;                             /* 路径名的最后一个分量(当LOOKUP_PARENT标志被设置时使用)*/

     unsigned int     flags;                        /* 查找标志 */

     int          last_type;                           /* 路径名最后一个分量的类型(当LOOKUP_PARENT标志被设置时使用)*/

     unsigned     depth;                           /* 符号链接嵌套的当前级别(参见下面);必须小于6 */

     char *saved_names[MAX_NESTED_LINKS + 1];    /* 与嵌套的符号链接关联的路径名数组 */

     /* Intent data 单个成员联合体,指定如何访问文件 */

     union {

          struct open_intent open;

     } intent;                         

};

dentry和mnt字段分别指向所解析的最后一个路径分量的目录项对象(注意是test.conf而不是system-configure-ext2)和已安装文件系统对象。这两个字段“描述”由给定路径名表示的文件。

由于path_lookup()函数返回的nameidata结构中的目录项对象和已安装文件系统对象代表了查找操作的结果,因此在path_lookup()的调用者完成使用查找结果之前,这个两个对象都不能被释放。因此,path_lookup()增加这两个对象引用计数器的值。如果调用者想释放这些对象,则调用path_release()函数,传递给它的参数就是nameidata结构的地址。

flags字段存放查找操作中使用的某些标志的值,这些标志中的大部分可由调用者在path_lookup()的flags参数中进行设置:

LOOKUP_FOLLOW:如果最后一个分量是符号链接,则解释(追踪)它

LOOKUP_DIRECTORY:最后一个分量必须是目录

LOOKUP_CONTINUE:在路径名中还有文件名要检查

LOOKUP_PARENT:查找最后一个分量名所在的目录

LOOKUP_NOALT:不考虑模拟根目录(在80x86体系结构中没有用)

LOOKUP_OPEN:试图打开一个文件

LOOKUP_CREATE:试图创建一个文件(如果不存在)

LOOKUP_ACCESS:试图为一个文件检查用户的权限

下面,我们来详细查看path_lookup()函数:

int fastcall path_lookup(const char *name, unsigned int flags,

               struct nameidata *nd)

{

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

}

static int fastcall do_path_lookup(int dfd, const char *name,

                    unsigned int flags, struct nameidata *nd)

     int retval = 0;

     int fput_needed;

     struct file *file;

     /* 首先,如下初始化nd参数的某些字段:*/

     nd->last_type = LAST_ROOT; /* if there are only slashes... */

     nd->flags = flags;

     nd->depth = 0;

     /* 如果路径名的第一个字符是“/”,那么查找操作必须从当前根目录开始:

      * 获取相应已安装文件对象(current->fs->rootmnt)和目录项对象(current->fs->root)的地址,

      * 增加引用计数器的值,并把它们的地址分别存放在nd->mnt和nd->dentry中。*/

     if (*name=='/') {

          /* 为进行读操作而获取当前进程的current->fs->lock读/写信号量。 */

          read_lock(&current->fs->lock);

          /* 80x86上altroot和altrootmnt总是NULL,不会执行以下分支 */

          if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) {

               nd->mnt = mntget(current->fs->altrootmnt);

               nd->dentry = dget(current->fs->altroot);

               read_unlock(&current->fs->lock);

               if (__emul_lookup_dentry(name,nd))

                    goto out; /* found in altroot */

               read_lock(&current->fs->lock);

          }

          nd->mnt = mntget(current->fs->rootmnt); /* 注意,这里增加了引用计数 */

          nd->dentry = dget(current->fs->root);   /* 注意,这里增加了引用计数 */

          /* 释放当前进程的current->fs->lock读/写信号量。 */

          read_unlock(&current->fs->lock);

     } else if (dfd == AT_FDCWD) {

          /* 否则,如果路径名的第一个字符不是“/”,则查找操作必须从当前工作目录开始:

           * 获得相应已安装文件系统对象(current->fs->mt)和目录项对象(current->fs->pwd)的地址,

           * 增加引用计数器的值,并把它们的地址分别存放在nd->mnt和nd->dentry中。 */

          nd->mnt = mntget(current->fs->pwdmnt); /* 注意,这里增加了引用计数 */

          nd->dentry = dget(current->fs->pwd);   /* 注意,这里增加了引用计数 */

     } else {

          /* 我们看到在path_lookup中只用了AT_FDCWD,所以也不会执行下面的分支。 */

          struct dentry *dentry;

          file = fget_light(dfd, &fput_needed);

          retval = -EBADF;

          if (!file)

               goto out_fail;

          dentry = file->f_dentry;

          retval = -ENOTDIR;

          if (!S_ISDIR(dentry->d_inode->i_mode))

               goto fput_fail;

          retval = file_permission(file, MAY_EXEC);

          if (retval)

          nd->mnt = mntget(file->f_vfsmnt);

          nd->dentry = dget(dentry);

          fput_light(file, fput_needed);

     }

     /* 把当前进程描述符中的total_link_count字段置为0(参见后面的“符号链接的查找”内容)。 */

     current->total_link_count = 0;

     /* 调用link_path_walk()函数处理真正进行的查找操作: */

     retval = link_path_walk(name, nd);

out:

     if (likely(retval == 0)) {

          if (unlikely(!audit_dummy_context() && nd && nd->dentry &&

                    nd->dentry->d_inode))

          audit_inode(name, nd->dentry);

out_fail:

     return retval;

fput_fail:

     fput_light(file, fput_needed);

     goto out_fail;

我们现在准备描述路径名查找操作的核心,也就是link_path_walk()函数。它接收的参数为要解析的路径名指针name和拥有目录项信息和安装文件系统信息的nameidata数据结构的地址nd。

int fastcall link_path_walk(const char *name, struct nameidata *nd)

     struct dentry *saved_dentry = nd->dentry;

     struct vfsmount *saved_mnt = nd->mnt;

     int result;

     ……

     result = __link_path_walk(name, nd);

     return result;

static fastcall int __link_path_walk(const char * name, struct nameidata *nd)

     struct path next;

     struct inode *inode;

     int err, atomic;

     unsigned int lookup_flags = nd->flags;

     atomic = (lookup_flags & LOOKUP_ATOMIC);

     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(;;) {

          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);

               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)

               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..

          if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {

               err = nd->dentry->d_op->d_hash(nd->dentry, &this);

               if (err < 0)

          /* This does the actual lookups.. */

          err = do_lookup(nd, &this, &next, atomic);

          if (err)

          err = -ENOENT;

          inode = next.dentry->d_inode;

          if (!inode)

               goto out_dput;

          err = -ENOTDIR;

          if (!inode->i_op)

          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)

               err = -ENOTDIR;

               if (!inode->i_op)

          } else

               path_to_nameidata(&next, nd);

          if (!inode->i_op->lookup)

          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;

                    goto return_reval;

          if ((lookup_flags & LOOKUP_FOLLOW)

              && inode && inode->i_op && inode->i_op->follow_link) {

          if (lookup_flags & LOOKUP_DIRECTORY) {

               if (!inode->i_op || !inode->i_op->lookup)

          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

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))

return_base:

          return 0;

out_dput:

          dput_path(&next, nd);

          break;

     path_release(nd);

return_err:

     return err;

为了简单起见,我们首先描述当LOOKUP_PARENT未被设置且路径名不包含符号链接时,link_path_walk()做些什么(非目录文件标准路径名查找)。接下来,我们讨论LOOKUP_PARENT被设置的情况:这种类型的查找在创建、删除或更名一个目录项时是需要的,也就是在父目录名查找过程中是需要的。最后,我们阐明该函数如何解析符号链接。

当LOOKUP_PARENT标志被清零时,link_path_walk()执行下列步骤:

1. 用nd->flags和nd->dentry->d_inode分别初始化lookup_flags和inode局部变量。

unsigned int lookup_flags = nd->flags;

inode = nd->dentry->d_inode;

2. 跳过路径名第一个分量前的任何斜杠(/)。

while (*name=='/')

     name++;

3. 如果剩余的路径名为空,则返回0。在nameidata数据结构中,dentry和mnt字段指向原路径名最后一个所解析分量对应的对象。

4. 如果nd描述符中的depth字段的值为正(大于0),则把lookup_flags局部变量置为LOOKUP_FOLLOW标志(这个跟符号链接查找相关)。

5. 执行一个循环,把name参数中传递的路径名分解为分量(中间的“/”被当做文件名分隔符对待);对于每个找到的分量,该函数:

  a) 检查存放到索引节点中的最近那个所解析分量的许可权是否允许执行(在Unix中,只有目录是可执行的,它才可以被遍历)。执行exec_permission_lite()函数,该函数检查存放在索引节点i_mode字段的访问模式和运行进程的特权。在两种情况中,如果最近所解析分量不允许执行,那么link_path_walk()跳出循环并返回一个错误码:

     err = exec_permission_lite(inode, nd);

     if (err == -EAGAIN)

          err = vfs_permission(nd, MAY_EXEC);

     if (err)

  b) 考虑要解析的下一个分量。从它的名字,函数为目录项高速缓存散列表计算一个32位的散列值:

     struct qstr this;

     this.name = name;

     c = *(const unsigned char *)name;

     hash = init_name_hash();  //就是返回hash = 0

     do { /* 这个do循环的目的就是计算hash值,仅此而已 */

          hash = partial_name_hash(c, hash);

     } while (c && (c != '/'));

     this.len = name - (const char *) this.name;

     this.hash = end_name_hash(hash);  //就是返回hash

注意,这里用到了目录项名字数据结构qstr:

struct qstr {

     unsigned int hash;

     unsigned int len;

     const unsigned char *name;

当前目录分量存放到了指向qstr结构的this内部变量中,即this-name = "usr",而name的值变成了/share/system-configure-ext2/test.conf。

其散列表的32位散列值如下计算:

partial_name_hash(unsigned long c, unsigned long prevhash)

     return (prevhash + (c << 4) + (c >> 4)) * 11;

  c) 如果“/”终止了要解析的分量名,则跳过“/”之后的任何尾部“/”。

     while (*++name == '/');

          goto last_with_slashes;

  d) 如果要解析的分量是原路径名中的最后一个分量,则跳到第6步。

     if (!c)

          goto last_component;

  e) 如果分量名是一个“.”(单个圆点),则继续下一个分量(“.”指的是当前目录,因此,这个点在目录内没有什么效果)。如果分量名是“..”(两个圆点),则尝试回到父目录:

     if (this.name[0] == '.') switch (this.len) {

          default:

          case 2:    

               if (this.name[1] != '.')

               follow_dotdot(nd);

               /* fallthrough */

          case 1:

               continue;

这里面有个重要的follow_dotdot(nd)函数:

static __always_inline void follow_dotdot(struct nameidata *nd)

     while(1) {

          struct vfsmount *parent;

          struct dentry *old = nd->dentry;

                read_lock(&current->fs->lock);

          if (nd->dentry == current->fs->root &&

              nd->mnt == current->fs->rootmnt) {

                        read_unlock(&current->fs->lock);

                read_unlock(&current->fs->lock);

          spin_lock(&dcache_lock);

          if (nd->dentry != nd->mnt->mnt_root) {

               nd->dentry = dget(nd->dentry->d_parent);

               spin_unlock(&dcache_lock);

               dput(old);

          spin_unlock(&dcache_lock);

          spin_lock(&vfsmount_lock);

          parent = nd->mnt->mnt_parent;

          if (parent == nd->mnt) {

               spin_unlock(&vfsmount_lock);

          mntget(parent);

          nd->dentry = dget(nd->mnt->mnt_mountpoint);

          spin_unlock(&vfsmount_lock);

          dput(old);

          mntput(nd->mnt);

          nd->mnt = parent;

     follow_mount(&nd->mnt, &nd->dentry);

i. 如果最近解析的目录是进程的根目录(nd->dentry等于curren->fs->root,而nd->mnt等于current->fs->rootmnt),那么再向上追踪是不允许的:在最近解析的分量上调用follow_mount()(见下面),继续下一个分量。

ii. 如果最近解析的目录是nd->mnt文件系统的根目录(nd->dentry等于nd->mnt->mnt_root),并且这个文件系统也没有被安装在其他文件系统之上(nd->mnt等于nd->mnt->mnt_parent),那么nd->mnt文件系统通常就是命名空间的根文件系统:在这种情况下,再向上追踪是不可能的,因此在最近解析的分量上调用follow_mount()(参见下面),继续下一个分量。

iii. 如果最近解析的目录是nd->mnt文件系统的根目录,而这个文件系统被安装在其他文件系统之上,那么就需要文件系统交换。因此,把nd->dentry置为nd->mnt->mnt_mountpoint,且把nd->mnt置为nd->mnt->mnt_parent,然后重新开始第5g步(回想一下,几个文件系统可以安装在同一个安装点上)。

iv. 如果最近解析的目录不是已安装文件系统的根目录,那么必须回到父目录:把nd->dentry置为nd->dentry->d_parent,在父目录上调用follow_mount(),继续下一个分量。

static void follow_mount(struct vfsmount **mnt, struct dentry **dentry)

     while (d_mountpoint(*dentry)) {

          struct vfsmount *mounted = lookup_mnt(*mnt, *dentry);

          if (!mounted)

          dput(*dentry);

          mntput(*mnt);

          *mnt = mounted;

          *dentry = dget(mounted->mnt_root);

follow_mount()函数检查nd->dentry是否是某文件系统的安装点(nd->dentry->d_mounted的值大于0);如果是,则调用lookup_mnt()搜索目录项高速缓存中已安装文件系统的根目录,并把nd->dentry和nd->mnt更新为相应已安装文件系统的安装点和安装系统对象的地址;然后重复整个操作(几个文件系统可以安装在同一个安装点上)。从本质上说,由于进程可能从某个文件系统的目录开始路径名的查找,而该目录被另一个安装在其父目录上的文件系统所隐藏,那么当需要回到父目录时,则调用follow_mount()函数。

回到__link_path_walk中,如果:

  f) 分量名既不是“.”,也不是“..”,因此函数必须在目录项高速缓存中查找它。如果低级文件系统有一个自定义的d_hash目录项方法,则调用它来修改已在第5b步计算出的散列值。

     if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {

          err = nd->dentry->d_op->d_hash(nd->dentry, &this);

          if (err < 0)

  g) 调用do_lookup (),得到与给定的父目录(nd->dentry)和文件名(要解析的路径名分量&next结果参数)相关的目录项对象,存放在结果参数next中:

     err = do_lookup(nd, &this, &next, atomic);

注意,这里又要介绍一个path数据结构,next局部变量就是一个该结构:

struct path {

     struct vfsmount *mnt;

     struct dentry *dentry;

static int do_lookup(struct nameidata *nd, struct qstr *name,

               struct path *path, int atomic)

     struct vfsmount *mnt = nd->mnt;

     struct dentry *dentry = __d_lookup(nd->dentry, name);

     if (!dentry)

          goto need_lookup;

     if (dentry->d_op && dentry->d_op->d_revalidate)

          goto need_revalidate;

done:

     path->mnt = mnt;

     path->dentry = dentry;

     __follow_mount(path);

     return 0;

need_lookup:

     if (atomic)

          return -EWOULDBLOCKIO;

     dentry = real_lookup(nd->dentry, name, nd);

     if (IS_ERR(dentry))

          goto fail;

     goto done;

need_revalidate:

     if (atomic) {

          dput(dentry);

     dentry = do_revalidate(dentry, nd);

fail:

     return PTR_ERR(dentry);

该函数本质上首先调用__d_lookup()在目录项高速缓存中搜索分量的目录项对象:

struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)

     unsigned int len = name->len;

     unsigned int hash = name->hash;

     const unsigned char *str = name->name;

     struct hlist_head *head = d_hash(parent,hash);

     struct dentry *found = NULL;

     struct hlist_node *node;

     rcu_read_lock();

     hlist_for_each_entry_rcu(dentry, node, head, d_hash) {

          struct qstr *qstr;

          if (dentry->d_name.hash != hash)

          if (dentry->d_parent != parent)

          spin_lock(&dentry->d_lock);

          ……

          if (!d_unhashed(dentry)) {

               atomic_inc(&dentry->d_count);

               found = dentry;

          spin_unlock(&dentry->d_lock);

next:

      }

      rcu_read_unlock();

      return found;

如果没有找到这样的目录项对象,则调用real_lookup()。而real_lookup()执行索引节点的lookup方法从磁盘读取目录,创建一个新的目录项对象并把它插入到目录项高速缓存中,然后创建一个新的索引节点对象并把它插入到索引节点高速缓存中(在少数情况下,函数real_lookup()可能发现所请求的索引节点已经在索引节点高速缓存中。路径名分量是最后一个路径名而且不是指向一个目录,与路径名相应的文件有几个硬健接,并且最近通过与这个路径名中被使用过的硬健接不同的硬链接访问过相应的文件)。在这一步结束时,next局部变量中的dentry和mnt字段将分别指向这次循环要解析的分量名的目录项对象和已安装文件系统对象。

  h) 调用follow_mount()函数检查刚解析的分量(next.dentry)是否指向某个文件系统安装点的一个目录(next.dentry->d_mounted值大于0)。follow_mount()更新next.dentry和next.mnt的值,以使它们指向由这个路径名分量所表示的目录上安装的最上层文件系统的目录项对象和已安装文件系统对象(参见第5f步)。

  j) 把nd->dentry和nd->mnt分别置为next.dentry和next.mnt,然后继续路径名的下一个分量:

     path_to_nameidata(&next, nd);

static inline void path_to_nameidata(struct path *path, struct nameidata *nd)

     dput(nd->dentry);

     if (nd->mnt != path->mnt)

     nd->mnt = path->mnt;

     nd->dentry = path->dentry;

  k) 检查刚解析的分量是否指向一个目录(next.dentry->d_inode具有一个自定义的lookup方法)。如果没有,返回一个错误码-ENOTDIR,因为这个分量位于原路径名的中间,然后continue继续路径名的下一个分量:

     if (!inode->i_op->lookup)

     continue;

6 好啦,if (!c)的话,说明除了最后一个分量,原路径名的所有分量都被解析,即循环体的qstr结构的this指向最后一个分量(/test.conf),name内部变量已经是NULL了,nd指向的dentry对应this的前一个分量(/system-configure-ext2/test.conf),此时goto last_component;如果跳出循环的话,那么清除nd->flags中的LOOKUP_CONTINUE标志:

nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;

7 检查lookup_flags变量中LOOKUP_PARENT标志的值。下面假定这个标志被置为0,并把相反的情况推迟到lookup_parent,下一博文介绍。

8 如果最后一个分量名是“.”(单个圆点),则终止执行并返回值0(无错误)。在nd指向的nameidata数据结构中,dentry和mnt字段指向路径名中倒数第二个分量对应的对象(/system-configure-ext2/test.conf,任何分量“.”在路径名中没有效果)。

9 如果最后一个分量名是“..”(两个圆点),则做跟第5f相似的工作,尝试回到父目录:

a) 如果最后解析的目录是进程的根目录(nd->dentry等于current->fs->root,nd->mnt等于current->fs->rootmnt),则在倒数第二个分量上调用follow_mount(),终止执行并返回值0(无错误)。nd->dentry和nd->mnt指向路径名的倒数第二个分量对应的对象,也就是进程的根目录。

b) 如果最后解析的目录是nd->mnt文件系统的根目录(nd->dentry等于nd->mnt->mnt_root),并且该文件系统没有被安装在另一个文件系统之上(nd->mnt等于nd->mnt->mnt_parent),那么再向上搜索是不可能的,因此在倒数第二个分量上调用follow_mount(),终止执行并返回值0(无错误)。

c) 如果最后解析的目录是nd->mnt文件系统的根目录,并且该文件系统被安装在其他文件系统之上,那么把nd->dentry和nd->mnt分别置为nd->mnt->mnt_mountpoint和nd->mnt_mnt_parent,然后重新执行第10步。

d) 如果最后解析的目录不是已安装文件系统的根目录,则把nd->dentry置为nd->dentry->d_parent,在父目录上调用follow_mount(),终止执行并返回值0(无错误)。nd->dentry和nd->mnt指向前一个分量(即路径名倒数第二个分量)对应的对象。

10. 路径名的最后分量名既不是“.”也不是“..”,因此,必须用do_lookup在高速缓存中查找它。如果低级文件系统有自定义的d_hash目录项方法,则该函数调用它来修改在第5c步已经计算出的散列值。

12. 要解析的分量不是一个符号链接或符号链接不该被解释。把nd->mnt和nd->dentry字段分别置为next.mnt和next.dentry的值。最后的目录项对象就是整个查找操作的结果:

path_to_nameidata(&next, nd);

13. 检查nd->dentry->d_inode是否为NULL。这发生在没有索引节点与目录项对象关联时,通常是因为路径名指向一个不存在的文件。在这种情况下,返回一个错误码-ENOENT。

14. 路径名的最后一个分量有一个关联的索引节点。如果在lookup_flags中设置了LOOKUP_DIRECTORY标志,则检查索引节点是否有一个自定义的lookup方法,也就是说它是一个目录。如果没有,则返回一个错误码-ENOTDIR。

     if (lookup_flags & LOOKUP_DIRECTORY) {

          if (!inode->i_op || !inode->i_op->lookup)

15. 返回值0(无错误)。nd->dentry和nd->mnt指向路径名的最后分量。

     goto return_base;