linux dentry cache 转自:http://blog.csdn.net/denzilxu/article/details/9188003

Linux dentry cache学习

每个dentry对象都属于下列几种状态之一:

 

(1)未使用(unused)状态:该dentry对象的引用计数d_count的值为0,但其d_inode指针仍然指向相关的的索引节点。该目录项仍然包含有效的信息,只是当前没有人引用他。这种dentry对象在回收内存时可能会被释放。

 

(2)正在使用(inuse)状态:处于该状态下的dentry对象的引用计数d_count大于0,且其d_inode指向相关的inode对象。这种dentry对象不能被释放。

 

3)负(negative)状态:与目录项相关的inode对象不复存在(相应的磁盘索引节点可能已经被删除),dentry对象的d_inode指针为NULL。但这种dentry对象仍然保存在dcache中,以便后续对同一文件名的查找能够快速完成。这种dentry对象在回收内存时将首先被释放。

 

Linux为了提高目录项对象的处理效率,设计与实现了目录项高速缓存(dentry cache,简称dcache),它主要由两个数据结构组成:

1. 哈希链表dentry_hashtabledcache中的所有dentry对象都通过d_hash指针域链到相应的dentry哈希链表中。

 

2. 未使用的dentry对象链表dentry_unuseddcache中所有处于“unused”状态和“negative”状态的dentry对象都通过其d_lru指针域链入dentry_unused链表中。该链表也称为LRU链表。

 

目录项高速缓存dcache是索引节点缓存icache的主控器(master),也即dcache中的dentry对象控制着icache中的inode对象的生命期转换。无论何时,只要一个目录项对象存在于dcache中(非 negative状态),则相应的inode就将总是存在,因为inode的引用计数i_count总是大于0。当dcache中的一个dentry被释放时,针对相应inode对象的iput()方法就会被调用。

struct dentry {

       atomic_t d_count;

       unsigned int d_flags;             /* protected by d_lock */

       spinlock_t d_lock;         /* per dentry lock */

       struct inode *d_inode;           /* Where the name belongs to - NULL is * negative */

       /*

        * The next three fields are touched by __d_lookup.  Place them here

        * so they all fit in a cache line.

        */

       struct hlist_node d_hash;       /* lookup hash list */

       struct dentry *d_parent; /* parent directory */

       struct qstr d_name;//contain the name,length,hash value of this dentry

 

       struct list_head d_lru;           /* LRU list */

       /*

        * d_child and d_rcu can share memory

        */

       union {

              struct list_head d_child; /* child of parent list */

             struct rcu_head d_rcu;

       } d_u;

       struct list_head d_subdirs;     /* our children ,all the children are linked together*/

       struct list_head d_alias;  /* inode alias list ,list of all the dentry share the same inode*/

       unsigned long d_time;           /* used by d_revalidate */

       struct dentry_operations *d_op;

       struct super_block *d_sb;      /* The root of the dentry tree */

       void *d_fsdata;                    /* fs-specific data */

#ifdef CONFIG_PROFILING

       struct dcookie_struct *d_cookie; /* cookie, if any */

#endif

       int d_mounted;

       unsigned char d_iname[DNAME_INLINE_LEN_MIN];     /* small names */

};

11 分配接口

dcachekmem_cache_alloc()的基础上定义两个高层分配接口:d_alloc()函数和d_alloc_root()函数,用来从dentry_cache slab分配器缓存中为一般的目录项和根目录分配一个dentry对象。

Dentry本身是一个树形结构,d_allocd_alloc_root用于build这棵树:

struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)

函数d_alloc_root()用来为fs的根目录(并不一定是系统全局文件系统的根“/”)分配dentry对象。它以根目录的inode对象指针为参数,如下所示:

struct dentry * d_alloc_root(struct inode * root_inode)

{

       struct dentry *res = NULL;

 

       if (root_inode) {

              static const struct qstr name = { .name = "/", .len = 1 };

 

              res = d_alloc(NULL, &name);

              if (res) {

                     res->d_sb = root_inode->i_sb;

                     res->d_parent = res;//将所分配的dentry对象的d_parent指针设置为指向自身。NOTE!这一点是判断一个dentry对象是否是一个fs的根目录的唯一准则(includelinuxdcache.h):#define IS_ROOTx)((x)==(x)->d_parent

                     d_instantiate(res, root_inode);

              }

       }

