分享
 
 
 

Linux(2.4.0)目录项缓存dcache机制的实现分析

王朝system·作者佚名  2008-05-18
窄屏简体版  字體: |||超大  

蓝森林 http://www.lslnet.com 2002年11月8日 16:30

作 者: 詹荣开

(詹荣开 zhanrk@sohu.com)

Linux用数据结构dentry来描述fs中与某个文件索引节点相链接的一个目录项(可以是文件,也可以是目录)。

每个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()方法就会被调用。

1 目录项对象的SLAB分配器缓存dentry_cache

dcache是建立在dentry对象的slab分配器缓存dentry_cache(按照Linux的命名规则,似乎应该是 dentry_cachep,^_^)之上的。因此,目录项对象的创建和销毁都应该通过kmem_cache_alloc()函数和 kmem_cache_free()函数来进行。

dentry_cache是一个kmem_cache_t类型的指针。它定义在dcache.c文件中:

static kmem_cache_t *dentry_cache;

这个slab分配器缓存是在dcache机制的初始化例程dcache_init()中通过调用函数kmem_cache_create()来创建的。

1.1 分配接口

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

其中,d_alloc()的实现如下:

#define NAME_ALLOC_LEN(len) ((len+16) & ~15)

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

{

char * str;

struct dentry *dentry;

dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);

if (!dentry)

return NULL;

if (name->len > DNAME_INLINE_LEN-1) {

str = kmalloc(NAME_ALLOC_LEN(name->len), GFP_KERNEL);

if (!str) {

kmem_cache_free(dentry_cache, dentry);

return NULL;

}

} else

str = dentry->d_iname;

memcpy(str, name->name, name->len);

str[name->len] = 0;

atomic_set(&dentry->d_count, 1);

dentry->d_flags = 0;

dentry->d_inode = NULL;

dentry->d_parent = NULL;

dentry->d_sb = NULL;

dentry->d_name.name = str;

dentry->d_name.len = name->len;

dentry->d_name.hash = name->hash;

dentry->d_op = NULL;

dentry->d_fsdata = NULL;

INIT_LIST_HEAD(&dentry->d_vfsmnt);

INIT_LIST_HEAD(&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;

spin_lock(&dcache_lock);

list_add(&dentry->d_child, &parent->d_subdirs);

spin_unlock(&dcache_lock);

} else

INIT_LIST_HEAD(&dentry->d_child);

dentry_stat.nr_dentry++;

return dentry;

}

NOTE:

(1)如果文件名的长度大于15,则调用kmalloc()函数从slab分配器中为文件名分配内存;否则将文件名拷贝到d_iname数组中,并让b_name.name指针指向d_iname。

(2)引用计数d_count将被初始化为1,其余成员都被初始化为NULL。

(3)如果父目录的dentry被给定,则设置d_parent指针指向父目录的dentry对象(因此必须通过dget函数来增加父目录dentry对象的引用计数)。并通过d_child指针域将这个dentry对象链入父目录dentry对象的d_subdirs链表。否则,将d_child初始化为指向自身。

(4)将dcache统计信息中的dentry对象总数nr_dentry值加1。

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

struct dentry * d_alloc_root(struct inode * root_inode)

{

struct dentry *res = NULL;

if (root_inode) {

res = d_alloc(NULL, &(const struct qstr) { "/", 1, 0 });

if (res) {

res->d_sb = root_inode->i_sb;

res->d_parent = res;

d_instantiate(res, root_inode);

}

}

return res;

}

(1)可以看出,首先还是必须调用d_alloc()函数来从dentry_cache slab分配器缓存中分配一个dentry对象。注意!特别之处在于d_alloc()函数的调用参数。

(2)然后,将所分配的dentry对象的d_parent指针设置为指向自身。NOTE!这一点是判断一个dentry对象是否是一个fs的根目录的唯一准则(include/linux/dcache.h):

