diff --git a/fs/dcache.c b/fs/dcache.c index 4046904..c8d6f08 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -308,10 +308,17 @@ static void dentry_unlink_inode(struct dentry * dentry) */ static void dentry_lru_add(struct dentry *dentry) { + if (list_empty(&dentry->d_lru)) { + struct mem_cgroup *memcg; + memcg = memcg_from_object(dentry); spin_lock(&dcache_lru_lock); - list_add(&dentry->d_lru, &dentry->d_sb->s_dentry_lru); - dentry->d_sb->s_nr_dentry_unused++; + if (!memcg) { + list_add(&dentry->d_lru, &dentry->d_sb->s_dentry_lru); + dentry->d_sb->s_nr_dentry_unused++; + } else { + memcg_add_dentry_lru(memcg, dentry); + } dentry_stat.nr_unused++; spin_unlock(&dcache_lru_lock); } @@ -319,9 +326,18 @@ static void dentry_lru_add(struct dentry *dentry) static void __dentry_lru_del(struct dentry *dentry) { + + struct mem_cgroup *memcg; + memcg = memcg_from_object(dentry); + list_del_init(&dentry->d_lru); dentry->d_flags &= ~DCACHE_SHRINK_LIST; - dentry->d_sb->s_nr_dentry_unused--; + + if (!memcg) + dentry->d_sb->s_nr_dentry_unused--; + else + memcg_del_dentry_lru(memcg, dentry); + dentry_stat.nr_unused--; } @@ -847,19 +863,7 @@ static void shrink_dentry_list(struct list_head *list) rcu_read_unlock(); } -/** - * prune_dcache_sb - shrink the dcache - * @sb: superblock - * @count: number of entries to try to free - * - * Attempt to shrink the superblock dcache LRU by @count entries. This is - * done when we need more memory an called from the superblock shrinker - * function. - * - * This function may fail to free any resources if all the dentries are in - * use. - */ -void prune_dcache_sb(struct super_block *sb, int count) +void prune_dcache_list(struct list_head *dentry_list, int count) { struct dentry *dentry; LIST_HEAD(referenced); @@ -867,10 +871,9 @@ void prune_dcache_sb(struct super_block *sb, int count) relock: spin_lock(&dcache_lru_lock); - while (!list_empty(&sb->s_dentry_lru)) { - dentry = list_entry(sb->s_dentry_lru.prev, + while (!list_empty(dentry_list)) { + dentry = list_entry(dentry_list->prev, struct dentry, d_lru); - BUG_ON(dentry->d_sb != sb); if (!spin_trylock(&dentry->d_lock)) { spin_unlock(&dcache_lru_lock); @@ -892,18 +895,37 @@ relock: cond_resched_lock(&dcache_lru_lock); } if (!list_empty(&referenced)) - list_splice(&referenced, &sb->s_dentry_lru); + list_splice(&referenced, dentry_list); spin_unlock(&dcache_lru_lock); shrink_dentry_list(&tmp); } /** + * prune_dcache_sb - shrink the dcache + * @sb: superblock + * @count: number of entries to try to free + * + * Attempt to shrink the superblock dcache LRU by @count entries. This is + * done when we need more memory an called from the superblock shrinker + * function. + * + * This function may fail to free any resources if all the dentries are in + * use. + */ +void prune_dcache_sb(struct super_block *sb, int count) +{ + prune_dcache_list(&sb->s_dentry_lru, 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. + * + * FIXME: This may be a problem if the lists are separate, because we need to get to all sb objects */ void shrink_dcache_sb(struct super_block *sb) { diff --git a/fs/super.c b/fs/super.c index 5af6817..0180cc0 100644 --- a/fs/super.c +++ b/fs/super.c @@ -52,6 +52,9 @@ static int prune_super(struct shrinker *shrink, struct shrink_control *sc) int fs_objects = 0; int total_objects; + if (sc->memcg) + return -1; + sb = container_of(shrink, struct super_block, s_shrink); /* diff --git a/include/linux/fs.h b/include/linux/fs.h index 6a1f97f..d4d3eb9 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1555,6 +1555,8 @@ struct super_block { /* superblock cache pruning functions */ extern void prune_icache_sb(struct super_block *sb, int nr_to_scan); extern void prune_dcache_sb(struct super_block *sb, int nr_to_scan); +extern void prune_dcache_list(struct list_head *dentry_list, int nr_to_scan); +extern void prune_icache_list(struct list_head *inode_list, int nr_to_scan); extern struct timespec current_fs_time(struct super_block *sb); diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index a3e462a..90b587d 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -567,5 +567,7 @@ memcg_kmem_get_cache(struct kmem_cache *cachep, gfp_t gfp) return __memcg_kmem_get_cache(cachep, gfp); } +extern void memcg_add_dentry_lru(struct mem_cgroup *memcg, struct dentry *dentry); +extern void memcg_del_dentry_lru(struct mem_cgroup *memcg, struct dentry *dentry); #endif /* _LINUX_MEMCONTROL_H */ diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h index ac6b8ee..a829570 100644 --- a/include/linux/shrinker.h +++ b/include/linux/shrinker.h @@ -10,6 +10,7 @@ struct shrink_control { /* How many slab objects shrinker() should scan and try to reclaim */ unsigned long nr_to_scan; + struct mem_cgroup *memcg; }; /* diff --git a/include/linux/slab.h b/include/linux/slab.h index 765e12c..0d833fe 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -351,6 +351,18 @@ extern void *__kmalloc_track_caller(size_t, gfp_t, unsigned long); #ifdef CONFIG_MEMCG_KMEM #define MAX_KMEM_CACHE_TYPES 400 +extern struct kmem_cache *virt_to_cache(const void *x); + +static inline struct mem_cgroup *memcg_from_object(const void *x) +{ + struct kmem_cache *s = virt_to_cache(x); + return s->memcg_params.memcg; +} +#else +static inline struct kmem_cache *memcg_from_object(const void *x) +{ + return NULL; +} #endif #ifdef CONFIG_NUMA diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 26834d1..4dac864 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -347,6 +347,9 @@ struct mem_cgroup { #ifdef CONFIG_MEMCG_KMEM /* Slab accounting */ struct kmem_cache *slabs[MAX_KMEM_CACHE_TYPES]; + unsigned long nr_dentry_unused; + struct list_head dentry_lru_list; + struct shrinker vfs_shrink; #endif }; @@ -413,6 +416,50 @@ int memcg_css_id(struct mem_cgroup *memcg) { return css_id(&memcg->css); } + +void memcg_add_dentry_lru(struct mem_cgroup *memcg, struct dentry *dentry) +{ + list_add(&dentry->d_lru, &memcg->dentry_lru_list); + memcg->nr_dentry_unused++; +} + +void memcg_del_dentry_lru(struct mem_cgroup *memcg, struct dentry *dentry) +{ + memcg->nr_dentry_unused--; +} + +static int vfs_shrink(struct shrinker *shrink, struct shrink_control *sc) +{ + struct mem_cgroup *memcg; + + memcg = container_of(shrink, struct mem_cgroup, vfs_shrink); + if (memcg != sc->memcg) + return -1; + + printk("Called vfs_shrink, memcg %p, nr_to_scan %lu\n", memcg, sc->nr_to_scan); + printk("Unused dentries: %lu\n", memcg->nr_dentry_unused); + + if (sc->nr_to_scan && !(sc->gfp_mask & __GFP_FS)) { + printk("out\n"); + return -1; + } + + if (sc->nr_to_scan) { + prune_dcache_list(&memcg->dentry_lru_list, sc->nr_to_scan); + printk("Remaining Unused dentries: %lu\n", memcg->nr_dentry_unused); + } + return memcg->nr_dentry_unused; +} +#else +void memcg_add_dentry_lru(struct mem_cgroup *memcg, struct dentry *dentry) +{ + BUG(); +} + +void memcg_del_dentry_lru(struct mem_cgroup *memcg, struct dentry *dentry) +{ + BUG(); +} #endif /* CONFIG_MEMCG_KMEM */ /* Stuffs for move charges at task migration. */ @@ -4631,6 +4678,14 @@ static void memcg_update_kmem_limit(struct mem_cgroup *memcg, u64 val) mutex_lock(&set_limit_mutex); if ((val != RESOURCE_MAX) && memcg_kmem_account(memcg)) { + INIT_LIST_HEAD(&memcg->dentry_lru_list); + memcg->vfs_shrink.seeks = DEFAULT_SEEKS; + memcg->vfs_shrink.shrink = vfs_shrink; + memcg->vfs_shrink.batch = 1024; + + register_shrinker(&memcg->vfs_shrink); + + /* * Once enabled, can't be disabled. We could in theory disable * it if we haven't yet created any caches, or if we can shrink @@ -5605,6 +5660,7 @@ static void free_work(struct work_struct *work) * the cgroup_lock. */ disarm_static_keys(memcg); + unregister_shrinker(&memcg->vfs_shrink); if (size < PAGE_SIZE) kfree(memcg); else diff --git a/mm/slab.c b/mm/slab.c index e4de1fa..e736e01 100644 --- a/mm/slab.c +++ b/mm/slab.c @@ -522,7 +522,7 @@ static inline struct kmem_cache *page_get_cache(struct page *page) return page->slab_cache; } -static inline struct kmem_cache *virt_to_cache(const void *obj) +struct kmem_cache *virt_to_cache(const void *obj) { struct page *page = virt_to_head_page(obj); return page->slab_cache; diff --git a/mm/slub.c b/mm/slub.c index 4e1f470..33c9a6d 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -2623,6 +2623,12 @@ void kmem_cache_free(struct kmem_cache *s, void *x) } EXPORT_SYMBOL(kmem_cache_free); +struct kmem_cache *virt_to_cache(const void *obj) +{ + struct page *page = virt_to_head_page(obj); + return page->slab; +} + /* * Object placement in a slab is made very easy because we always start at * offset 0. If we tune the size of the object to the alignment then we can