       return res;

}

 

函数d_instantiate用于向dentry结构中填写inode信息,因此该函数会将一个dentry对象从negative状态转变为inuse状态。如下所示:

void d_instantiate(struct dentry *entry, struct inode * inode) {

spin_lock(&dcache_lock);

if (inode)

list_add(&entry->d_alias, &inode->i_dentry);

entry->d_inode = inode;

spin_unlock(&dcache_lock);

}

NOTE! 函数d_instantiate()假定在被调用之前,调用者已经增加了inode的引用计数。

 

struct dentry *d_alloc_name(struct dentry *parent, const char *name)

{

       struct qstr q;

 

       q.name = name;

       q.len = strlen(name);

       q.hash = full_name_hash(q.name, q.len);

       return d_alloc(parent, &q);

}

 

12 释放接口

目录项缓存dcache定义了两个高层释放接口:d_free()函数和 dentry_iput()函数。其中,d_free函数用来将dcache中不使用的dentry对象释放回dentry_cache slab分配器缓存;而dentry_iput()函数则用来释放一个dentry对象对一个inode对象的引用关联。

函数d_free()首先调用dentry对象操作方法中的d_release ()函数(如果定义了的话),通常在d_release()函数中释放dentry->fsdata数据。然后,用dname_external ()函数判断是否已经为目录项名字d_name分配了内存,如果是,则调用kfree()函数释放d_name所占用的内存。接下来,调用 kmem_cache_free()函数释放这个dentry对象。最后,修改dcache统计信息中的dentry对象总数(减1)。其源码如下:

D_free__d_free主要用于Dentry内存释放
/*

 * no dcache_lock, please.  The caller must decrement dentry_stat.nr_dentry

 * inside dcache_lock.

 */

static void d_free(struct dentry *dentry)

{

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

              dentry->d_op->d_release(dentry);//主要用于释放dentry->fsdata数据

       /* if dentry was never inserted into hash, immediate free is OK */

       if (hlist_unhashed(&dentry->d_hash))

              __d_free(dentry);

       else

              call_rcu(&dentry->d_u.d_rcu, d_callback);

}

static void __d_free(struct dentry *dentry)

{

       WARN_ON(!list_empty(&dentry->d_alias));

       if (dname_external(dentry))//判断是否为d_name分配了内存

              kfree(dentry->d_name.name);

       kmem_cache_free(dentry_cache, dentry);

}

 

dentry_iput()函数则主要用于在调用d_free()函数释放一个 dentry对象之前,释放该dentry对象与相应inode对象的关联,从而将一个dentry对象转变为negative状态。主要包括如下几项任务:(1)将这个dentry对象从相应inode对象的别名链表i_dentry中摘除;(2)解除自旋锁dcache_lock;(3)调用 dentry的操作方法d_iput()函数(如果有的话),或者iput()方法。

该函数与d_instantiate()函数是相反的,如下:

*

 * Release the dentry's inode, using the filesystem

 * d_iput() operation if defined.

 */

static void dentry_iput(struct dentry * dentry)//0401

       __releases(dentry->d_lock)

       __releases(dcache_lock)

{

       struct inode *inode = dentry->d_inode;

       if (inode) {

              dentry->d_inode = NULL;

              list_del_init(&dentry->d_alias);

              spin_unlock(&dentry->d_lock);

              spin_unlock(&dcache_lock);

              if (!inode->i_nlink)

                     fsnotify_inoderemove(inode);

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

                     dentry->d_op->d_iput(dentry, inode);

              else

                     iput(inode);

       } else {

              spin_unlock(&dentry->d_lock);

              spin_unlock(&dcache_lock);

       }

}

NOTE

1)如果定义了dentry方法d_iput(),则dentry_iput()通过调用d_iput()方法来释放inode对象,否则就通过iput()来释放inode对象。

2dentry_iput()函数假定被调用时调用者已经持有了dcache_lock锁。因此它在返回之前对该自旋锁进行解锁。

 

2 dcache数据结构

 

Linux通过在dentry_cache slab分配器缓存上定义了各种dentry链表来有效地管理目录项对象,从而实现dcache机制。它们包括:

1. dentry对象的哈希链表dentry_hashtable

2. dentry对象的LRU链表dentry_unused