#define IS_ROOT(x) ((x)==(x)->d_parent)

(3)最后,通过调用d_instantiate()函数来实例化这个dentry对象。

函数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的引用计数。

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)。其源码如下:

/* no dcache_lock, please */

static inline void d_free(struct dentry *dentry)

{

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

dentry->d_op->d_release(dentry);

if (dname_external(dentry))

kfree(dentry->d_name.name);

kmem_cache_free(dentry_cache, dentry);

dentry_stat.nr_dentry--;

}

其中,dname_external()是定义在dcache.h头文件中的内联函数,如下:

static __inline__ int dname_external(struct dentry *d)

{

return d->d_name.name != d->d_iname;

}

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

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

static inline void dentry_iput(struct dentry * dentry)

{

struct inode *inode = dentry->d_inode;

if (inode) {

dentry->d_inode = NULL;

list_del_init(&dentry->d_alias);

spin_unlock(&dcache_lock);

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

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

else

iput(inode);

} else

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

dcache中的每个dentry对象都通过其中的d_hash指针域链入哈希链表dentry_hashtable,以便加快对dentry对象的查找速度。哈希链表dentry_hashtable定义在dcache.c文件中,如下:

#define D_HASHBITS d_hash_shift

#define D_HASHMASK d_hash_mask

static unsigned int d_hash_mask;

static unsigned int d_hash_shift;

static struct list_head *dentry_hashtable;

指针dentry_hashtable定义了dentry哈希链表表头数组的首地址,而d_hash_mask和d_hash_shift的含义与icache中的inode哈希链表的i_hash_mask和i_hash_shift的含义相同,这里就不再解释。

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

static inline struct list_head * d_hash(struct dentry * parent, unsigned long hash)

{

hash += (unsigned long) parent / L1_CACHE_BYTES;

hash = hash ^ (hash >> D_HASHBITS) ^ (hash >> D_HASHBITS*2);

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

}

可以看出,该函数又向下调用partial_name_hash()函数和end_name_hash()函数来完成哈希值的计算工作。

n dcache的初始化

函数dcache_init()完成整个dcache机制的初始化工作,它主要做两件事:(1)哈希链表表头数组的初始化;(2)slab分配器缓存 dentry_cache的创建。该函数的实现思想与icache的初始化函数inode_init()相似,这里就不再详细分析了。如下所示:

static void __init dcache_init(unsigned long mempages)

{

struct list_head *d;

unsigned long order;

unsigned int nr_hash;

int i;

/*

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

* If fragmentation is too bad then the SLAB_HWCACHE_ALIGN

* flag could be removed here, to hint to the allocator that

* it should not try to get multiple page regions.

*/

dentry_cache = kmem_cache_create("dentry_cache",

sizeof(struct dentry),

0,

SLAB_HWCACHE_ALIGN,

NULL, NULL);

if (!dentry_cache)

panic("Cannot create dentry cache");

#if PAGE_SHIFT < 13

mempages >>= (13 - PAGE_SHIFT);

#endif

mempages *= sizeof(struct list_head);

for (order = 0; ((1UL << order) << PAGE_SHIFT) < mempages; order++)

;

do {

unsigned long tmp;

nr_hash = (1UL << order) * PAGE_SIZE /

sizeof(struct list_head);

d_hash_mask = (nr_hash - 1);

tmp = nr_hash;

d_hash_shift = 0;

while ((tmp >>= 1UL) != 0UL)

d_hash_shift++;

dentry_hashtable = (struct list_head *)

__get_free_pages(GFP_ATOMIC, order);

} while (dentry_hashtable == NULL && --order >= 0);

printk("Dentry-cache hash table entries: %d (order: %ld, %ld bytes)\\n",

nr_hash, order, (PAGE_SIZE << order));

if (!dentry_hashtable)

panic("Failed to allocate dcache hash table\\n");

d = dentry_hashtable;

i = nr_hash;

do {

INIT_LIST_HEAD(d);

d++;

i--;

} while (i);

}

2.2 dentry对象的LRU链表

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

static LIST_HEAD(dentry_unused);

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

另一方面,由于dentry_unused链表中的目录项对象是未使用的,因此当内存紧张时,应该将其中一些很长时间内未被使用的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 {

int nr_dentry; /* dcache中的dentry对象总数 */

int nr_unused; /* dcache中未使用的dentry对象个数 */

int age_limit; /* age in seconds */

int want_pages; /* pages requested by system */

int dummy[2];

} dentry_stat = {0, 0, 45, 0,};

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

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

BUG();

atomic_inc(&dentry->d_count);

}

