提交 c8b2a36f 编写于 作者: G Glauber Costa 提交者: Linus Torvalds

memcg: execute the whole memcg freeing in free_worker()

A lot of the initialization we do in mem_cgroup_create() is done with
softirqs enabled.  This include grabbing a css id, which holds
&ss->id_lock->rlock, and the per-zone trees, which holds
rtpz->lock->rlock.  All of those signal to the lockdep mechanism that
those locks can be used in SOFTIRQ-ON-W context.

This means that the freeing of memcg structure must happen in a
compatible context, otherwise we'll get a deadlock, like the one below,
caught by lockdep:

   free_accounted_pages+0x47/0x4c
   free_task+0x31/0x5c
   __put_task_struct+0xc2/0xdb
   put_task_struct+0x1e/0x22
   delayed_put_task_struct+0x7a/0x98
   __rcu_process_callbacks+0x269/0x3df
   rcu_process_callbacks+0x31/0x5b
   __do_softirq+0x122/0x277

This usage pattern could not be triggered before kmem came into play.
With the introduction of kmem stack handling, it is possible that we call
the last mem_cgroup_put() from the task destructor, which is run in an rcu
callback.  Such callbacks are run with softirqs disabled, leading to the
offensive usage pattern.

In general, we have little, if any, means to guarantee in which context
the last memcg_put will happen.  The best we can do is test it and try to
make sure no invalid context releases are happening.  But as we add more
code to memcg, the possible interactions grow in number and expose more
ways to get context conflicts.  One thing to keep in mind, is that part of
the freeing process is already deferred to a worker, such as vfree(), that
can only be called from process context.

For the moment, the only two functions we really need moved away are:

  * free_css_id(), and
  * mem_cgroup_remove_from_trees().

But because the later accesses per-zone info,
free_mem_cgroup_per_zone_info() needs to be moved as well.  With that, we
are left with the per_cpu stats only.  Better move it all.
Signed-off-by: NGlauber Costa <glommer@parallels.com>
Tested-by: NGreg Thelen <gthelen@google.com>
Acked-by: NMichal Hocko <mhocko@suse.cz>
Acked-by: NKAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Cc: Christoph Lameter <cl@linux.com>
Cc: David Rientjes <rientjes@google.com>
Cc: Frederic Weisbecker <fweisbec@redhat.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: JoonSoo Kim <js1304@gmail.com>
Cc: Mel Gorman <mel@csn.ul.ie>
Cc: Pekka Enberg <penberg@cs.helsinki.fi>
Cc: Rik van Riel <riel@redhat.com>
Cc: Suleiman Souhlal <suleiman@google.com>
Cc: Tejun Heo <tj@kernel.org>
Signed-off-by: NAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: NLinus Torvalds <torvalds@linux-foundation.org>
上级 bea207c8
...@@ -5247,16 +5247,29 @@ static struct mem_cgroup *mem_cgroup_alloc(void) ...@@ -5247,16 +5247,29 @@ static struct mem_cgroup *mem_cgroup_alloc(void)
} }
/* /*
* Helpers for freeing a kmalloc()ed/vzalloc()ed mem_cgroup by RCU, * At destroying mem_cgroup, references from swap_cgroup can remain.
* but in process context. The work_freeing structure is overlaid * (scanning all at force_empty is too costly...)
* on the rcu_freeing structure, which itself is overlaid on memsw. *
* Instead of clearing all references at force_empty, we remember
* the number of reference from swap_cgroup and free mem_cgroup when
* it goes down to 0.
*
* Removal of cgroup itself succeeds regardless of refs from swap.
*/ */
static void free_work(struct work_struct *work)
static void __mem_cgroup_free(struct mem_cgroup *memcg)
{ {
struct mem_cgroup *memcg; int node;
int size = sizeof(struct mem_cgroup); int size = sizeof(struct mem_cgroup);
memcg = container_of(work, struct mem_cgroup, work_freeing); mem_cgroup_remove_from_trees(memcg);
free_css_id(&mem_cgroup_subsys, &memcg->css);
for_each_node(node)
free_mem_cgroup_per_zone_info(memcg, node);
free_percpu(memcg->stat);
/* /*
* We need to make sure that (at least for now), the jump label * We need to make sure that (at least for now), the jump label
* destruction code runs outside of the cgroup lock. This is because * destruction code runs outside of the cgroup lock. This is because
...@@ -5275,38 +5288,27 @@ static void free_work(struct work_struct *work) ...@@ -5275,38 +5288,27 @@ static void free_work(struct work_struct *work)
vfree(memcg); vfree(memcg);
} }
static void free_rcu(struct rcu_head *rcu_head)
{
struct mem_cgroup *memcg;
memcg = container_of(rcu_head, struct mem_cgroup, rcu_freeing);
INIT_WORK(&memcg->work_freeing, free_work);
schedule_work(&memcg->work_freeing);
}
/* /*
* At destroying mem_cgroup, references from swap_cgroup can remain. * Helpers for freeing a kmalloc()ed/vzalloc()ed mem_cgroup by RCU,
* (scanning all at force_empty is too costly...) * but in process context. The work_freeing structure is overlaid
* * on the rcu_freeing structure, which itself is overlaid on memsw.
* Instead of clearing all references at force_empty, we remember
* the number of reference from swap_cgroup and free mem_cgroup when
* it goes down to 0.
*
* Removal of cgroup itself succeeds regardless of refs from swap.
*/ */
static void free_work(struct work_struct *work)
static void __mem_cgroup_free(struct mem_cgroup *memcg)
{ {
int node; struct mem_cgroup *memcg;
mem_cgroup_remove_from_trees(memcg); memcg = container_of(work, struct mem_cgroup, work_freeing);
free_css_id(&mem_cgroup_subsys, &memcg->css); __mem_cgroup_free(memcg);
}
for_each_node(node) static void free_rcu(struct rcu_head *rcu_head)
free_mem_cgroup_per_zone_info(memcg, node); {
struct mem_cgroup *memcg;
free_percpu(memcg->stat); memcg = container_of(rcu_head, struct mem_cgroup, rcu_freeing);
call_rcu(&memcg->rcu_freeing, free_rcu); INIT_WORK(&memcg->work_freeing, free_work);
schedule_work(&memcg->work_freeing);
} }
static void mem_cgroup_get(struct mem_cgroup *memcg) static void mem_cgroup_get(struct mem_cgroup *memcg)
...@@ -5318,7 +5320,7 @@ static void __mem_cgroup_put(struct mem_cgroup *memcg, int count) ...@@ -5318,7 +5320,7 @@ static void __mem_cgroup_put(struct mem_cgroup *memcg, int count)
{ {
if (atomic_sub_and_test(count, &memcg->refcnt)) { if (atomic_sub_and_test(count, &memcg->refcnt)) {
struct mem_cgroup *parent = parent_mem_cgroup(memcg); struct mem_cgroup *parent = parent_mem_cgroup(memcg);
__mem_cgroup_free(memcg); call_rcu(&memcg->rcu_freeing, free_rcu);
if (parent) if (parent)
mem_cgroup_put(parent); mem_cgroup_put(parent);
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册