mm: memcontrol: fix swap counter leak on swapout from offline cgroup
Subsystems (3)
PATCH v3 1/3 mm: memcontrol: fix swap counter leak on swapout from offline cgroup
An offline memory cgroup might have anonymous memory or shmem left charged to it and no swap. Since only swap entries pin the id of an offline cgroup, such a cgroup will have no id and so an attempt to swapout its anon/shmem will not store memory cgroup info in the swap cgroup map. As a result, memcg->swap or memcg->memsw will never get uncharged from it and any of its ascendants.
Fix this by always charging swapout to the first ancestor cgroup that hasn't released its id yet.
---
Changes in v3:
- add comment to mem_cgroup_swapout (Johannes)
- rename mem_cgroup_id_get_active to mem_cgroup_id_get_online (Johannes, Michal)
mm/memcontrol.c | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-)
mm/memcontrol.c +38 -6
@@ -4036,6 +4036,24 @@ static void mem_cgroup_id_get(struct mem_cgroup *memcg)atomic_inc (&memcg->id.ref); } +static struct mem_cgroup *mem_cgroup_id_get_online (struct mem_cgroup *memcg) +{ +while (!atomic_inc_not_zero (&memcg->id.ref)) { +/* +* The root cgroup cannot be destroyed, so it's refcount must +* always be >= 1. +*/ +if (memcg == root_mem_cgroup) { +VM_BUG_ON (1 ); +break ; + } + memcg =parent_mem_cgroup (memcg); +if (!memcg) + memcg = root_mem_cgroup; + } +return memcg; +} +static void mem_cgroup_id_put (struct mem_cgroup *memcg) {if (atomic_dec_and_test (&memcg->id.ref)) {
@@ -5752,7 +5770,7 @@ subsys_initcall(mem_cgroup_init);*/ void mem_cgroup_swapout (struct page *page,swp_entry_t entry) { -struct mem_cgroup *memcg; +struct mem_cgroup *memcg, *swap_memcg;unsigned short oldid;VM_BUG_ON_PAGE (PageLRU (page), page);
@@ -5767,16 +5785,27 @@ void mem_cgroup_swapout(struct page *page, swp_entry_t entry)if (!memcg)return ; -mem_cgroup_id_get (memcg); - oldid =swap_cgroup_record (entry,mem_cgroup_id (memcg)); +/* +* In case the memcg owning these pages has been offlined and doesn't +* have an ID allocated to it anymore, charge the closest online +* ancestor for the swap instead and transfer the memory+swap charge. +*/ + swap_memcg =mem_cgroup_id_get_online (memcg); + oldid =swap_cgroup_record (entry,mem_cgroup_id (swap_memcg));VM_BUG_ON_PAGE (oldid, page); -mem_cgroup_swap_statistics (memcg, true); +mem_cgroup_swap_statistics (swap_memcg, true); page->mem_cgroup =NULL ;if (!mem_cgroup_is_root (memcg))page_counter_uncharge (&memcg->memory,1 ); +if (memcg != swap_memcg) { +if (!mem_cgroup_is_root (swap_memcg)) +page_counter_charge (&swap_memcg->memsw,1 ); +page_counter_uncharge (&memcg->memsw,1 ); + } +/* * Interrupts should be disabled here because the caller holds the * mapping->tree_lock lock which is taken with interrupts-off. It is
@@ -5815,11 +5844,14 @@ int mem_cgroup_try_charge_swap(struct page *page, swp_entry_t entry)if (!memcg)return 0 ; + memcg =mem_cgroup_id_get_online (memcg); +if (!mem_cgroup_is_root (memcg) && - !page_counter_try_charge (&memcg->swap,1 , &counter)) + !page_counter_try_charge (&memcg->swap,1 , &counter)) { +mem_cgroup_id_put (memcg);return -ENOMEM ; + } -mem_cgroup_id_get (memcg); oldid =swap_cgroup_record (entry,mem_cgroup_id (memcg));VM_BUG_ON_PAGE (oldid, page);mem_cgroup_swap_statistics (memcg, true);
Signature · 7 lines
2.1.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 v3 2/3 mm: memcontrol: fix memcg id ref counter on swap charge move
Since commit 73f576c04b941 swap entries do not pin memcg->css.refcnt directly. Instead, they pin memcg->id.ref. So we should adjust the reference counters accordingly when moving swap charges between cgroups.
--- mm/memcontrol.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-)
mm/memcontrol.c +18 -6
@@ -4031,9 +4031,9 @@ static struct cftype mem_cgroup_legacy_files[] = {static DEFINE_IDR (mem_cgroup_idr); -static void mem_cgroup_id_get (struct mem_cgroup *memcg) +static void mem_cgroup_id_get_many (struct mem_cgroup *memcg,unsigned int n) { -atomic_inc (&memcg->id.ref); +atomic_add (n, &memcg->id.ref); }static struct mem_cgroup *mem_cgroup_id_get_online (struct mem_cgroup *memcg)
@@ -4054,9 +4054,9 @@ static struct mem_cgroup *mem_cgroup_id_get_online(struct mem_cgroup *memcg)return memcg; } -static void mem_cgroup_id_put (struct mem_cgroup *memcg) +static void mem_cgroup_id_put_many (struct mem_cgroup *memcg,unsigned int n) { -if (atomic_dec_and_test (&memcg->id.ref)) { +if (atomic_sub_and_test (n, &memcg->id.ref)) {idr_remove (&mem_cgroup_idr, memcg->id.id); memcg->id.id =0 ;
@@ -4065,6 +4065,16 @@ static void mem_cgroup_id_put(struct mem_cgroup *memcg) } } +static inline void mem_cgroup_id_get (struct mem_cgroup *memcg) +{ +mem_cgroup_id_get_many (memcg,1 ); +} + +static inline void mem_cgroup_id_put (struct mem_cgroup *memcg) +{ +mem_cgroup_id_put_many (memcg,1 ); +} +/** * mem_cgroup_from_id - look up a memcg from a memcg id * @id: the memcg id to look up
@@ -4699,6 +4709,8 @@ static void __mem_cgroup_clear_mc(void)if (!mem_cgroup_is_root (mc.from))page_counter_uncharge (&mc.from->memsw, mc.moved_swap); +mem_cgroup_id_put_many (mc.from, mc.moved_swap); +/* * we charged both to->memory and to->memsw, so we * should uncharge to->memory.
@@ -4706,9 +4718,9 @@ static void __mem_cgroup_clear_mc(void)if (!mem_cgroup_is_root (mc.to))page_counter_uncharge (&mc.to->memory, mc.moved_swap); -css_put_many (&mc.from->css, mc.moved_swap); +mem_cgroup_id_get_many (mc.to, mc.moved_swap); +css_put_many (&mc.to->css, mc.moved_swap); -/* we've already done css_get(mc.to) */ mc.moved_swap =0 ; }memcg_oom_recover (from);
Signature · 7 lines
2.1.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 v3 3/3 mm: memcontrol: add sanity checks for memcg->id.ref on get/put
In reply to Vladimir Davydov---
Changes in v3:
- move memcg->id.ref initialization to mem_cgroup_css_online to make the code easier to follow (Johannes)
mm/memcontrol.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
mm/memcontrol.c +5 -1
@@ -4033,6 +4033,7 @@ static DEFINE_IDR(mem_cgroup_idr);static void mem_cgroup_id_get_many (struct mem_cgroup *memcg,unsigned int n) { +VM_BUG_ON (atomic_read (&memcg->id.ref) <=0 );atomic_add (n, &memcg->id.ref); }
@@ -4056,6 +4057,7 @@ static struct mem_cgroup *mem_cgroup_id_get_online(struct mem_cgroup *memcg)static void mem_cgroup_id_put_many (struct mem_cgroup *memcg,unsigned int n) { +VM_BUG_ON (atomic_read (&memcg->id.ref) < n);if (atomic_sub_and_test (n, &memcg->id.ref)) {idr_remove (&mem_cgroup_idr, memcg->id.id); memcg->id.id =0 ;
@@ -4244,8 +4246,10 @@ fail:static int mem_cgroup_css_online (struct cgroup_subsys_state *css) { +struct mem_cgroup *memcg =mem_cgroup_from_css (css); +/* Online state pins memcg ID, memcg ID pins CSS */ -mem_cgroup_id_get (mem_cgroup_from_css (css)); +atomic_set (&memcg->id.ref,1 );css_get (css);return 0 ; }
Signature · 7 lines
2.1.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>
Re: [PATCH v3 3/3] mm: memcontrol: add sanity checks for memcg->id.ref on get/put
Quoted 2 lines · Vladimir Davydov
Signed-off-by Vladimir Davydov <[email protected]>Acked-by Michal Hocko <[email protected]>
--
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>
Quoted 10 lines · Vladimir Davydov
An offline memory cgroup might have anonymous memory or shmem left charged to it and no swap. Since only swap entries pin the id of an offline cgroup, such a cgroup will have no id and so an attempt to swapout its anon/shmem will not store memory cgroup info in the swap cgroup map. As a result,
memcg->swapormemcg->memswwill never get uncharged from it and any of its ascendants.Fix this by always charging swapout to the first ancestor cgroup that hasn't released its id yet.
Quoted 6 lines
[[email protected]: add comment to mem_cgroup_swapout]Fixes 73f576c04b941 ("mm: memcontrol: fix cgroup creation failure after many small jobs")Signed-off-by Vladimir Davydov <[email protected]>Acked-by Johannes Weiner <[email protected]>Acked-by Michal Hocko <[email protected]>Cc <[email protected]> [3.19+]
Andrew, could you please fold this in?
mm/memcontrol.c +1 -1
@@ -4044,7 +4044,7 @@ static struct mem_cgroup *mem_cgroup_id_get_online(struct mem_cgroup *memcg)* The root cgroup cannot be destroyed, so it's refcount must * always be >= 1. */ -if (memcg == root_mem_cgroup) { +if (WARN_ON_ONCE (memcg == root_mem_cgroup)) {VM_BUG_ON (1 );break ; }
--
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>