return dentry;

}

从上述实现可以看出,对于引用那些d_count=0的dentry对象,我们决不应该使用dget()函数,而是应该使用dget_locked()函数。这是因为:引用一个d_count=0的dentry对象,将使该dentry对象从unused状态转变为inuse状态,该dentry状态也必须从LRU链表中脱离,而在操作dcache链表时是必须先持有自旋锁dcache_lock的。函数dget()并不对调用者由任何调用假设,相反, dget_locked()函数则假定调用者在调用它之前已经持有自旋锁dentry_lock。

函数dget_locked()定义在dcache.c中:

struct dentry * dget_locked(struct dentry *dentry)

{

return __dget_locked(dentry);

}

可以看出,内部函数__dget_locked()完成实际的工作:

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

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

{

atomic_inc(&dentry->d_count);

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

dentry_stat.nr_unused--;

list_del(&dentry->d_lru);

INIT_LIST_HEAD(&dentry->d_lru); /* make "list_empty()" work */

}

return dentry;

}

3.2 释放接口dput

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

void dput(struct dentry *dentry)

{

if (!dentry)

return;

repeat:

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

return;

/* dput on a free dentry? */

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

BUG();

/*

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

goto unhash_it;

}

/* Unreachable? Get rid of it */

if (list_empty(&dentry->d_hash))

goto kill_it;

list_add(&dentry->d_lru, &dentry_unused);

dentry_stat.nr_unused++;

/*

* Update the timestamp

*/

dentry->d_reftime = jiffies;

spin_unlock(&dcache_lock);

return;

unhash_it:

list_del_init(&dentry->d_hash);

kill_it: {

struct dentry *parent;

list_del(&dentry->d_child);

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

dentry_iput(dentry);

parent = dentry->d_parent;

d_free(dentry);

if (dentry == parent)

return;

dentry = parent;

goto repeat;

}

}

NOTE:

(1)首先判断是否对LRU链表中的一个dentry对象进行dput操作,如果是则报错。

(2)如果定义了dentry操作方法d_delete(),则应该按照d_delete()方法的功能描述首先调用它来删除这个dentry独享。如果 d_delete()返回非0值(执行出错),则跳转到unhash_it部分,将这个dentry对象从哈希链表中摘除,并将他释放回slab分配器缓存。

(3)判断这个dentry对象是否链在哈希链表中,如果没有,则跳转到kill_it部分,将这个dentry对象释放掉。

(4)如果这个dentry对象是链在哈希链表中的,则将它移到dentry_unused链表的首部,并将统计信息的nr_unused域加1,最后更新这个dentry对象的最近一次引用时间戳d_reftime,然后就直接返回。

(5)unhash_it部分:将这个dentry对象从哈希链表中摘除。

(6)kill_it部分:这一部分将dentry对象释放回给dentry_cache slab分配器缓存,其过程如下:

n 首先,将dentry对象从它的父目录dentry的d_subdirs链表中摘除。

n 然后,调用dentry_iput()函数释放其对相应inode对象的引用。

n 保存父目录dentry对象的指针,然后调用d_free()函数将这个dentry对象释放回dentry_cache slab分配器缓存。

