linux 内核 目录项高速缓存 dentry cache 简介

本文深入探讨Linux文件系统中的Dentry对象及其状态,包括未使用、正在使用和负状态,以及Dcache(目录项高速缓存)的结构与工作原理。详细介绍了Dentry的分配与释放接口,如d_alloc、d_free等,以及Dcache中的关键数据结构,如哈希链表和LRU链表。此外,还分析了Dentry的VFS操作接口和Dcache的压缩机制。
摘要由CSDN通过智能技术生成

每个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_hashtable:dcache中的所有dentry对象都通过d_hash指针域链到相应的dentry哈希链表中。

2. 未使用的dentry对象链表dentry_unused:dcache中所有处于“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 */
};

 

1.1 分配接口

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

Dentry本身是一个树形结构,d_alloc和d_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;

            /*
               将所分配的dentry对象的d_parent指针设置为指向自身。
               NOTE!这一点是判断一个dentry对象是否是一个fs的根目录的唯一准则
               (include/linux/dcache.h):#define IS_ROOT(x)((x)==(x)->d_parent)
             */
            res->d_parent = res;
            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);
}

 

1.2 释放接口

目录项缓存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对象。

(2)dentry_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中。
 

2.1 哈希链表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);
}

 

2.2 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对象释放掉,以缓解系统的压力。

 

2.3 dcache链表的保护锁

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

spinlock_t dcache_lock = SPIN_LOCK_UNLOCKED;

 

2.4 dcache统计信息

Linux在dcache.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访问接口——dget/dput函数

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

 

3.1 引用接口

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

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

 

3.2 释放接口dput

函数dput()用于释放对一个dentry对象的引用。该函数的核心就是将 dentry对象的引用计数d_count减1。如果d_count减1后还不为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);//从parent的children 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)机制。

下面将详细分析dcache的shrink机制实现。
 

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

 

5.2 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()是dcache的shrink机制的实现基础。在此基础上,Linux实现了根据指定条件压缩dcache的高层接口函数:

①shink_dcache_sb()——根据指定的超级块对象,压缩dcache;

②shrink_dcache_parent()——根据指定的父目录dentry对象,压缩dcache;

③shrink_dcache_memory()——根据优先级压缩dcache。
 

5.3 shrink_dcache_sb()函数

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

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

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

void shrink_dcache_sb(struct super_block * sb)
{
    __shrink_dcache_sb(sb, NULL, 0);//见5.2
}

 

5.4 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对象的个数。

 

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

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

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值