最近追查某线上服务发现一个问题:频繁的创建&删除目录或者文件,会消耗大量的物理内存。
原因是因为,目录或者文件删除后,dentry并没有立即释放,而是保存在了superblock上的lru链表上。通常只有内核认为内存紧张的时候才会回收此部分内存。
不过由于dentry结构非常小,这种case下内存增长是很慢。
// linux-2.6.32.68/fs/dcache.c
/*
* 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)
{
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))
goto unhash_it;
}
/* Unreachable? Get rid of it */
if (d_unhashed(dentry))
goto kill_it;
if (list_empty(&dentry->d_lru)) {
dentry->d_flags |= DCACHE_REFERENCED;
// 这里把dentry放到sb的lru链表里
dentry_lru_add(dentry);
}
spin_unlock(&dentry->d_lock);
spin_unlock(&dcache_lock);
return;
unhash_it:
__d_drop(dentry);
kill_it:
/* if dentry was on the d_lru list delete it from there */
dentry_lru_del(dentry);
dentry = d_kill(dentry);
if (dentry)
goto repeat;
}
而内存回收有两种方式:
- 内核发现内存不足时主动处罚。例如分配不了内存时,达到某种指定的阈值时等等。
- 手动触发 sysctl vm.drop_caches。
这里只讨论第二种方法。内核在初始化的时候,注册了一个dcache_shrinker来释放dcache内存。shrink_dcache_memory的最主要的逻辑时prune_dcache()函数
static struct shrinker dcache_shrinker = {
.shrink = shrink_dcache_memory,
.seeks = DEFAULT_SEEKS,
};
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);
// 注册钩子函数,当用户手动触发内存回收时,调用dcache_shrinker
register_shrinker(&dcache_shrinker);
/* ... */
}