n 如果这个dentry对象的父目录指针指向自身,这说明这个dentry对象就是fs的根目录,因此就可以返回了。否则,就要对父目录dentry对象做一次dput()操作。

4 对哈希链表的操作

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

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

void d_rehash(struct dentry * entry)

{

struct list_head *list = d_hash(entry->d_parent, entry->d_name.hash);

spin_lock(&dcache_lock);

list_add(&entry->d_hash, list);

spin_unlock(&dcache_lock);

}

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

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

static __inline__ void d_drop(struct dentry * dentry)

{

spin_lock(&dcache_lock);

list_del(&dentry->d_hash);

INIT_LIST_HEAD(&dentry->d_hash);

spin_unlock(&dcache_lock);

}

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

static __inline__ int d_unhashed(struct dentry *dentry)

{

return list_empty(&dentry->d_hash);

}

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操作。

该函数的源码如下:

void prune_dcache(int count)

{

spin_lock(&dcache_lock);

for (;;) {

struct dentry *dentry;

struct list_head *tmp;

tmp = dentry_unused.prev;

if (tmp == &dentry_unused)

break;

list_del_init(tmp);

dentry = list_entry(tmp, struct dentry, d_lru);

/* If the dentry was recently referenced, don't free it. */

if (dentry->d_flags & DCACHE_REFERENCED) {

dentry->d_flags &= ~DCACHE_REFERENCED;

list_add(&dentry->d_lru, &dentry_unused);

count--;

continue;

}

dentry_stat.nr_unused--;

/* Unused dentry with a count? */

if (atomic_read(&dentry->d_count))

BUG();

prune_one_dentry(dentry);

if (!--count)

break;

}

spin_unlock(&dcache_lock);

}

5.2 prune_dcache()函数

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

void prune_dcache(int count)

{

spin_lock(&dcache_lock);

for (;;) {

struct dentry *dentry;

struct list_head *tmp;

tmp = dentry_unused.prev;

if (tmp == &dentry_unused)

break;

list_del_init(tmp);

dentry = list_entry(tmp, struct dentry, d_lru);

/* If the dentry was recently referenced, don't free it. */

if (dentry->d_flags & DCACHE_REFERENCED) {

dentry->d_flags &= ~DCACHE_REFERENCED;

list_add(&dentry->d_lru, &dentry_unused);

count--;

continue;

}

dentry_stat.nr_unused--;

/* Unused dentry with a count? */

if (atomic_read(&dentry->d_count))

BUG();

prune_one_dentry(dentry);

if (!--count)

break;

}

spin_unlock(&dcache_lock);

}

上述两个函数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函数)。个人认为,判断条件d_count=0是不必要的,当然偏执一点也没什么不好:)

函数源码如下:

void shrink_dcache_sb(struct super_block * sb)

{

struct list_head *tmp, *next;

struct dentry *dentry;

/*

* Pass one ... move the dentries for the specified

* superblock to the most recent end of the unused list.

*/

spin_lock(&dcache_lock);

next = dentry_unused.next;

while (next != &dentry_unused) {

tmp = next;

next = tmp->next;

dentry = list_entry(tmp, struct dentry, d_lru);

if (dentry->d_sb != sb)

continue;

list_del(tmp);

list_add(tmp, &dentry_unused);

}

/*

* Pass two ... free the dentries for this superblock.

*/

repeat:

next = dentry_unused.next;

while (next != &dentry_unused) {

tmp = next;

next = tmp->next;

dentry = list_entry(tmp, struct dentry, d_lru);

if (dentry->d_sb != sb)

continue;

if (atomic_read(&dentry->d_count))

continue;

dentry_stat.nr_unused--;

list_del(tmp);

INIT_LIST_HEAD(tmp);

prune_one_dentry(dentry);

goto repeat;

}

spin_unlock(&dcache_lock);

}

5.4 shrink_dcache_parent()函数

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

void shrink_dcache_parent(struct dentry * parent)