3. 每个索引节点对象的别名链表i_dentry。每个非negative状态的dentry对象都通过d_alias指针域链入其对应的inode对象的别名链表i_dentry中。

4. 父目录dentry对象的子目录项(目录或文件)链表d_subdirs。每个dentry对象都通过d_child指针域链入其父目录dentry对象的子目录项链表d_subdirs中。

 

21 哈希链表dentry_hashtable

Dentry_hashtable的初始化是在dcache_init_early或者dcache_init中实现的:

static void __init dcache_init_early(void){

       int loop;

 

       /* If hashes are distributed across NUMA nodes, defer

        * hash allocation until vmalloc space is available.

        */

       if (hashdist)

              return;

 

       dentry_hashtable =

              alloc_large_system_hash("Dentry cache",

                                   sizeof(struct hlist_head),

                                   dhash_entries,

                                   13,

                                   HASH_EARLY,

                                   &d_hash_shift,

                                   &d_hash_mask,

                                   0);

 

       for (loop = 0; loop < (1 << d_hash_shift); loop++)

              INIT_HLIST_HEAD(&dentry_hashtable[loop]);

}

 

static void __init dcache_init(void)

{

       int loop;

 

       /*

        * A constructor could be added for stable state like the lists,

        * but it is probably not worth it because of the cache nature

        * of the dcache.

        */

       dentry_cache = KMEM_CACHE(dentry,

              SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD);

      

       register_shrinker(&dcache_shrinker);

 

       /* Hash may have been set up in dcache_init_early */

       if (!hashdist)

              return;

 

       dentry_hashtable =

              alloc_large_system_hash("Dentry cache",

                                   sizeof(struct hlist_head),

                                   dhash_entries,

                                   13,

                                   0,

                                   &d_hash_shift,

                                   &d_hash_mask,

                                   0);

 

       for (loop = 0; loop < (1 << d_hash_shift); loop++)

              INIT_HLIST_HEAD(&dentry_hashtable[loop]);

}

每一个dentry对象都通过其父目录dentry对象的指针和其文件名的哈希值hash来唯一地确定它所属的哈希链表的表头指针,这是通过d_hash函数来完成的:

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

}

每个目录项文件名的哈希值是通过full_name_hash()函数(定义在include/linux/dcache.h文件中)来计算的,如下所示:

/* Compute the hash for a name string. */

static inline unsigned int full_name_hash(const unsigned char *name, unsigned int len){

       unsigned long hash = init_name_hash();

       while (len--)

              hash = partial_name_hash(*name++, hash);

       return end_name_hash(hash);

}

 

22 dentry对象的LRU链表

对于那些处于“未使用”状态的dentry对象来说,它们被再次访问的可能性很大。因此,不能将它们立即丢弃,而必须将它们在dcache中保留一段时间。为此,Linux通过LRU链表来有效地管理这些未使用的dentry对象。每一个处于unused状态下的dentry通过其d_lru指针域链入系统全局的LRU链表,表头包含在super_block::s_dentry_lru中。

从某种程度上讲,super_block::s_dentry_lru链表就是处于inuse状态下的dentry对象的直接缓存。当一个dentry不再被使用时,它首先应被移到LRU链表中,而不是直接将其丢弃,因为该dentry对象很可能会再次被引用。

另一方面,由于super_block::s_dentry_lru链表中的目录项对象是未使用的,因此当内存紧张时,应该将其中一些很长时间内未被使用的dentry对象释放掉,以缓解系统的压力。

 

23 dcache链表的保护锁

Linuxdcache.c文件中定义了自旋锁dcache_lock,来实现对dcache链表的互斥访问。也即,任何一段想要访问任何一条dcache链表的代码段,都必须首先持有该自旋锁。其定义如下:

spinlock_t dcache_lock = SPIN_LOCK_UNLOCKED;

 

24 dcache统计信息

Linuxdcache.c文件中定义了全局变量dentry_stat来表示dcache的统计信息,如下:

struct dentry_stat_t {

       int nr_dentry;

       int nr_unused;

       int age_limit;          /* age in seconds */

       int want_pages;         /* pages requested by system */

       int dummy[2];

};

extern struct dentry_stat_t dentry_stat;

 

3 dentry访问接口——dgetdput函数

