do_lookup()路径名查找

来源:互联网 发布:淘宝买家长时间不发货 编辑:程序博客网 时间:2024/05/16 12:04

转:

http://blog.chinaunix.net/uid-12567959-id-160998.html

do_lookup()根据父目录的路径,及文件名来找到文件的路径,也就是目录项和vfsmount,回忆一下,do_lookup()的调用环境,在link_path_walk()中有:

fs/namei.c                 nd->flags |= LOOKUP_CONTINUE;                 err = exec_permission(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)                         goto last_component;                 while (*++name == '/');                   if (!*name)                         goto last_with_slashes;                  err = do_lookup(nd, &this, &next);                 if (err)                         break;

在qstr结构局部变量this中存有路径分量的信息,包括文件名字符串地址及其长度,根据文件名算得的哈希值,nd变量中存有父路径的信息,包括vfsmount对象地址和目录项对象地址。Path结构体类型的next变量用来存放查找的结果。
 
do_lookup()接受3个参数,nd保存有要查找的分量所在的目录的信息,name要查找的分量的名字信息,path则用于返回查找的结果。do_lookup()定义如下:

fs/namei.c static int do_lookup(struct nameidata *nd, struct qstr *name,                      struct path *path) {         struct vfsmount *mnt = nd->path.mnt;         struct dentry *dentry, *parent;         struct inode *dir;         /*          * 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) {                 int err = nd->path.dentry->d_op->d_hash(nd->path.dentry, name);                 if (err < 0)                         return err;         }         dentry = __d_lookup(nd->path.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:         parent = nd->path.dentry;         dir = parent->d_inode;         mutex_lock(&dir->i_mutex);         /*          * First re-do the cached lookup just in case it was created          * while we waited for the directory semaphore..          *          * FIXME! This could use version numbering or similar to          * avoid unnecessary cache lookups.          *          * The "dcache_lock" is purely to protect the RCU list walker          * from concurrent renames at this point (we mustn't get false          * negatives from the RCU list walk here, unlike the optimistic          * fast walk).          *          * so doing d_lookup() (with seqlock), instead of lockfree __d_lookup          */         dentry = d_lookup(parent, name);         if (!dentry) {                 struct dentry *new;                 /* Don't create child dentry for a dead directory. */                 dentry = ERR_PTR(-ENOENT);                 if (IS_DEADDIR(dir))                         goto out_unlock;                 new = d_alloc(parent, name);                 dentry = ERR_PTR(-ENOMEM);                 if (new) {                         dentry = dir->i_op->lookup(dir, new, nd);                         if (dentry)                                 dput(new);                         else                                 dentry = new;                 } out_unlock:                 mutex_unlock(&dir->i_mutex);                 if (IS_ERR(dentry))                         goto fail;                 goto done;         }         /*          * Uhhuh! Nasty case: the cache was re-populated while          * we waited on the semaphore. Need to revalidate.          */         mutex_unlock(&dir->i_mutex);         if (dentry->d_op && dentry->d_op->d_revalidate) {                 dentry = do_revalidate(dentry, nd);                 if (!dentry)                         dentry = ERR_PTR(-ENOENT);         }         if (IS_ERR(dentry))                 goto fail;         goto done; need_revalidate:         dentry = do_revalidate(dentry, nd);         if (!dentry)                 goto need_lookup;         if (IS_ERR(dentry))                 goto fail;         goto done; fail:         return PTR_ERR(dentry); }

这个函数也有点长,但结构还算清晰。
1、检查底层文件系统是否要使用它自己的哈希方法(nd->path.dentry->d_op->d_hash),若是,则调用该方法来更新已经计算出的分量名的哈希值。
 
2、调用__d_lookup(nd->path.dentry, name)来在目录项高速缓存中搜索分量的目录项对象。该函数定义如下:

fs/dcache.c 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;         struct dentry *dentry;         rcu_read_lock();                 hlist_for_each_entry_rcu(dentry, node, head, d_hash) {                 struct qstr *qstr;                 if (dentry->d_name.hash != hash)                         continue;                 if (dentry->d_parent != parent)                         continue;                 spin_lock(&dentry->d_lock);                 /*                  * Recheck the dentry after taking the lock - d_move may have                  * changed things.  Don't bother checking the hash because we're                  * about to compare the whole name anyway.                  */                 if (dentry->d_parent != parent)                         goto next;                 /* non-existing due to RCU? */                 if (d_unhashed(dentry))                         goto next;                 /*                  * It is safe to compare names since d_move() cannot                  * change the qstr (protected by d_lock).                  */                 qstr = &dentry->d_name;                 if (parent->d_op && parent->d_op->d_compare) {                         if (parent->d_op->d_compare(parent, qstr, name))                                 goto next;                 } else {                         if (qstr->len != len)                                 goto next;                         if (memcmp(qstr->name, str, len))                                 goto next;                 }                 atomic_inc(&dentry->d_count);                 found = dentry;                 spin_unlock(&dentry->d_lock);                 break; next:                 spin_unlock(&dentry->d_lock);         }         rcu_read_unlock();         return found; }

a.调用d_hash(parent,hash)来找到目录项可能存在于其中的哈希表项,也就是hlist_head指针,存放在局部变量head中。

fs/dcache.c static inline struct hlist_head *d_hash(struct dentry *parent,                                         unsigned long hash) {         hash += ((unsigned long) parent ^ GOLDEN_RATIO_PRIME) / L1_CACHE_BYTES;         hash = hash ^ ((hash ^ GOLDEN_RATIO_PRIME) >> D_HASHBITS);         return dentry_hashtable + (hash & D_HASHMASK); }

