mm: introduce memory.min
Subsystems (7)
PATCH v2 1/1 mm: introduce memory.min
Memory controller implements the memory.low best-effort memory protection mechanism, which works perfectly in many cases and allows protecting working sets of important workloads from sudden reclaim.
But its semantics has a significant limitation: it works only as long as there is a supply of reclaimable memory. This makes it pretty useless against any sort of slow memory leaks or memory usage increases. This is especially true for swapless systems. If swap is enabled, memory soft protection effectively postpones problems, allowing a leaking application to fill all swap area, which makes no sense.
The only effective way to guarantee the memory protection in this case is to invoke the OOM killer.
It's possible to handle this case in userspace by reacting on MEMCG_LOW events; but there is still a place for a fail-safe in-kernel mechanism to provide stronger guarantees.
This patch introduces the memory.min interface for cgroup v2 memory controller. It works very similarly to memory.low (sharing the same hierarchical behavior), except that it's not disabled if there is no more reclaimable memory in the system.
--- Documentation/cgroup-v2.txt | 24 ++++++++- include/linux/memcontrol.h | 15 ++++-- include/linux/page_counter.h | 11 +++- mm/memcontrol.c | 118 ++++++++++++++++++++++++++++++++++--------- mm/page_counter.c | 63 ++++++++++++++++------- mm/vmscan.c | 18 ++++++- 6 files changed, 199 insertions(+), 50 deletions(-)
Documentation/cgroup-v2.txt +22 -2
@@ -1002,6 +1002,26 @@ PAGE_SIZE multiple when read back. The total amount of memory currently being used by the cgroup and its descendants. + memory.min + A read-write single value file which exists on non-root + cgroups. The default is "0". + + Hard memory protection. If the memory usage of a cgroup + is within its effective min boundary, the cgroup's memory + won't be reclaimed under any conditions. If there is no + unprotected reclaimable memory available, OOM killer + is invoked. + + Effective low boundary is limited by memory.min values of + all ancestor cgroups. If there is memory.min overcommitment + (child cgroup or cgroups are requiring more protected memory + than parent will allow), then each child cgroup will get + the part of parent's protection proportional to its + actual memory usage below memory.min. + + Putting more memory than generally available under this + protection is discouraged and may lead to constant OOMs. + memory.low A read-write single value file which exists on non-root cgroups. The default is "0".
@@ -1013,9 +1033,9 @@ PAGE_SIZE multiple when read back. Effective low boundary is limited by memory.low values of all ancestor cgroups. If there is memory.low overcommitment - (child cgroup or cgroups are requiring more protected memory, + (child cgroup or cgroups are requiring more protected memory than parent will allow), then each child cgroup will get - the part of parent's protection proportional to the its + the part of parent's protection proportional to its actual memory usage below memory.low. Putting more memory than generally available under this
include/linux/memcontrol.h +11 -4
@@ -59,6 +59,12 @@ enum memcg_memory_event { MEMCG_NR_MEMORY_EVENTS, }; +enum mem_cgroup_protection { +MEMCG_PROT_NONE , +MEMCG_PROT_LOW , +MEMCG_PROT_HIGH , +}; +struct mem_cgroup_reclaim_cookie {pg_data_t *pgdat;int priority;
@@ -297,7 +303,8 @@ static inline bool mem_cgroup_disabled(void)return !cgroup_subsys_enabled (memory_cgrp_subsys); } -bool mem_cgroup_low (struct mem_cgroup *root,struct mem_cgroup *memcg); +enum mem_cgroup_protection mem_cgroup_protected (struct mem_cgroup *root, +struct mem_cgroup *memcg); int mem_cgroup_try_charge(struct page *page, struct mm_struct *mm, gfp_t gfp_mask, struct mem_cgroup **memcgp,
@@ -756,10 +763,10 @@ static inline void memcg_memory_event(struct mem_cgroup *memcg, { } -static inline bool mem_cgroup_low (struct mem_cgroup *root, -struct mem_cgroup *memcg) +static inline enum mem_cgroup_protection mem_cgroup_protected ( +struct mem_cgroup *root,struct mem_cgroup *memcg) { -return false; +return MEMCG_PROT_NONE ; } static inline int mem_cgroup_try_charge(struct page *page, struct mm_struct *mm,
include/linux/page_counter.h +9 -2
@@ -8,10 +8,16 @@struct page_counter {atomic_long_t usage; -unsigned long max; +unsigned long min;unsigned long low; +unsigned long max;struct page_counter *parent; +/* effective memory.min and memory.min usage tracking */ +unsigned long emin; +atomic_long_t min_usage; +atomic_long_t children_min_usage; +/* effective memory.low and memory.low usage tracking */ unsigned long elow;atomic_long_t low_usage;
@@ -47,8 +53,9 @@ bool page_counter_try_charge(struct page_counter *counter,unsigned long nr_pages, struct page_counter **fail);void page_counter_uncharge (struct page_counter *counter,unsigned long nr_pages); -int page_counter_set_max (struct page_counter *counter,unsigned long nr_pages); +void page_counter_set_min (struct page_counter *counter,unsigned long nr_pages);void page_counter_set_low (struct page_counter *counter,unsigned long nr_pages); +int page_counter_set_max (struct page_counter *counter,unsigned long nr_pages);int page_counter_memparse (const char *buf,const char *max,unsigned long *nr_pages);
mm/memcontrol.c +94 -24
188 lines not on this page · raw message
mm/page_counter.c +46 -17
113 lines not on this page · raw message
mm/vmscan.c +17 -1
@@ -2544,12 +2544,28 @@ static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)unsigned long reclaimed;unsigned long scanned; -if (mem_cgroup_low (root, memcg)) { +switch (mem_cgroup_protected (root, memcg)) { +case MEMCG_PROT_HIGH : +/* +* Hard protection. +* If there is no reclaimable memory, OOM. +*/ +continue ; +case MEMCG_PROT_LOW : +/* +* Soft protection. +* Respect the protection only as long as +* there is an unprotected supply +* of reclaimable memory from other cgroups. +*/ if (!sc->memcg_low_reclaim) { sc->memcg_low_skipped =1 ;continue ; }memcg_memory_event (memcg,MEMCG_LOW ); +break ; +case MEMCG_PROT_NONE : +break ; } reclaimed = sc->nr_reclaimed;
Signature · 1 line
2.14.3
Hi Roman,
Documentation/cgroup-v2.txt PAGE_SIZE multiple when read back. · line 1013
Quoted 9 lines · Roman Gushchin
Documentation/cgroup-v2.txt +9
-0+ memory.min + A read-write single value file which exists on non-root + cgroups. The default is "0". + + Hard memory protection. If the memory usage of a cgroup + is within its effective min boundary, the cgroup's memory + won't be reclaimed under any conditions. If there is no + unprotected reclaimable memory available, OOM killer + is invoked.
What will happen if all tasks attached to a cgroup are killed by OOM, but its memory usage is still within memory.min? Will memory.min be ignored then?
Quoted 10 lines
Documentation/cgroup-v2.txt +10
-0+ + Effective low boundary is limited by memory.min values of + all ancestor cgroups. If there is memory.min overcommitment + (child cgroup or cgroups are requiring more protected memory + than parent will allow), then each child cgroup will get + the part of parent's protection proportional to its + actual memory usage below memory.min. + + Putting more memory than generally available under this + protection is discouraged and may lead to constant OOMs.
Hi Vladimir!
Quoted 6 lines · Roman Gushchin
Quoted 5 lines
(quoted diff) +5
-0+ memory.min + A read-write single value file which exists on non-root + cgroups. The default is "0". + + Hard memory protection. If the memory usage of a cgroup
Quoted 8 lines
Review
Quoted 4 lines
(quoted diff) +4
-0+ is within its effective min boundary, the cgroup's memory + won't be reclaimed under any conditions. If there is no + unprotected reclaimable memory available, OOM killer + is invoked.What will happen if all tasks attached to a cgroup are killed by OOM, but its memory usage is still within memory.min? Will memory.min be ignored then?
Not really.
I don't think it's a big problem as long as a user isn't doing something weird (e.g. moving processes with significant amount of charged memory to other cgroups).
But what we can do here, is to ignore memory.min of empty cgroups (patch below), it will resolve some edge cases like this.
Thanks!
--------------------------------------------------------------------------------
From 54a4f4fc9f8a6847c8a5814c53f0b575010808e5 Mon Sep 17 00:00:00 2001
From: Roman Gushchin <[email protected]>
Date: Tue, 24 Apr 2018 14:44:14 +0100
Subject: [PATCH] mm: ignore memory.min of abandoned memory cgroups
If a cgroup has no associated tasks, invoking the OOM killer won't help release any memory, so respecting the memory.min can lead to an infinite OOM loop or system stall.
Let's ignore memory.min of unpopulated cgroups.
--- mm/vmscan.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
mm/vmscan.c +4 -1
@@ -2549,8 +2549,11 @@ static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)/* * Hard protection. * If there is no reclaimable memory, OOM. +* Abandoned cgroups are loosing protection, +* because OOM killer won't release any memory. */ -continue ; +if (cgroup_is_populated (memcg->css.cgroup)) +continue ;case MEMCG_PROT_LOW :/* * Soft protection.
Signature · 1 line
2.14.3
Quoted 15 lines · Roman Gushchin
Quoted 6 lines · Roman Gushchin
Quoted 5 lines
(quoted diff) +5
-0+ memory.min + A read-write single value file which exists on non-root + cgroups. The default is "0". + + Hard memory protection. If the memory usage of a cgroupQuoted 8 lines
Review
Quoted 4 lines
(quoted diff) +4
-0+ is within its effective min boundary, the cgroup's memory + won't be reclaimed under any conditions. If there is no + unprotected reclaimable memory available, OOM killer + is invoked.What will happen if all tasks attached to a cgroup are killed by OOM, but its memory usage is still within memory.min? Will memory.min be ignored then?
Quoted 5 lines
Not really.
I don't think it's a big problem as long as a user isn't doing something weird (e.g. moving processes with significant amount of charged memory to other cgroups).
The user doesn't have to do anything weird for this to happen - just read a file. This will allocate and charge page cache pages that are not mapped to any process and hence cannot be freed by OOM killer.
Quoted 3 lines
But what we can do here, is to ignore memory.min of empty cgroups (patch below), it will resolve some edge cases like this.
Makes sense to me.
Thanks,
Vladimir
Quoted 6 lines · Vladimir Davydov
Quoted 3 lines · Roman Gushchin
But what we can do here, is to ignore memory.min of empty cgroups (patch below), it will resolve some edge cases like this.
Makes sense to me.
Ok, let's keep it as a fallback mechanism.
Thank you!
Quoted 9 lines · Roman Gushchin
From: Roman Gushchin <[email protected]>
Date: Tue, 24 Apr 2018 14:44:14 +0100
Subject: [PATCH] mm: ignore memory.min of abandoned memory cgroupsIf a cgroup has no associated tasks, invoking the OOM killer won't help release any memory, so respecting the memory.min can lead to an infinite OOM loop or system stall.
Let's ignore memory.min of unpopulated cgroups.
Good point, this makes sense.
Quoted 5 lines
Signed-off-by Roman Gushchin <[email protected]>Cc Johannes Weiner <[email protected]>Cc Michal Hocko <[email protected]>Cc Vladimir Davydov <[email protected]>Cc Tejun Heo <[email protected]>
mm/vmscan.c static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc) · line 2552
Quoted 5 lines
mm/vmscan.c +1
-0@@ -2549,8 +2549,11 @@ static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)/* * Hard protection. * If there is no reclaimable memory, OOM. +* Abandoned cgroups are loosing protection,
losing
include/linux/memcontrol.h enum memcg_memory_event { · line 65
Quoted 8 lines · Roman Gushchin
include/linux/memcontrol.h +4
-0@@ -59,6 +59,12 @@ enum memcg_memory_event { MEMCG_NR_MEMORY_EVENTS, }; +enum mem_cgroup_protection { +MEMCG_PROT_NONE , +MEMCG_PROT_LOW , +MEMCG_PROT_HIGH ,
Ha, HIGH doesn't make much sense, but I went back and it's indeed what I suggested. Must have been a brainfart. This should be
MEMCG_PROT_NONE,MEMCG_PROT_LOW,MEMCG_PROT_MIN
right? To indicate which type of protection is applying.
The rest of the patch looks good:
Quoted 13 lines · Johannes Weiner
include/linux/memcontrol.h enum memcg_memory_event { · line 65
Quoted 8 lines · Roman Gushchin
include/linux/memcontrol.h +4
-0@@ -59,6 +59,12 @@ enum memcg_memory_event { MEMCG_NR_MEMORY_EVENTS, }; +enum mem_cgroup_protection { +MEMCG_PROT_NONE , +MEMCG_PROT_LOW , +MEMCG_PROT_HIGH ,Ha, HIGH doesn't make much sense, but I went back and it's indeed what I suggested. Must have been a brainfart. This should be
Quoted 5 lines
MEMCG_PROT_NONE,MEMCG_PROT_LOW,MEMCG_PROT_MINright? To indicate which type of protection is applying.
Hm, I wasn't actually sure if it was a typo or not :)
But I thought that MEMCG_PROT_HIGH means a higher level of protection than MEMCG_PROT_LOW, which sounds reasonable.
So, I'm fine with either option.
Quoted 5 lines
The rest of the patch looks good:
Acked-by Johannes Weiner <[email protected]>
Thanks!
Can you, also, please, take a look at this one:
https://lkml.org/lkml/2018/4/24/703.
Thank you!