注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4

1、背景

在之前的文章《vm内核参数之缓存回收drop_caches》中,我们知道在使用drop_caches接口释放内存时,drop_slab中会调用各个slab管理区的shrink函数释放空闲内存。在实际释放前会先计算可释放的空闲slab缓存数量,这个计算在针对超级块以及mbcache时会受vfs_cache_pressure参数控制。

2、super block shrink

文件系统在初始化时会调用alloc_super分配超级块,此时就会设置超级块的shrink函数,为prune_super

static struct super_block *alloc_super(struct file_system_type *type, int flags)
{
	struct super_block *s = kzalloc(sizeof(struct super_block_wrapper),  GFP_USER);
	...
	INIT_LIST_HEAD(&s->s_inodes); //s_inodes链表存放该超级块所有inode
	INIT_LIST_HEAD(&s->s_dentry_lru); //s_dentry_lru链表存放未使用的dentry
	INIT_LIST_HEAD(&s->s_inode_lru); //s_inode_lru链表存放未使用的inode
	...
	s->s_shrink.seeks = DEFAULT_SEEKS; // DEFAULT_SEEKS值为2
	s->s_shrink.shrink = prune_super; // 每个超级块对应的shrink函数
	s->s_shrink.batch = 1024; //批处理数量
	return s;
fail:
	destroy_super(s);
	return NULL;
}

3、prune_super

每个超级块都会注册一个shrink函数,当内存不足时用于回收内存,主要就是回收未使用的dentry和inode缓存,

static int prune_super(struct shrinker *shrink, struct shrink_control *sc)
{
	struct super_block *sb;
	int	fs_objects = 0;
	int	total_objects;

	sb = container_of(shrink, struct super_block, s_shrink);

	/*
	 * Deadlock avoidance.  We may hold various FS locks, and we don't want
	 * to recurse into the FS that called us in clear_inode() and friends..
	 */
	if (sc->nr_to_scan && !(sc->gfp_mask & __GFP_FS))
		return -1;

	if (sb->s_op && sb->s_op->nr_cached_objects)
		fs_objects = sb->s_op->nr_cached_objects(sb);

	total_objects = sb->s_nr_dentry_unused +
			sb->s_nr_inodes_unused + fs_objects + 1;

    //batch size
	if (sc->nr_to_scan) {
		int	dentries;
		int	inodes;

		/* proportion the scan between the caches */
        //按照比例分配dentry和inode的扫描额度
		dentries = (sc->nr_to_scan * sb->s_nr_dentry_unused) /
							total_objects;
		inodes = (sc->nr_to_scan * sb->s_nr_inodes_unused) /
							total_objects;
		if (fs_objects)
			fs_objects = (sc->nr_to_scan * fs_objects) /
							total_objects;
		/*
		 * prune the dcache first as the icache is pinned by it, then
		 * prune the icache, followed by the filesystem specific caches
		 */
        //先回收dentry缓存,扫描该超级块上的s_dentry_lru链表
		prune_dcache_sb(sb, dentries);
        //再回收inode缓存,扫描该超级块上的s_inode_lru链表
		prune_icache_sb(sb, inodes);

		if (fs_objects && sb->s_op->free_cached_objects) {
			sb->s_op->free_cached_objects(sb, fs_objects);
			fs_objects = sb->s_op->nr_cached_objects(sb);
		}
        //再次计算此时系统中可回收的slab缓存数量
		total_objects = sb->s_nr_dentry_unused +
				sb->s_nr_inodes_unused + fs_objects;
	}
    //通过vfs_cache_pressure控制返回的空闲缓存数量,默认值100,即系统存在多少就返回多少
    //增加该值就会让shrink函数扫描和释放更多的缓存,相反,减少该值时,就不会回收那么多内存
	total_objects = (total_objects / 100) * sysctl_vfs_cache_pressure;
	return total_objects;
}

由上可以,vfs_cache_pressure通过控制缓存回收时扫描和回收的数量来达到释放缓存的多少,系统默认值是100,也就是默认释放当前系统所有缓存,当值大于100时,也就是要回收多余当前系统缓存量,这样就需要持续一段时间,一边等待系统生成缓存,一边再回收,直到达到指定值。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