{

int found;

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

prune_dcache(found);

}

可以看出,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对象的个数。源代码如下:

static int select_parent(struct dentry * parent)

{

struct dentry *this_parent = parent;

struct list_head *next;

int found = 0;

spin_lock(&dcache_lock);

repeat:

next = this_parent->d_subdirs.next;

resume:

while (next != &this_parent->d_subdirs) {

struct list_head *tmp = next;

struct dentry *dentry = list_entry(tmp, struct dentry, d_child);

next = tmp->next;

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

list_del(&dentry->d_lru);

list_add(&dentry->d_lru, dentry_unused.prev);

found++;

}

/*

* Descend a level if the d_subdirs list is non-empty.

*/

if (!list_empty(&dentry->d_subdirs)) {

this_parent = dentry;

#ifdef DCACHE_DEBUG

printk(KERN_DEBUG "select_parent: descending to %s/%s, found=%d\\n",

dentry->d_parent->d_name.name, dentry->d_name.name, found);

#endif

goto repeat;

}

}

/*

* All done at this level ... ascend and resume the search.

*/

if (this_parent != parent) {

next = this_parent->d_child.next;

this_parent = this_parent->d_parent;

#ifdef DCACHE_DEBUG

printk(KERN_DEBUG "select_parent: ascending to %s/%s, found=%d\\n",

this_parent->d_parent->d_name.name, this_parent->d_name.name, found);

#endif

goto resume;

}

spin_unlock(&dcache_lock);

return found;

}

这是一个算法比较复杂的函数,对他的NOTE如下:

(1)由于所有在以parent为根的部分目录树中的目录项都是parent目录的子目录项(直接或间接),而且dentry对象的父、子关系是级联的,因此函数select_parent()必须搜索出LRU链表中所有parent目录的直接或间接子目录项。

为此,函数维护一个指针this_parent,表示当前正在对部分目录树(以parent为根)中的那一个中间目录(非叶节点)进行搜索。初始时,this_parent指针指向parent,因此整个搜索过程是从上到下、从左到右的一个树型结构遍历过程。

(2)while循环对当前父目录this_parent的子目录项链表d_subdirs进行搜索。如果链表中dentry对象的d_count=0,则将该dentry对象移到LRU链表的表尾,并将返回值found加1。然后判断这个dentry对象的d_subdirs链表是否为空(也即是否为目录树的中间节点),如果不为空,则让this_parent指针指向这个dentry对象,并跳转回repeat部分,从而对这个dentry对象的子目录项链表开始进行搜索。

(3)如果当前while循环中测试的每一个dentry对象都没有d_subdirs链表,也即this_parent目录的子目录项全部为目录树的叶节点,则在完成对这一层次的搜索后,退出while循环。函数接下来考虑上升搜索层次(直到this_parent=parent),于是它将next指针修改为this_parent->d_child.next(当前父目录的兄弟),然后将this_parent指针修改为 this_parent->d_parent,然后跳转到resume部分,于是搜索过程就从上一层次正确地继续。

(4)is_parent=parent的情况下退出while循环则宣告整个搜索过程结束。

5.5 shringk_dcache_memory()函数

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

void shrink_dcache_memory(int priority, unsigned int gfp_mask)

{

int count = 0;

/*

* Nasty deadlock avoidance.

*

* ext2_new_block->getblk->GFP->shrink_dcache_memory->prune_dcache->

* prune_one_dentry->dput->dentry_iput->iput->inode->i_sb->s_op->

* put_inode->ext2_discard_prealloc->ext2_free_blocks->lock_super->

* DEADLOCK.

*

* We should make sure we don't hold the superblock lock over

* block allocations, but for now:

*/

if (!(gfp_mask & __GFP_IO))

return;

if (priority)

count = dentry_stat.nr_unused / priority;

prune_dcache(count);

kmem_cache_shrink(dentry_cache);

}

优先级参数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对象的机制。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有