b.执行循环hlist_for_each_entry_rcu(dentry, node, head, d_hash),在链表中查找
c.返回查找结果。若没找到,则返回NULL,若找到则返回目录项。
3、如果没有找到这样的目录项对象,则执行如下操作:
a.首先,获得要父目录的inode的i_mutex锁。
b.调用d_lookup(parent, name)来在目录项缓存中查找,以防在上一步等待信号量的时候已经有进程创建了我们要查找的目录项。
c.如果d_lookup(parent, name)返回非NULL值,则首先解对父目录的inode的i_mutex锁,然后检查dentry->d_op->d_revalidate方法是否有效,若是对查找结果dentry调用它,该方法成功返回时do_lookup(),设置path->mnt为nd->path.mnt,path->dentry为查找到的目录项dentry。然后在path上调用__follow_mount(path)并返回0。该方法失败时,则返回错误码。 
d.如果d_lookup(parent, name)依然返回NULL值,即说明目录项缓存中依然没有我们要查找的目录项。则
(1)、首先检查提供的父目录路径是不是真的是一个目录文件,若不是对对父目录的inode解锁并返回-ENOENT。
(2)、父目录路径是一个目录文件。则调用d_alloc(parent, name)来分配并填充一个目录项。其定义为:

fs/dcache.c struct dentry *d_alloc(struct dentry * parent, const struct qstr *name) {         struct dentry *dentry;         char *dname;         dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);         if (!dentry)                 return NULL;         if (name->len > DNAME_INLINE_LEN-1) {                 dname = kmalloc(name->len + 1, GFP_KERNEL);                 if (!dname) {                         kmem_cache_free(dentry_cache, dentry);                         return NULL;                 }         } else  {                 dname = dentry->d_iname;         }               dentry->d_name.name = dname;         dentry->d_name.len = name->len;         dentry->d_name.hash = name->hash;         memcpy(dname, name->name, name->len);         dname[name->len] = 0;         atomic_set(&dentry->d_count, 1);         dentry->d_flags = DCACHE_UNHASHED;         spin_lock_init(&dentry->d_lock);         dentry->d_inode = NULL;         dentry->d_parent = NULL;         dentry->d_sb = NULL;         dentry->d_op = NULL;         dentry->d_fsdata = NULL;         dentry->d_mounted = 0;         INIT_HLIST_NODE(&dentry->d_hash);         INIT_LIST_HEAD(&dentry->d_lru);         INIT_LIST_HEAD(&dentry->d_subdirs);         INIT_LIST_HEAD(&dentry->d_alias);         if (parent) {                 dentry->d_parent = dget(parent);                 dentry->d_sb = parent->d_sb;         } else {                 INIT_LIST_HEAD(&dentry->d_u.d_child);         }         spin_lock(&dcache_lock);         if (parent)                 list_add(&dentry->d_u.d_child, &parent->d_subdirs);         dentry_stat.nr_dentry++;         spin_unlock(&dcache_lock);         return dentry; }
(3)、若分配失败则返回-ENOMEM。
(4)、成功分配目录项,则执行父目录索引节点的lookup方法从磁盘读取该目录,创建一个新的目录项对象并把它插入到目录项高速缓存中,然后创建一个新的索引节点对象并把它插入到索引节点高速缓存中。解除对父目录的inode的i_mutex锁,然后检查返回值的类型,若是错误码,则返回错误码。若是有效地目录项,则设置path->mnt为nd->path.mnt,path->dentry为查找到的目录项dentry。然后在path上调用__follow_mount(path)并返回0。
4、非常幸运的直接在目录项缓存中找到了要查找的目录项对象,则
a.调用do_revalidate(dentry, nd)检查其有效性,若返回NULL,则执行同第3不完全相同的操作。

b. 若返回非NULL,检查返回值的类型,若是错误码,则返回错误码。若是有效地目录项,则设置path->mnt为nd->path.mnt,path->dentry为查找到的目录项dentry。然后在path上调用__follow_mount(path)并返回0。


在do_lookup函数中调用__d_lookup返回后,并找到相应的目录项后,会回到:

done:path->mnt = mnt;//设置path->mnt为nd->path.mntpath->dentry = dentry;//,path->dentry为查找到的目录项dentry__follow_mount(path);return 0;

注意这里的__follow_mount

static int __follow_mount(struct path *path){int res = 0;while (d_mountpoint(path->dentry)) {struct vfsmount *mounted = lookup_mnt(path);if (!mounted)break;dput(path->dentry);if (res)mntput(path->mnt);path->mnt = mounted;path->dentry = dget(mounted->mnt_root);res = 1;}return res;}

这里会检查 当前的这个path的目录项是不是挂载点,如果是挂载点的话会调用lookup_mnt进行进一步的处理

/* * lookup_mnt increments the ref count before returning * the vfsmount struct. */struct vfsmount *lookup_mnt(struct path *path){struct vfsmount *child_mnt;spin_lock(&vfsmount_lock);if ((child_mnt = __lookup_mnt(path->mnt, path->dentry, 1)))mntget(child_mnt);spin_unlock(&vfsmount_lock);return child_mnt;}

lookup_mnt() 函数用于查找一个 VFS 目录树下某一目录最近一次被 mount 时的安装区域块的指针,这样当这个目录有新的挂载点时,查找的就是这个最新的挂载点下的目录。