memcg: release kmemcg_id on css offline
Subsystems (9)
cover memcg: release kmemcg_id on css offline
Hi,
There's one thing about kmemcg implementation that's bothering me. It's about arrays holding per-memcg data (e.g. kmem_cache->memcg_params-> memcg_caches). On kmalloc or list_lru_{add,del} we want to quickly lookup the copy of kmem_cache or list_lru corresponding to the current cgroup. Currently, we hold all per-memcg caches/lists in an array indexed by mem_cgroup->kmemcg_id. This allows us to lookup quickly, and that's nice, but the arrays can grow indefinitely, because we reserve slots for all cgroups, including offlined, and this is disastrous and must be fixed.
There are several ways to fix this issue [1], but it seems the best we can do is to free kmemcg_id on css offline (solution #2). The idea is that we actually only need kmemcg_id on kmem cache allocations, in order to lookup the kmem cache copy corresponding to the allocating memory cgroup, while it is never used of kmem frees. We do rely on kmemcg_id being unique for each cgroup in some places, but they are easy to fix. And regarding per memcg list_lru, which are indexed by kmemcg_id, we can easily reparent them - it is as simple as splicing lists. This patch set therefore implements this approach.
It is organized as follows:
- Currently, sl[AU]b core relies on kmemcg_id being unique per kmem_cache. Patches 1-3 fix that.
- Patch 4 makes memcg_cache_params->memcg_caches entries released on css offline.
- Patch 5 alters the list_lru API a bit to facilitate reparenting.
- Patch 6 implements per memcg list_lru reparenting on css offline. After list_lrus have been reparented, there's no need to keep kmemcg_id any more, so we can free it on css offline.
[1] https://lkml.org/lkml/2015/1/13/107
Thanks,
Vladimir Davydov (6):
slab: embed memcg_cache_params to kmem_cache
slab: link memcg caches of the same kind into a list
slab: use css id for naming per memcg caches
memcg: free memcg_caches slot on css offline
list_lru: add helpers to isolate items
memcg: reparent list_lrus and free kmemcg_id on css offline
fs/dcache.c | 21 +++--- fs/gfs2/quota.c | 5 +- fs/inode.c | 8 +-- fs/xfs/xfs_buf.c | 6 +- fs/xfs/xfs_qm.c | 5 +- include/linux/list_lru.h | 12 +++- include/linux/slab.h | 31 +++++---- include/linux/slab_def.h | 2 +- include/linux/slub_def.h | 2 +- mm/list_lru.c | 65 ++++++++++++++++-- mm/memcontrol.c | 86 ++++++++++++++++++----- mm/slab.c | 13 ++-- mm/slab.h | 65 +++++++++++------- mm/slab_common.c | 172 +++++++++++++++++++++++++++------------------- mm/slub.c | 24 +++---- mm/workingset.c | 3 +- 16 files changed, 336 insertions(+), 184 deletions(-)
Signature · 7 lines
1.7.10.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 1/6 slab: embed memcg_cache_params to kmem_cache
Currently, kmem_cache stores a pointer to struct memcg_cache_params instead of embedding it. The rationale is to save memory when kmem accounting is disabled. However, the memcg_cache_params has shrivelled drastically since it was first introduced:
* Initially:
struct memcg_cache_params {
bool is_root_cache;
union {
struct kmem_cache *memcg_caches[0];
struct {
struct mem_cgroup *memcg;
struct list_head list;
struct kmem_cache *root_cache;
bool dead;
atomic_t nr_pages;
struct work_struct destroy;
};
};
};* Now:
struct memcg_cache_params {
bool is_root_cache;
union {
struct {
struct rcu_head rcu_head;
struct kmem_cache *memcg_caches[0];
};
struct {
struct mem_cgroup *memcg;
struct kmem_cache *root_cache;
};
};
};So the memory saving does not seem to be a clear win anymore.
OTOH, keeping a pointer to memcg_cache_params struct instead of embedding it results in touching one more cache line on kmem alloc/free hot paths. Besides, it makes linking kmem caches in a list chained by a field of struct memcg_cache_params really painful due to a level of indirection, while I want to make them linked in the following patch. That said, let us embed it.
--- include/linux/slab.h | 17 +++---- include/linux/slab_def.h | 2 +- include/linux/slub_def.h | 2 +- mm/memcontrol.c | 11 ++-- mm/slab.h | 48 +++++++++--------- mm/slab_common.c | 126 +++++++++++++++++++++++++--------------------- mm/slub.c | 5 +- 7 files changed, 109 insertions(+), 102 deletions(-)
include/linux/slab.h +7 -10
@@ -473,14 +473,14 @@ static __always_inline void *kmalloc_node(size_t size, gfp_t flags, int node)#ifndef ARCH_SLAB_MINALIGN #define ARCH_SLAB_MINALIGN __alignof__(unsigned long long)#endif + +struct memcg_cache_array { +struct rcu_head rcu; +struct kmem_cache *entries[0 ]; +}; +/* * This is the main placeholder for memcg-related information in kmem caches. -* struct kmem_cache will hold a pointer to it, so the memory cost while -* disabled is 1 pointer. The runtime cost while enabled, gets bigger than it -* would otherwise be if that would be bundled in kmem_cache: we'll need an -* extra pointer chase. But the trade off clearly lays in favor of not -* penalizing non-users. -* * Both the root cache and the child caches will have it. For the root cache, * this will hold a dynamically allocated array large enough to hold * information about the currently limited memcgs in the system. To allow the
@@ -495,10 +495,7 @@ static __always_inline void *kmalloc_node(size_t size, gfp_t flags, int node) struct memcg_cache_params { bool is_root_cache; union { - struct { - struct rcu_head rcu_head; - struct kmem_cache *memcg_caches[0]; - }; + struct memcg_cache_array __rcu *memcg_caches; struct { struct mem_cgroup *memcg; struct kmem_cache *root_cache;
include/linux/slab_def.h +1 -1
@@ -70,7 +70,7 @@ struct kmem_cache {int obj_offset;#endif /* CONFIG_DEBUG_SLAB */ #ifdef CONFIG_MEMCG_KMEM -struct memcg_cache_params *memcg_params; +struct memcg_cache_params memcg_params;#endif struct kmem_cache_node *node[MAX_NUMNODES ];
include/linux/slub_def.h +1 -1
@@ -85,7 +85,7 @@ struct kmem_cache {struct kobject kobj;/* For sysfs */ #endif #ifdef CONFIG_MEMCG_KMEM -struct memcg_cache_params *memcg_params; +struct memcg_cache_params memcg_params;int max_attr_size;/* for propagation, maximum size of a stored attr */ #ifdef CONFIG_SYSFS struct kset *memcg_kset;
mm/memcontrol.c +5 -6
@@ -345,7 +345,7 @@ struct mem_cgroup {struct cg_proto tcp_mem;#endif #if defined(CONFIG_MEMCG_KMEM ) -/* Index in the kmem_cache->memcg_params->memcg_caches array */ +/* Index in the kmem_cache->memcg_params.memcg_caches array */ int kmemcg_id;#endif
@@ -557,7 +557,7 @@ static void disarm_sock_keys(struct mem_cgroup *memcg)#ifdef CONFIG_MEMCG_KMEM /* -* This will be the memcg's index in each cache's ->memcg_params->memcg_caches. +* This will be the memcg's index in each cache's ->memcg_params.memcg_caches. * The main reason for not using cgroup id for this: * this works better in sparse environments, where we have a lot of memcgs, * but only a few kmem-limited. Or also, if we have, for instance, 200
@@ -2676,8 +2676,7 @@ struct kmem_cache *__memcg_kmem_get_cache(struct kmem_cache *cachep)struct mem_cgroup *memcg;struct kmem_cache *memcg_cachep; -VM_BUG_ON (!cachep->memcg_params); -VM_BUG_ON (!cachep->memcg_params->is_root_cache); +VM_BUG_ON (!is_root_cache (cachep));if (current->memcg_kmem_skip_account)return cachep;
@@ -2711,7 +2710,7 @@ out:void __memcg_kmem_put_cache (struct kmem_cache *cachep) {if (!is_root_cache (cachep)) -css_put (&cachep->memcg_params->memcg->css); +css_put (&cachep->memcg_params.memcg->css); }/*
@@ -2787,7 +2786,7 @@ struct mem_cgroup *__mem_cgroup_from_kmem(void *ptr)if (PageSlab (page)) { cachep = page->slab_cache;if (!is_root_cache (cachep)) - memcg = cachep->memcg_params->memcg; + memcg = cachep->memcg_params.memcg; }else /* page allocated by alloc_kmem_pages */ memcg = page->mem_cgroup;
mm/slab.h +23 -25
112 lines not on this page · raw message
mm/slab_common.c +69 -57
240 lines not on this page · raw message
mm/slub.c +3 -2
@@ -3566,6 +3566,7 @@ static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache) p->slab_cache = s;#endif } +slab_init_memcg_params (s);list_add (&s->list, &slab_caches);return s; }
@@ -4953,7 +4954,7 @@ static void memcg_propagate_slab_attrs(struct kmem_cache *s)if (is_root_cache (s))return ; - root_cache = s->memcg_params->root_cache; + root_cache = s->memcg_params.root_cache;/* * This mean this cache had no attribute written. Therefore, no point
@@ -5033,7 +5034,7 @@ static inline struct kset *cache_kset(struct kmem_cache *s) {#ifdef CONFIG_MEMCG_KMEM if (!is_root_cache (s)) -return s->memcg_params->root_cache->memcg_kset; +return s->memcg_params.root_cache->memcg_kset;#endif return slab_kset; }
Signature · 7 lines
1.7.10.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 2/6 slab: link memcg caches of the same kind into a list
In reply to Vladimir DavydovSometimes, we need to iterate over all memcg copies of a particular root kmem cache. Currently, we use memcg_cache_params->memcg_caches array for that, because it contains all existing memcg caches.
However, it's a bad practice to keep all caches, including those that belong to offline cgroups, in this array, because it will be growing beyond any bounds then. I'm going to wipe away dead caches from it to save space. To still be able to perform iterations over all memcg caches of the same kind, let us link them into a list.
--- include/linux/slab.h | 4 ++++ mm/slab.c | 13 +++++-------- mm/slab.h | 17 +++++++++++++++++ mm/slab_common.c | 21 ++++++++++----------- mm/slub.c | 19 +++++-------------- 5 files changed, 41 insertions(+), 33 deletions(-)
include/linux/slab.h +4 -0
@@ -491,9 +491,13 @@ struct memcg_cache_array {* * @memcg: pointer to the memcg this cache belongs to * @root_cache: pointer to the global, root cache, this cache was derived from +* +* Both root and child caches of the same kind are linked into a list chained +* through @list. */ struct memcg_cache_params { bool is_root_cache; + struct list_head list; union { struct memcg_cache_array __rcu *memcg_caches; struct {
mm/slab.c +5 -8
@@ -3708,8 +3708,7 @@ static int do_tune_cpucache(struct kmem_cache *cachep, int limit, int batchcount, int shared, gfp_t gfp) { int ret; -struct kmem_cache *c =NULL ; -int i =0 ; +struct kmem_cache *c; ret =__do_tune_cpucache (cachep, limit, batchcount, shared, gfp);
@@ -3719,12 +3718,10 @@ static int do_tune_cpucache(struct kmem_cache *cachep, int limit,if ((ret <0 ) || !is_root_cache (cachep))return ret; -VM_BUG_ON (!mutex_is_locked (&slab_mutex)); -for_each_memcg_cache_index (i) { - c =cache_from_memcg_idx (cachep, i); -if (c) -/* return value determined by the parent cache only */ -__do_tune_cpucache (c, limit, batchcount, shared, gfp); +lockdep_assert_held (&slab_mutex); +for_each_memcg_cache (c, cachep) { +/* return value determined by the root cache only */ +__do_tune_cpucache (c, limit, batchcount, shared, gfp); }return ret;
mm/slab.h +17 -0
@@ -163,6 +163,18 @@ ssize_t slabinfo_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos);#ifdef CONFIG_MEMCG_KMEM +/* +* Iterate over all memcg caches of the given root cache. The caller must hold +* slab_mutex. +*/ +#define for_each_memcg_cache (iter, root) \ + list_for_each_entry(iter, &(root)->memcg_params.list, \ + memcg_params.list) + +#define for_each_memcg_cache_safe (iter, tmp, root) \ + list_for_each_entry_safe(iter, tmp, &(root)->memcg_params.list, \ + memcg_params.list) +static inline bool is_root_cache (struct kmem_cache *s) {return s->memcg_params.is_root_cache;
@@ -241,6 +253,11 @@ extern void slab_init_memcg_params(struct kmem_cache *);#else /* !CONFIG_MEMCG_KMEM */ +#define for_each_memcg_cache (iter, root) \ + for (iter = NULL, (root); 0; ) +#define for_each_memcg_cache_safe (iter, tmp, root) \ + for (iter = NULL, tmp = NULL, (root); 0; ) +static inline bool is_root_cache (struct kmem_cache *s) {return true;
mm/slab_common.c +10 -11
@@ -109,6 +109,7 @@ static inline int kmem_cache_sanity_check(const char *name, size_t size)void slab_init_memcg_params (struct kmem_cache *s) { s->memcg_params.is_root_cache = true; +INIT_LIST_HEAD (&s->memcg_params.list);RCU_INIT_POINTER (s->memcg_params.memcg_caches,NULL ); }
@@ -448,6 +449,7 @@ static int do_kmem_cache_shutdown(struct kmem_cache *s, lockdep_is_held(&slab_mutex));BUG_ON (arr->entries[idx] != s); arr->entries[idx] =NULL ; + list_del(&s->memcg_params.list); }#endif list_move (&s->list, release);
@@ -528,6 +530,8 @@ void memcg_create_kmem_cache(struct mem_cgroup *memcg, goto out_unlock; } +list_add (&s->memcg_params.list, &root_cache->memcg_params.list); +/* * Since readers won't lock (see cache_from_memcg_idx()), we need a * barrier here to ensure nobody will see the kmem_cache partially
@@ -580,11 +584,13 @@ void slab_kmem_cache_release(struct kmem_cache *s)void kmem_cache_destroy (struct kmem_cache *s) { -int i; +struct kmem_cache *c, *c2;LIST_HEAD (release);bool need_rcu_barrier = false;bool busy = false; +BUG_ON (!is_root_cache (s)); +get_online_cpus ();get_online_mems ();
@@ -594,10 +600,8 @@ void kmem_cache_destroy(struct kmem_cache *s)if (s->refcount) goto out_unlock; -for_each_memcg_cache_index (i) { -struct kmem_cache *c =cache_from_memcg_idx (s, i); - -if (c &&do_kmem_cache_shutdown (c, &release, &need_rcu_barrier)) +for_each_memcg_cache_safe (c, c2, s) { +if (do_kmem_cache_shutdown (c, &release, &need_rcu_barrier)) busy = true; }
@@ -931,16 +935,11 @@ memcg_accumulate_slabinfo(struct kmem_cache *s, struct slabinfo *info) {struct kmem_cache *c;struct slabinfo sinfo; -int i;if (!is_root_cache (s))return ; -for_each_memcg_cache_index (i) { - c =cache_from_memcg_idx (s, i); -if (!c) -continue ; - +for_each_memcg_cache (c, s) {memset (&sinfo,0 ,sizeof (sinfo));get_slabinfo (c, &sinfo);
mm/slub.c +5 -14
@@ -3625,13 +3625,10 @@ struct kmem_cache * __kmem_cache_alias(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *)) { - struct kmem_cache *s; + struct kmem_cache *s, *c; s =find_mergeable (size, align, flags, name, ctor);if (s) { -int i; -struct kmem_cache *c; - s->refcount++;/*
@@ -3641,10 +3638,7 @@ __kmem_cache_alias(const char *name, size_t size, size_t align, s->object_size =max (s->object_size, (int )size); s->inuse =max_t (int, s->inuse,ALIGN (size,sizeof (void *))); -for_each_memcg_cache_index (i) { - c =cache_from_memcg_idx (s, i); -if (!c) -continue ; +for_each_memcg_cache (c, s) { c->object_size = s->object_size; c->inuse =max_t (int, c->inuse,ALIGN (size,sizeof (void *)));
@@ -4910,7 +4904,7 @@ static ssize_t slab_attr_store(struct kobject *kobj, err = attribute->store (s, buf, len);#ifdef CONFIG_MEMCG_KMEM if (slab_state >=FULL && err >=0 &&is_root_cache (s)) { -int i; +struct kmem_cache *c;mutex_lock (&slab_mutex);if (s->max_attr_size < len)
@@ -4933,11 +4927,8 @@ static ssize_t slab_attr_store(struct kobject *kobj,* directly either failed or succeeded, in which case we loop * through the descendants with best-effort propagation. */ -for_each_memcg_cache_index (i) { -struct kmem_cache *c =cache_from_memcg_idx (s, i); -if (c) - attribute->store (c, buf, len); - } +for_each_memcg_cache (c, s) + attribute->store (c, buf, len); mutex_unlock(&slab_mutex); }#endif
Signature · 1 line
1.7.10.4
PATCH 3/6 slab: use css id for naming per memcg caches
In reply to Vladimir DavydovCurrently, we use mem_cgroup->kmemcg_id to guarantee kmem_cache->name uniqueness. This is correct, because kmemcg_id is only released on css free after destroying all per memcg caches.
However, I am going to change that and release kmemcg_id on css offline, because it is not wise to keep it for so long, wasting valuable entries of memcg_cache_params->memcg_caches arrays. Therefore, to preserve cache name uniqueness, let us switch to css->id.
--- mm/slab_common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
mm/slab_common.c +3 -3
@@ -487,6 +487,7 @@ void memcg_create_kmem_cache(struct mem_cgroup *memcg, struct kmem_cache *root_cache) {static char memcg_name_buf[NAME_MAX +1 ];/* protected by slab_mutex */ +struct cgroup_subsys_state *css =mem_cgroup_css (memcg);struct memcg_cache_array *arr;struct kmem_cache *s =NULL ;char *cache_name;
@@ -509,10 +510,9 @@ void memcg_create_kmem_cache(struct mem_cgroup *memcg,if (arr->entries[idx]) goto out_unlock; -cgroup_name (mem_cgroup_css (memcg)->cgroup, - memcg_name_buf,sizeof (memcg_name_buf)); +cgroup_name (css->cgroup, memcg_name_buf,sizeof (memcg_name_buf)); cache_name =kasprintf (GFP_KERNEL ,"%s(%d:%s)" , root_cache->name, - idx, memcg_name_buf); + css->id, memcg_name_buf);if (!cache_name) goto out_unlock;
Signature · 1 line
1.7.10.4
PATCH 4/6 memcg: free memcg_caches slot on css offline
In reply to Vladimir DavydovWe need to look up a kmem_cache in ->memcg_params.memcg_caches arrays only on allocations, so there is no need to have the array entries set until css free - we can clear them on css offline. This will allow us to reuse array entries more efficiently and avoid costly array relocations.
--- include/linux/slab.h | 10 +++++----- mm/memcontrol.c | 38 ++++++++++++++++++++++++++++++++------ mm/slab_common.c | 39 ++++++++++++++++++++++++++++----------- mm/vmscan.c | 2 +- 4 files changed, 66 insertions(+), 23 deletions(-)
include/linux/slab.h +5 -5
@@ -115,13 +115,12 @@ int slab_is_available(void);struct kmem_cache *kmem_cache_create (const char *,size_t ,size_t ,unsigned long ,void (*)(void *)); -#ifdef CONFIG_MEMCG_KMEM -void memcg_create_kmem_cache (struct mem_cgroup *,struct kmem_cache *); -void memcg_destroy_kmem_caches (struct mem_cgroup *); -#endif void kmem_cache_destroy (struct kmem_cache *);int kmem_cache_shrink (struct kmem_cache *); -void kmem_cache_free (struct kmem_cache *,void *); + +void memcg_create_kmem_cache (struct mem_cgroup *,struct kmem_cache *); +void memcg_deactivate_kmem_caches (struct mem_cgroup *); +void memcg_destroy_kmem_caches (struct mem_cgroup *);/* * Please use this macro to create slab caches. Simply specify the
@@ -288,6 +287,7 @@ static __always_inline int kmalloc_index(size_t size)void *__kmalloc (size_t size,gfp_t flags);void *kmem_cache_alloc (struct kmem_cache *,gfp_t flags); +void kmem_cache_free (struct kmem_cache *,void *);#ifdef CONFIG_NUMA void *__kmalloc_node (size_t size,gfp_t flags,int node);
mm/memcontrol.c +32 -6
@@ -347,6 +347,7 @@ struct mem_cgroup {#if defined(CONFIG_MEMCG_KMEM )/* Index in the kmem_cache->memcg_params.memcg_caches array */ int kmemcg_id; +bool kmem_acct_active;#endif int last_scanned_node;
@@ -367,7 +368,7 @@ struct mem_cgroup {#ifdef CONFIG_MEMCG_KMEM bool memcg_kmem_is_active (struct mem_cgroup *memcg) { -return memcg->kmemcg_id >=0 ; +return memcg->kmem_acct_active; }#endif
@@ -611,7 +612,7 @@ static void memcg_free_cache_id(int id);static void disarm_kmem_keys (struct mem_cgroup *memcg) { -if (memcg_kmem_is_active (memcg)) { +if (memcg->kmemcg_id >=0 ) {static_key_slow_dec (&memcg_kmem_enabled_key);memcg_free_cache_id (memcg->kmemcg_id); }
@@ -2675,6 +2676,7 @@ struct kmem_cache *__memcg_kmem_get_cache(struct kmem_cache *cachep) {struct mem_cgroup *memcg;struct kmem_cache *memcg_cachep; +int kmemcg_id;VM_BUG_ON (!is_root_cache (cachep));
@@ -2682,10 +2684,11 @@ struct kmem_cache *__memcg_kmem_get_cache(struct kmem_cache *cachep)return cachep; memcg =get_mem_cgroup_from_mm (current->mm); -if (!memcg_kmem_is_active (memcg)) + kmemcg_id =ACCESS_ONCE (memcg->kmemcg_id); +if (kmemcg_id <0 ) goto out; - memcg_cachep =cache_from_memcg_idx (cachep,memcg_cache_id (memcg)); + memcg_cachep =cache_from_memcg_idx (cachep, kmemcg_id);if (likely (memcg_cachep))return memcg_cachep;
@@ -3327,8 +3330,8 @@ static int memcg_activate_kmem(struct mem_cgroup *memcg,int err =0 ;int memcg_id; -if (memcg_kmem_is_active (memcg)) -return 0 ; +BUG_ON (memcg->kmemcg_id >=0 ); +BUG_ON (memcg->kmem_acct_active);/* * For simplicity, we won't allow this to be disabled. It also can't
@@ -3371,6 +3374,7 @@ static int memcg_activate_kmem(struct mem_cgroup *memcg,* patched. */ memcg->kmemcg_id = memcg_id; + memcg->kmem_acct_active = true; out:return err; }
@@ -4046,6 +4050,22 @@ static int memcg_init_kmem(struct mem_cgroup *memcg, struct cgroup_subsys *ss)return mem_cgroup_sockets_init (memcg, ss); } +static void memcg_deactivate_kmem (struct mem_cgroup *memcg) +{ +if (!memcg->kmem_acct_active) +return ; + +/* +* Clear the 'active' flag before clearing memcg_caches arrays entries. +* Since we take the slab_mutex in memcg_deactivate_kmem_caches(), it +* guarantees no cache will be created for this cgroup after we are +* done (see memcg_create_kmem_cache()). +*/ + memcg->kmem_acct_active = false; + +memcg_deactivate_kmem_caches (memcg); +} +static void memcg_destroy_kmem (struct mem_cgroup *memcg) {memcg_destroy_kmem_caches (memcg);
@@ -4057,6 +4077,10 @@ static int memcg_init_kmem(struct mem_cgroup *memcg, struct cgroup_subsys *ss)return 0 ; } +static void memcg_deactivate_kmem (struct mem_cgroup *memcg) +{ +} +static void memcg_destroy_kmem (struct mem_cgroup *memcg) { }
@@ -4661,6 +4685,8 @@ static void mem_cgroup_css_offline(struct cgroup_subsys_state *css)spin_unlock (&memcg->event_list_lock);vmpressure_cleanup (&memcg->vmpressure); + +memcg_deactivate_kmem (memcg); }static void mem_cgroup_css_free (struct cgroup_subsys_state *css)
mm/slab_common.c +28 -11
@@ -439,18 +439,8 @@ static int do_kmem_cache_shutdown(struct kmem_cache *s, *need_rcu_barrier = true;#ifdef CONFIG_MEMCG_KMEM -if (!is_root_cache (s)) { -int idx; -struct memcg_cache_array *arr; - - idx =memcg_cache_id (s->memcg_params.memcg); - arr =rcu_dereference_protected (s->memcg_params.root_cache-> - memcg_params.memcg_caches, -lockdep_is_held (&slab_mutex)); -BUG_ON (arr->entries[idx] != s); - arr->entries[idx] =NULL ; +if (!is_root_cache (s))list_del (&s->memcg_params.list); - }#endif list_move (&s->list, release);return 0 ;
@@ -498,6 +488,13 @@ void memcg_create_kmem_cache(struct mem_cgroup *memcg,mutex_lock (&slab_mutex); +/* +* The memory cgroup could have been deactivated while the cache +* creation work was pending. +*/ +if (!memcg_kmem_is_active (memcg)) + goto out_unlock; + idx =memcg_cache_id (memcg); arr =rcu_dereference_protected (root_cache->memcg_params.memcg_caches,lockdep_is_held (&slab_mutex));
@@ -547,6 +544,26 @@ out_unlock:put_online_cpus (); } +void memcg_deactivate_kmem_caches (struct mem_cgroup *memcg) +{ +int idx; +struct memcg_cache_array *arr; +struct kmem_cache *s; + + idx =memcg_cache_id (memcg); + +mutex_lock (&slab_mutex); +list_for_each_entry (s, &slab_caches, list) { +if (!is_root_cache (s)) +continue ; + + arr =rcu_dereference_protected (s->memcg_params.memcg_caches, +lockdep_is_held (&slab_mutex)); + arr->entries[idx] =NULL ; + } +mutex_unlock (&slab_mutex); +} +void memcg_destroy_kmem_caches (struct mem_cgroup *memcg) {LIST_HEAD (release);
mm/vmscan.c +1 -1
@@ -377,7 +377,7 @@ static unsigned long shrink_slab(gfp_t gfp_mask, int nid,struct shrinker *shrinker;unsigned long freed =0 ; -if (memcg && !memcg_kmem_is_active (memcg)) +if (memcg_cache_id (memcg) <0 )return 0 ;if (nr_scanned ==0 )
Signature · 1 line
1.7.10.4
PATCH 5/6 list_lru: add helpers to isolate items
In reply to Vladimir DavydovCurrently, the isolate callback passed to the list_lru_walk family of functions is supposed to just delete an item from the list upon returning LRU_REMOVED or LRU_REMOVED_RETRY, while nr_items counter is fixed by __list_lru_walk_one after the callback returns. Since the callback is allowed to drop the lock after removing an item (it has to return LRU_REMOVED_RETRY then), the nr_items can be less than the actual number of elements on the list even if we check them under the lock. This makes it difficult to move items from one list_lru_one to another, which is required for per-memcg list_lru reparenting - we can't just splice the lists, we have to move entries one by one.
This patch therefore introduces helpers that must be used by callback functions to isolate items instead of raw list_del/list_move. These are list_lru_isolate and list_lru_isolate_move. They not only remove the entry from the list, but also fix the nr_items counter, making sure nr_items always reflects the actual number of elements on the list if checked under the appropriate lock.
--- fs/dcache.c | 21 +++++++++++---------- fs/gfs2/quota.c | 5 +++-- fs/inode.c | 8 ++++---- fs/xfs/xfs_buf.c | 6 ++++-- fs/xfs/xfs_qm.c | 5 +++-- include/linux/list_lru.h | 9 +++++++-- mm/list_lru.c | 19 ++++++++++++++++--- mm/workingset.c | 3 ++- 8 files changed, 50 insertions(+), 26 deletions(-)
fs/dcache.c +11 -10
@@ -400,19 +400,20 @@ static void d_shrink_add(struct dentry *dentry, struct list_head *list)* LRU lists entirely, while shrink_move moves it to the indicated * private list. */ -static void d_lru_isolate (struct dentry *dentry) +static void d_lru_isolate (struct list_lru_one *lru,struct dentry *dentry) {D_FLAG_VERIFY (dentry,DCACHE_LRU_LIST ); dentry->d_flags &= ~DCACHE_LRU_LIST ;this_cpu_dec (nr_dentry_unused); -list_del_init (&dentry->d_lru); +list_lru_isolate (lru, &dentry->d_lru); } -static void d_lru_shrink_move (struct dentry *dentry,struct list_head *list) +static void d_lru_shrink_move (struct list_lru_one *lru,struct dentry *dentry, +struct list_head *list) {D_FLAG_VERIFY (dentry,DCACHE_LRU_LIST ); dentry->d_flags |=DCACHE_SHRINK_LIST ; -list_move_tail (&dentry->d_lru, list); +list_lru_isolate_move (lru, &dentry->d_lru, list); }/*
@@ -869,8 +870,8 @@ static void shrink_dentry_list(struct list_head *list) } } -static enum lru_status -dentry_lru_isolate (struct list_head *item,spinlock_t *lru_lock,void *arg) +static enum lru_status dentry_lru_isolate (struct list_head *item, +struct list_lru_one *lru,spinlock_t *lru_lock,void *arg) {struct list_head *freeable = arg; struct dentry *dentry = container_of(item, struct dentry, d_lru);
@@ -890,7 +891,7 @@ dentry_lru_isolate(struct list_head *item, spinlock_t *lru_lock, void *arg)* another pass through the LRU. */ if (dentry->d_lockref.count) { -d_lru_isolate (dentry); +d_lru_isolate (lru, dentry);spin_unlock (&dentry->d_lock);return LRU_REMOVED ; }
@@ -921,7 +922,7 @@ dentry_lru_isolate(struct list_head *item, spinlock_t *lru_lock, void *arg)return LRU_ROTATE ; } -d_lru_shrink_move (dentry, freeable); +d_lru_shrink_move (lru, dentry, freeable);spin_unlock (&dentry->d_lock);return LRU_REMOVED ;
@@ -951,7 +952,7 @@ long prune_dcache_sb(struct super_block *sb, struct shrink_control *sc) }static enum lru_status dentry_lru_isolate_shrink (struct list_head *item, -spinlock_t *lru_lock,void *arg) +struct list_lru_one *lru,spinlock_t *lru_lock,void *arg) {struct list_head *freeable = arg; struct dentry *dentry = container_of(item, struct dentry, d_lru);
@@ -964,7 +965,7 @@ static enum lru_status dentry_lru_isolate_shrink(struct list_head *item,if (!spin_trylock (&dentry->d_lock))return LRU_SKIP ; -d_lru_shrink_move (dentry, freeable); +d_lru_shrink_move (lru, dentry, freeable);spin_unlock (&dentry->d_lock);return LRU_REMOVED ;
fs/gfs2/quota.c +3 -2
@@ -145,7 +145,8 @@ static void gfs2_qd_dispose(struct list_head *list) } -static enum lru_status gfs2_qd_isolate (struct list_head *item,spinlock_t *lock,void *arg) +static enum lru_status gfs2_qd_isolate (struct list_head *item, +struct list_lru_one *lru,spinlock_t *lru_lock,void *arg) {struct list_head *dispose = arg; struct gfs2_quota_data *qd = list_entry(item, struct gfs2_quota_data, qd_lru);
@@ -155,7 +156,7 @@ static enum lru_status gfs2_qd_isolate(struct list_head *item, spinlock_t *lock,if (qd->qd_lockref.count ==0 ) {lockref_mark_dead (&qd->qd_lockref); -list_move (&qd->qd_lru, dispose); +list_lru_isolate_move (lru, &qd->qd_lru, dispose); }spin_unlock (&qd->qd_lockref.lock);
fs/inode.c +4 -4
@@ -684,8 +684,8 @@ int invalidate_inodes(struct super_block *sb, bool kill_dirty)* LRU does not have strict ordering. Hence we don't want to reclaim inodes * with this flag set because they are the inodes that are out of order. */ -static enum lru_status -inode_lru_isolate (struct list_head *item,spinlock_t *lru_lock,void *arg) +static enum lru_status inode_lru_isolate (struct list_head *item, +struct list_lru_one *lru,spinlock_t *lru_lock,void *arg) {struct list_head *freeable = arg; struct inode *inode = container_of(item, struct inode, i_lru);
@@ -703,7 +703,7 @@ inode_lru_isolate(struct list_head *item, spinlock_t *lru_lock, void *arg)*/ if (atomic_read (&inode->i_count) || (inode->i_state & ~I_REFERENCED )) { -list_del_init (&inode->i_lru); +list_lru_isolate (lru, &inode->i_lru);spin_unlock (&inode->i_lock);this_cpu_dec (nr_unused);return LRU_REMOVED ;
@@ -737,7 +737,7 @@ inode_lru_isolate(struct list_head *item, spinlock_t *lru_lock, void *arg)WARN_ON (inode->i_state &I_NEW ); inode->i_state |=I_FREEING ; -list_move (&inode->i_lru, freeable); +list_lru_isolate_move (lru, &inode->i_lru, freeable);spin_unlock (&inode->i_lock);this_cpu_dec (nr_unused);
fs/xfs/xfs_buf.c +4 -2
@@ -1488,6 +1488,7 @@ xfs_buf_iomove(static enum lru_status xfs_buftarg_wait_rele (struct list_head *item, +struct list_lru_one *lru,spinlock_t *lru_lock,void *arg)
@@ -1509,7 +1510,7 @@ xfs_buftarg_wait_rele(*/ atomic_set (&bp->b_lru_ref,0 ); bp->b_state |=XFS_BSTATE_DISPOSE ; -list_move (item, dispose); +list_lru_isolate_move (lru, item, dispose);spin_unlock (&bp->b_lock);return LRU_REMOVED ; }
@@ -1546,6 +1547,7 @@ xfs_wait_buftarg(static enum lru_status xfs_buftarg_isolate (struct list_head *item, +struct list_lru_one *lru,spinlock_t *lru_lock,void *arg) {
@@ -1569,7 +1571,7 @@ xfs_buftarg_isolate( } bp->b_state |=XFS_BSTATE_DISPOSE ; -list_move (item, dispose); +list_lru_isolate_move (lru, item, dispose);spin_unlock (&bp->b_lock);return LRU_REMOVED ; }
fs/xfs/xfs_qm.c +3 -2
@@ -430,6 +430,7 @@ struct xfs_qm_isolate { static enum lru_status xfs_qm_dquot_isolate( struct list_head *item, + struct list_lru_one *lru, spinlock_t *lru_lock, void *arg) __releases(lru_lock) __acquires(lru_lock)
@@ -450,7 +451,7 @@ xfs_qm_dquot_isolate(XFS_STATS_INC (xs_qm_dqwants);trace_xfs_dqreclaim_want (dqp); -list_del_init (&dqp->q_lru); +list_lru_isolate (lru, &dqp->q_lru);XFS_STATS_DEC (xs_qm_dquot_unused);return LRU_REMOVED ; }
@@ -494,7 +495,7 @@ xfs_qm_dquot_isolate(xfs_dqunlock (dqp);ASSERT (dqp->q_nrefs ==0 ); -list_move_tail (&dqp->q_lru, &isol->dispose); +list_lru_isolate_move (lru, &dqp->q_lru, &isol->dispose);XFS_STATS_DEC (xs_qm_dquot_unused);trace_xfs_dqreclaim_done (dqp);XFS_STATS_INC (xs_qm_dqreclaims);
include/linux/list_lru.h +7 -2
@@ -125,8 +125,13 @@ static inline unsigned long list_lru_count(struct list_lru *lru)return count; } -typedef enum lru_status -(*list_lru_walk_cb )(struct list_head *item,spinlock_t *lock,void *cb_arg); +void list_lru_isolate (struct list_lru_one *list,struct list_head *item); +void list_lru_isolate_move (struct list_lru_one *list,struct list_head *item, +struct list_head *head); + +typedef enum lru_status (*list_lru_walk_cb )(struct list_head *item, +struct list_lru_one *list,spinlock_t *lock,void *cb_arg); +/** * list_lru_walk_one: walk a list_lru, isolating and disposing freeable items. * @lru: the lru pointer.
mm/list_lru.c +16 -3
@@ -132,6 +132,21 @@ bool list_lru_del(struct list_lru *lru, struct list_head *item) }EXPORT_SYMBOL_GPL (list_lru_del); +void list_lru_isolate (struct list_lru_one *list,struct list_head *item) +{ +list_del_init (item); + list->nr_items--; +} +EXPORT_SYMBOL_GPL (list_lru_isolate); + +void list_lru_isolate_move (struct list_lru_one *list,struct list_head *item, +struct list_head *head) +{ +list_move (item, head); + list->nr_items--; +} +EXPORT_SYMBOL_GPL (list_lru_isolate_move); +static unsigned long __list_lru_count_one (struct list_lru *lru,int nid,int memcg_idx) {
@@ -194,13 +209,11 @@ restart:break ; --*nr_to_walk; - ret =isolate (item, &nlru->lock, cb_arg); + ret =isolate (item, l, &nlru->lock, cb_arg);switch (ret) {case LRU_REMOVED_RETRY :assert_spin_locked (&nlru->lock);case LRU_REMOVED : - l->nr_items--; -WARN_ON_ONCE (l->nr_items <0 ); isolated++;/* * If the lru lock has been dropped, our list
mm/workingset.c +2 -1
@@ -302,6 +302,7 @@ static unsigned long count_shadow_nodes(struct shrinker *shrinker, }static enum lru_status shadow_lru_isolate (struct list_head *item, +struct list_lru_one *lru,spinlock_t *lru_lock,void *arg) {
@@ -332,7 +333,7 @@ static enum lru_status shadow_lru_isolate(struct list_head *item, goto out; } -list_del_init (item); +list_lru_isolate (lru, item);spin_unlock (lru_lock);/*
Signature · 7 lines
1.7.10.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 6/6 memcg: reparent list_lrus and free kmemcg_id on css offline
In reply to Vladimir DavydovNow, the only reason to keep kmemcg_id till css free is list_lru, which uses it to distribute elements between per-memcg lists. However, it can be easily sorted out - we only need to change kmemcg_id of an offline cgroup to its parent's id, making further list_lru_add()'s add elements to the parent's list, and then move all elements from the offline cgroup's list to the one of its parent. It will work, because a racing list_lru_del() does not need to know the list it is deleting the element from. It can decrement the wrong nr_items counter though, but the ongoing reparenting will fix it. After list_lru reparenting is done we are free to release kmemcg_id saving a valuable slot in a per-memcg array for new cgroups.
--- include/linux/list_lru.h | 3 ++- mm/list_lru.c | 46 +++++++++++++++++++++++++++++++++++++++++++--- mm/memcontrol.c | 39 ++++++++++++++++++++++++++++++++++----- mm/vmscan.c | 2 +- 4 files changed, 80 insertions(+), 10 deletions(-)
include/linux/list_lru.h +2 -1
@@ -26,7 +26,7 @@ enum lru_status {struct list_lru_one {struct list_head list; -/* kept as signed so we can catch imbalance bugs */ +/* may become negative during memcg reparenting */ long nr_items; };
@@ -62,6 +62,7 @@ int __list_lru_init(struct list_lru *lru, bool memcg_aware,#define list_lru_init_memcg (lru) __list_lru_init((lru), true, NULL)int memcg_update_all_list_lrus (int num_memcgs); +void memcg_drain_all_list_lrus (int src_idx,int dst_idx);/** * list_lru_add: add an element to the lru list's tail
mm/list_lru.c +43 -3
70 lines not on this page · raw message
mm/memcontrol.c +34 -5
@@ -347,6 +347,7 @@ struct mem_cgroup {#if defined(CONFIG_MEMCG_KMEM )/* Index in the kmem_cache->memcg_params.memcg_caches array */ int kmemcg_id; +bool kmem_acct_activated;bool kmem_acct_active;#endif
@@ -608,14 +609,10 @@ void memcg_put_cache_ids(void)struct static_key memcg_kmem_enabled_key;EXPORT_SYMBOL (memcg_kmem_enabled_key); -static void memcg_free_cache_id (int id); -static void disarm_kmem_keys (struct mem_cgroup *memcg) { -if (memcg->kmemcg_id >=0 ) { +if (memcg->kmem_acct_activated)static_key_slow_dec (&memcg_kmem_enabled_key); -memcg_free_cache_id (memcg->kmemcg_id); - }/* * This check can't live in kmem destruction function, * since the charges will outlive the cgroup
@@ -3331,6 +3328,7 @@ static int memcg_activate_kmem(struct mem_cgroup *memcg,int memcg_id;BUG_ON (memcg->kmemcg_id >=0 ); +BUG_ON (memcg->kmem_acct_activated);BUG_ON (memcg->kmem_acct_active);/*
@@ -3374,6 +3372,7 @@ static int memcg_activate_kmem(struct mem_cgroup *memcg,* patched. */ memcg->kmemcg_id = memcg_id; + memcg->kmem_acct_activated = true; memcg->kmem_acct_active = true; out:return err;
@@ -4052,6 +4051,10 @@ static int memcg_init_kmem(struct mem_cgroup *memcg, struct cgroup_subsys *ss)static void memcg_deactivate_kmem (struct mem_cgroup *memcg) { +struct cgroup_subsys_state *css; +struct mem_cgroup *parent, *child; +int kmemcg_id; +if (!memcg->kmem_acct_active)return ;
@@ -4064,6 +4067,32 @@ static void memcg_deactivate_kmem(struct mem_cgroup *memcg) memcg->kmem_acct_active = false;memcg_deactivate_kmem_caches (memcg); + + kmemcg_id = memcg->kmemcg_id; +BUG_ON (kmemcg_id <0 ); + + parent =parent_mem_cgroup (memcg); +if (!parent) + parent = root_mem_cgroup; + +/* +* Change kmemcg_id of this cgroup and all its descendants to the +* parent's id, and then move all entries from this cgroup's list_lrus +* to ones of the parent. After we have finished, all list_lrus +* corresponding to this cgroup are guaranteed to remain empty. The +* ordering is imposed by list_lru_node->lock taken by +* memcg_drain_all_list_lrus(). +*/ +css_for_each_descendant_pre (css, &memcg->css) { + child =mem_cgroup_from_css (css); +BUG_ON (child->kmemcg_id != kmemcg_id); + child->kmemcg_id = parent->kmemcg_id; +if (!memcg->use_hierarchy) +break ; + } +memcg_drain_all_list_lrus (kmemcg_id, parent->kmemcg_id); + +memcg_free_cache_id (kmemcg_id); }static void memcg_destroy_kmem (struct mem_cgroup *memcg)
mm/vmscan.c +1 -1
@@ -377,7 +377,7 @@ static unsigned long shrink_slab(gfp_t gfp_mask, int nid,struct shrinker *shrinker;unsigned long freed =0 ; -if (memcg_cache_id (memcg) <0 ) +if (memcg && !memcg_kmem_is_active (memcg))return 0 ;if (nr_scanned ==0 )
Signature · 7 lines
1.7.10.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Drop this patch please. It needs a rebase. Will resend soon.
Thanks,
Vladimir