要引用dcache中的任何一个dentry对象,都必须通过应用接口函数dget()dget_locked()来进行;然后在使用完这个dentry对象后,通过释放引用接口dput()函数来释放对它的引用。

 

31 引用接口

引用函数dget()仅仅简单地增加dentry对象的引用计数器,如下所示(dcache.h):

static inline struct dentry *dget(struct dentry *dentry)

{

       if (dentry) {

              BUG_ON(!atomic_read(&dentry->d_count));//这里可知,对于dentry引用计数为0dentry决不能使用dget,而应使用dget_locked。因为:引用一个d_count0dentry对象,将使该dentry对象从unused状态转变为inuse状态,该dentry状态也必须从LRU链表中脱离,而在操作dcache链表时是必须先持有自旋锁dcache_lock的。函数dget()并不对调用者由任何调用假设,相反,dget_locked()函数则假定调用者在调用它之前已经持有自旋锁dentry_lock

              atomic_inc(&dentry->d_count);

       }

       return dentry;

}

 

/* This should be called _only_ with dcache_lock held */

static inline struct dentry * __dget_locked(struct dentry *dentry)

{

       atomic_inc(&dentry->d_count);

       dentry_lru_del_init(dentry);

       return dentry;

}

 

struct dentry * dget_locked(struct dentry *dentry)

{

       return __dget_locked(dentry);

}

 

32 释放接口dput

函数dput()用于释放对一个dentry对象的引用。该函数的核心就是将 dentry对象的引用计数d_count1。如果d_count1后还不为0,则dput直接返回即可;否则就将该dentry对象放到LRU链表中,或直接释放掉(在该dentry对象未链入哈希链表的情况下)。其源码如下:

/ * This is dput

 *

 * This is complicated by the fact that we do not want to put

 * dentries that are no longer on any hash chain on the unused

 * list: we'd much rather just get rid of them immediately.

 *

 * However, that implies that we have to traverse the dentry

 * tree upwards to the parents which might _also_ now be

 * scheduled for deletion (it may have been only waiting for

 * its last child to go away).

 *

 * This tail recursion is done by hand as we don't want to depend

 * on the compiler to always get this right (gcc generally doesn't).

 * Real recursion would eat up our stack space.

 */

 

/*

 * dput - release a dentry

 * @dentry: dentry to release

 *

 * Release a dentry. This will drop the usage count and if appropriate

 * call the dentry unlink method as well as removing it from the queues and

 * releasing its resources. If the parent dentries were scheduled for release

 * they too may now get deleted.

 *

 * no dcache lock, please.

 */

void dput(struct dentry *dentry)//d_put函数有点麻烦

{

       if (!dentry)

              return;

 

repeat:

       if (atomic_read(&dentry->d_count) == 1)

              might_sleep();

       if (!atomic_dec_and_lock(&dentry->d_count, &dcache_lock))

              return;

 

       spin_lock(&dentry->d_lock);

       if (atomic_read(&dentry->d_count)) {

              spin_unlock(&dentry->d_lock);

              spin_unlock(&dcache_lock);

              return;

       }

 

       /*

        * AV: ->d_delete() is _NOT_ allowed to block now.

        */

       if (dentry->d_op && dentry->d_op->d_delete) {

              if (dentry->d_op->d_delete(dentry))//返回值非0(执行出错)

                     goto unhash_it;

       }

       /* Unreachable? Get rid of it */

      if (d_unhashed(dentry))//不在hash表中

              goto kill_it;

      if (list_empty(&dentry->d_lru)) {//hash链表中,则将其移植到lru链表的首部

             dentry->d_flags |= DCACHE_REFERENCED;

              dentry_lru_add(dentry);

      }

      spin_unlock(&dentry->d_lock);

       spin_unlock(&dcache_lock);

       return;

 

unhash_it:

       __d_drop(dentry);//unhash from the dentry hash

kill_it:

       /* if dentry was on the d_lru list delete it from there */

       dentry_lru_del(dentry);//delete from the d_lru list

       dentry = d_kill(dentry);//kill the dentry and return the parent(如果dentry->d_parent指向自身,则代表fs的根目录,于是d_kill返回NULL

       if (dentry)//parent进行递归处理

              goto repeat;

}

/**

 * d_kill - kill dentry and return parent

 * @dentry: dentry to kill

 *

 * The dentry must already be unhashed and removed from the LRU.

 * If this is the root of the dentry tree, return NULL.

 */

static struct dentry *d_kill(struct dentry *dentry)

       __releases(dentry->d_lock)

       __releases(dcache_lock){

       struct dentry *parent;

 

       list_del(&dentry->d_u.d_child);//parentchildren list中删除

       dentry_stat.nr_dentry--; /* For d_free, below */

       /*drops the locks, at that point nobody can reach this dentry */

       dentry_iput(dentry);//release the dentry's inode

       if (IS_ROOT(dentry))//判断是否满足dentry->d_parent=dentry

              parent = NULL;

       else

              parent = dentry->d_parent;

       d_free(dentry);//释放占用的内存空间给dentry cache slab

       return parent;

}

 

4 对哈希链表的操作

1)向哈希链表中增加一个dentry对象

函数d_rehash()实现这一功能,它首先通过d_hash()函数找到这个dentry对象应该挂到哪一个哈希链表中,然后设置d_hash指针。如下所示(dcache.c):

void d_rehash(struct dentry * entry)

{

       spin_lock(&dcache_lock);

       spin_lock(&entry->d_lock);

       _d_rehash(entry);

       spin_unlock(&entry->d_lock);

       spin_unlock(&dcache_lock);

}

 

static void _d_rehash(struct dentry * entry)

{

       __d_rehash(entry, d_hash(entry->d_parent, entry->d_name.hash));

}

static void __d_rehash(struct dentry * entry, struct hlist_head *list)

{

      entry->d_flags &= ~DCACHE_UNHASHED;

      hlist_add_head_rcu(&entry->d_hash, list);

}

 

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

}

2)从哈希链表中摘除一个dentry对象

函数d_drop()实现这一点,如下所示(dcache.h):

D_drop__d_drop主要将dentry从相应hash中摘除,VFS lookup将不会找到该dentry

/**

 * d_drop - drop a dentry

 * @dentry: dentry to drop

 *

 * d_drop() unhashes the entry from the parent dentry hashes, so that it won't

 * be found through a VFS lookup any more. Note that this is different from

 * deleting the dentry - d_delete will try to mark the dentry negative if

 * possible, giving a successful _negative_ lookup, while d_drop will

 * just make the cache lookup fail.

 *

 * d_drop() is used mainly for stuff that wants to invalidate a dentry for some

 * reason (NFS timeouts or autofs deletes).

 *

 * __d_drop requires dentry->d_lock.

 */

static inline void __d_drop(struct dentry *dentry)//0401

{

       if (!(dentry->d_flags & DCACHE_UNHASHED)) {

              dentry->d_flags |= DCACHE_UNHASHED;

              hlist_del_rcu(&dentry->d_hash);//hlist_del_rcu

       }

}

static inline void d_drop(struct dentry *dentry)//0401

{

       spin_lock(&dcache_lock);

       spin_lock(&dentry->d_lock);

      __d_drop(dentry);

       spin_unlock(&dentry->d_lock);

       spin_unlock(&dcache_lock);

}

头文件dcache.h中还定义了一个函数d_unhashed(),用来测试一个dentry对象是否没有链接在哈希链表中,如下:

static inline int d_unhashed(struct dentry *dentry)

{

       return (dentry->d_flags & DCACHE_UNHASHED);

}

 

5 LRU链表的管理与操作

LRU链表dentry_unused的管理和维护主要体现在两点上:

1)当哈希链表中的一个dentry对象从inuse状态转变为unused状态时,应该将他插入到LRU链表的首部,具体请参见dput()函数的实现。

2)当系统内存紧张时,应该释放LRU链表中的一些dentry对象,且通常是释放LRU链表尾部的dentry对象(因为它们是最近最少使用的)。但是也可以根据指定条件释放LRU中特定的dentry对象,因此在这之前要做一个挑选过程,并由这一过程将所选中的dentry对象移到dentry_unused链表的尾部。这一机制也称为dcache的压缩(shrink)机制。

下面将详细分析dcacheshrink机制实现。

 

51 prune_one_dentry()函数

该函数实现从LRU链表中释放一个指定的dentry对象。这是一个静态的内部函数,它通常被别的函数调用。NOTE! Prune_one_dentry()函数假定被调用之前,调用者已经将dentry对象从LRU链表中摘除,并且持有自旋锁dcache_lock。因此,它所要做的事情就是:①将这个dentry对象从哈希链表中摘除;②将这个dentry对象从其父目录对象的d_subdirs链表中摘除;③用 dentry_iput()函数释放对相应inode对象的引用;④用d_free()释放这个dentry对象;⑤对父目录dentry对象做一次 dput操作。

static void prune_one_dentry(struct dentry * dentry)

 

52 prune_dcache()函数

该函数用于实现从LRU链表的尾部开始倒序释放指定个数的dentry对象。它从尾部开始扫描LRU链表,如果被扫描的dentry对象设置了DCACHE_REFERENCED标志,则让其继续留在LRU链表中,否则就将其从LRU链表中摘除,然后调用prune_one_dentry()函数释放该dentry对象。

/*Shrink the dcache. This is done when we need more memory, or simply when we

  need to unmount something (at which point we need to unuse all dentries).*/

static void prune_dcache(int count){

spin_lock(&dcache_lock);

spin_lock(&sb_lock);

list_for_each_entry(sb, &super_blocks, s_list) {//针对每一个super_block

              if (sb->s_nr_dentry_unused == 0)//# of dentries on lru

                     continue;

。。。。。。

__shrink_dcache_sb(sb, &w_count,DCACHE_REFERENCED);//针对当前sb执行

。。。。。。

 

spin_unlock(&sb_lock);

spin_unlock(&dcache_lock);        

}

 

/*Shrink the dentry LRU on a given superblock.*/

static void __shrink_dcache_sb(struct super_block *sb, int *count, int flags){

。。。。。。

prune_one_dentry(dentry);/*Throw away a dentry - free the inode, dput the parent.  This requires that the LRU list has already been removed.*/

。。。。。。

}

 

上述两个函数prune_one_dentry()prune_dcache()是dcacheshrink机制的实现基础。在此基础上,Linux实现了根据指定条件压缩dcache的高层接口函数:①shink_dcache_sb()——根据指定的超级块对象,压缩dcache;②shrink_dcache_parent()——根据指定的父目录dentry对象,压缩dcache;③shrink_dcache_memory()——根据优先级压缩dcache

 

53 shrink_dcache_sb()函数

该函数释放dcacheLRU链表中属于某个特定超级块对象的dentry对象。该函数的实现过程主要是两次遍历dentry_unused链表:

①第一次遍历过程将属于指定超级块对象的dentry对象移到dentry_unused链表的首部。

②第二次遍历则将属于指定超级块对象、且d_count0dentry对象释放掉(通过prune_one_dentry函数)。

void shrink_dcache_sb(struct super_block * sb)

{

       __shrink_dcache_sb(sb, NULL, 0);//5.2

}

 

54 shrink_dcache_parent()函数

该函数释放LRU链表中属于给定父目录对象的子dentry对象。实现源码如下:

void shrink_dcache_parent(struct dentry * parent)

{

       struct super_block *sb = parent->d_sb;

       int found;

 

       while ((found = select_parent(parent)) != 0)

              __shrink_dcache_sb(sb, &found, 0);

}

可以看出,shrink_dcache_parent()函数首先通过调用 select_parent()函数来从LRU链表中查找父目录parent的子目录对象,并将这些子dentry对象移到LRU链表的尾部,并返回所找到的子dentry对象的个数(这一步是为调用prune_dcache()函数做准备的);然后,调用prune_dcache()函数将LRU链表尾部的子dentry对象释放掉。

 

函数select_parent()是在dcache.c中实现的内部函数,他根据给定的参数parent,在LRU链表中查找父目录parent的子目录对象,并将这些子dentry对象移到LRU链表的尾部,并返回所找到的子dentry对象的个数。

 

55 shringk_dcache_memory()函数

当我们需要内存,但又不知道具体需要多少时,就可以调用这个函数来压缩dcache所占用的内存。该函数通常被kswapd守护进程所调用。

优先级参数priority值越大(优先级越低),表明对内存的需要就越不迫切。因此prune_dcache()函数释放的dentry对象个数就越少。

 

6 dentry对象的VFS操作接口

VFS实现了几个对dcache中的dentry对象的操作函数,下面我们列举一些:

1. d_invalidate()——使一个dcache中的dentry对象无效。该函数的核心就是要将指定的dentry对象从哈希链表中摘除。

2. d_find_alias()——为指定inode对象找到一个位于哈希链表中的、且在该索引节点的别名链表i_dentry中的dentry对象。

3. d_prune_aliases()——释放指定inode对象的别名链表i_dentry中未使用的dentry对象。

4. have_submounts()——查看在参数parent指定的部分目录树中是否至少有一个安装点。

5. d_lookup()——在参数parent指定的父目录中查找名字为name的目录项。

6. d_validate()——验证一个dentry对象的有效性。

7. d_delete()——删除一个dentry对象。实际上是将这个dentry对象转变为negative状态或unused状态。

8. d_move()——移动一个dentry对象。

9. __d_path()——得到一个dentry对象的全路径名。

10. is_subdir()——判断一个dentry对象是否是另一个dentry对象的子孙。

11. find_inode_number()——在父目录dir中,查找是否存在参数name指定的名字的目录项,并返回对应inode的索引节点。

 

7 小结

由于dentry是一种纯软件数据结构,不存在对应的磁盘数据。因此,与icache机制和buffer cache机制不同,dcache中没有如何同步一个dentry对象的机制。

/*

 * When a file is deleted, we have two options:

 * - turn this dentry into a negative dentry

 * - unhash this dentry and free it.

 *

 * Usually, we want to just turn this into

 * a negative dentry, but if anybody else is

 * currently using the dentry or the inode

 * we can't do that and we fall back on removing

 * it from the hash queues and waiting for

 * it to be deleted later when it has no users

 */

 

/**

 * d_delete - delete a dentry

 * @dentry: The dentry to delete

 *

 * Turn the dentry into a negative dentry if possible, otherwise

 * remove it from the hash queues so it can be deleted later

 */

void d_delete(struct dentry * dentry)//0401

{

       int isdir = 0;

       /*

        * Are we the only user?

        */

       spin_lock(&dcache_lock);

       spin_lock(&dentry->d_lock);

       isdir = S_ISDIR(dentry->d_inode->i_mode);

       if (atomic_read(&dentry->d_count) == 1) {//该情况下,turn the dentry into a negtive dentry

              dentry_iput(dentry);//下面分析

              fsnotify_nameremove(dentry, isdir);

              return;

       }

 

       if (!d_unhashed(dentry))//others used the dentry or inode currently,so just unhash it and waiting for it to be deleted later when no users

              __d_drop(dentry);

       spin_unlock(&dentry->d_lock);

       spin_unlock(&dcache_lock);

 

       fsnotify_nameremove(dentry, isdir);

}

 

 

区别下面两类API,一类是struct dentry_operations中的,另一类是关于dentry cachede

Struct dentry_operations

--------------------------

This describes how a filesystem can overload the standard dentry operations. Dentries and the dcache are the domain of the VFS and the individual filesystem implementations. Device drivers have no business here. These methods may be set to NULL, as they are either optional or the VFS uses a default. As of kernel 2.6.22, the following members are defined:

struct dentry_operations {

       int (*d_revalidate)(struct dentry *, struct nameidata *);

       int (*d_hash) (struct dentry *, struct qstr *);

       int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);

       int (*d_delete)(struct dentry *);

       void (*d_release)(struct dentry *);

       void (*d_iput)(struct dentry *, struct inode *);

       char *(*d_dname)(struct dentry *, char *, int);

};

  d_revalidate: called when the VFS needs to revalidate a dentry. This is called whenever a name look-up finds a dentry in the dcache. Most filesystems leave this as NULL, because all their dentries in the dcache are valid

  d_hash: called when the VFS adds a dentry to the hash table

  d_compare: called when a dentry should be compared with another

  d_delete: called when the last reference to a dentry is deleted. This means no-one is using the dentry, however it is still valid and in the dcache

  d_release: called when a dentry is really deallocated

  d_iput: called when a dentry loses its inode (just prior to its being deallocated). The default when this is NULL is that the VFS calls iput(). If you define this method, you must call iput() yourself

  d_dname: called when the pathname of a dentry should be generated. Useful for some pseudo filesystems (sockfs, pipefs, ...) to delay pathname generation. (Instead of doing it when dentry is created,it's done only when the path is needed.). Real filesystems probably dont want to use it, because their dentries are present in global dcache hash, so their hash should be an invariant. As no lock is held, d_dname() should not try to modify the dentry itself, unless appropriate SMP safety is used. CAUTION : d_path() logic is quite tricky. The correct way to return for example "Hello" is to put it at the end of the buffer, and returns a pointer to the first char. dynamic_dname() helper function is provided to take care of this.

 

Directory Entry Cache API

--------------------------

There are a number of functions defined which permit a filesystem to manipulate dentries:

  dget: open a new handle for an existing dentry (this just increments the usage count)

  dput: close a handle for a dentry (decrements the usage count). If the usage count drops to 0, the "d_delete" method is called and the dentry is placed on the unused list if the dentry is still in its parents hash list. Putting the dentry on the unused list just means that if the system needs some RAM, it goes through the unused list of dentries and deallocates them. If the dentry has already been unhashed and the usage count drops to 0, in this case the dentry is deallocated after the

"d_delete" method is called

  d_drop: this unhashes a dentry from its parents hash list. A subsequent call to dput() will deallocate the dentry if its usage count drops to 0

  d_delete: delete a dentry. If there are no other open references to the dentry then the dentry is turned into a negative dentry (the d_iput() method is called). If there are other references, then d_drop() is called instead

  d_add: add a dentry to its parents hash list and then calls d_instantiate()

  d_instantiate: add a dentry to the alias hash list for the inode and updates the "d_inode" member. The "i_count" member in the inode structure should be set/incremented. If the inode pointer is NULL, the dentry is called a "negative dentry". This function is commonly called when an inode is created for an existing negative dentry

  d_lookup: look up a dentry given its parent and path name component It looks up the child of that given name from the dcache hash table. If it is found, the reference count is incremented and the dentry is returned. The caller must use d_put() to free the dentry when it finishes using it.

 

//Scan `nr' dentries and return the number which remain.

static int shrink_dcache_memory(int nr, gfp_t gfp_mask)---->prune_dcache(nr)

 

/**

 * prune_dcache - shrink the dcache

 * @count: number of entries to try to free

 *

 * Shrink the dcache. This is done when we need more memory, or simply when we

 * need to unmount something (at which point we need to unuse all dentries).

 *

 * This function may fail to free any resources if all the dentries are in use.

 */

static void prune_dcache(int count)

 

/**

 * shrink_dcache_sb - shrink dcache for a superblock

 * @sb: superblock

 *

 * Shrink the dcache for the specified super block. This

 * is used to free the dcache before unmounting a file

 * system

 */

void shrink_dcache_sb(struct super_block * sb)

 

/**

 * shrink_dcache_parent - prune dcache

 * @parent: parent of entries to prune

 *

 * Prune the dcache to remove unused children of the parent dentry.

 */

void shrink_dcache_parent(struct dentry * parent)

 

D_addd_add_uniquedentry加入hash表:

/**

 * d_add - add dentry to hash queues

 * @entry: dentry to add

 * @inode: The inode to attach to this dentry

 *

 * This adds the entry to the hash queues and initializes @inode.

 * The entry was actually filled in earlier during d_alloc().

 */

 

static inline void d_add(struct dentry *entry, struct inode *inode)

{

       d_instantiate(entry, inode);

       d_rehash(entry);

}

 

/**

 * d_add_unique - add dentry to hash queues without aliasing

 * @entry: dentry to add

 * @inode: The inode to attach to this dentry

 *

 * This adds the entry to the hash queues and initializes @inode.

 * The entry was actually filled in earlier during d_alloc().

 */

static inline struct dentry *d_add_unique(struct dentry *entry, struct inode *inode)

{

       struct dentry *res;

 

       res = d_instantiate_unique(entry, inode);

       d_rehash(res != NULL ? res : entry);

       return res;

}

static void dentry_lru_del_init(struct dentry *dentry)

{

       if (likely(!list_empty(&dentry->d_lru))) {

              list_del_init(&dentry->d_lru);

              dentry->d_sb->s_nr_dentry_unused--;

              dentry_stat.nr_unused--;

       }

}

 

这里最后给出一个图,表明dentry mnt 所组成的一幅全景图,并且还是一个dentry下面安装多个文件系统的例子:体会下
上面的这句话:
/*从一个dentry需找其父节点的时候,要知道mnt才能在这其中做出选择, follow_dot_dot我们能学到这个。。。*/

 

原文地址:https://www.cnblogs.com/linghuchong0605/p/3662376.html