From 1364cbc2a9dd9557bf9296465fe24dc8f3236e23 Mon Sep 17 00:00:00 2001 From: Michal Hocko Date: Sat, 17 May 2014 23:19:25 +1000 Subject: [PATCH] memcg, mm: introduce lowlimit reclaim Previous discussions have shown that soft limits cannot be reformed (http://lwn.net/Articles/555249/). This series introduces an alternative approach for protecting memory allocated to processes executing within a memory cgroup controller. It is based on a new tunable that was discussed with Johannes and Tejun held during the kernel summit 2013 and at LSF 2014. This patchset introduces such low limit that is functionally similar to a minimum guarantee. Memcgs which are under their lowlimit are not considered eligible for the reclaim (both global and hardlimit) unless all groups under the reclaimed hierarchy are below the low limit when all of them are considered eligible. The previous version of the patchset posted as a RFC (http://marc.info/?l=linux-mm&m=138677140628677&w=2) suggested a hard guarantee without any fallback. More discussions led me to reconsidering the default behavior and come up a more relaxed one. The hard requirement can be added later based on a use case which really requires. It would be controlled by memory.reclaim_flags knob which would specify whether to OOM or fallback (default) when all groups are bellow low limit. The default value of the limit is 0 so all groups are eligible by default and an interested party has to explicitly set the limit. The primary use case is to protect an amount of memory allocated to a workload without it being reclaimed by an unrelated activity. In some cases this requirement can be fulfilled by mlock but it is not suitable for many loads and generally requires application awareness. Such application awareness can be complex. It effectively forbids the use of memory overcommit as the application must explicitly manage memory residency. With the low limit, such workloads can be placed in a memcg with a low limit that protects the estimated working set. The hierarchical behavior of the lowlimit is described in the first patch. The second patch allows setting the lowlimit. The last 2 patches clarify documentation about the memcg reclaim in gereneral (3rd patch) and low limit (4th patch). This patch (of 4) This patch introduces low limit reclaim. The low_limit acts as a reclaim protection because groups which are under their low_limit are considered ineligible for reclaim. While hardlimit protects from using more memory than allowed lowlimit protects from getting below memory assigned to the group due to external memory pressure. More precisely a group is considered eligible for the reclaim under a specific hierarchy represented by its root only if the group is above its low limit and the same applies to all parents up the hierarchy to the root. Nevertheless the limit still might be ignored if all groups under the reclaimed hierarchy are under their low limits. This will prevent from OOM rather than protecting the memory. Consider the following hierarchy with memory pressure coming from the group A (hard limit reclaim - l-low_limit_in_bytes, u-usage_in_bytes, h-limit_in_bytes): root_mem_cgroup . _____/ / A (l = 80 u=90 h=90) / / \_________ / \ B (l=0 u=50) C (l=50 u=40) \ D (l=0 u=30) A and B are reclaimable but C and D are not (D is protected by C). The low_limit is 0 by default so every group is eligible. This patch doesn't provide a way to set the limit yet although the core infrastructure is there already. Signed-off-by: Michal Hocko Cc: Johannes Weiner Cc: KAMEZAWA Hiroyuki Cc: KOSAKI Motohiro Cc: Greg Thelen Cc: Michel Lespinasse Cc: Tejun Heo Cc: Hugh Dickins Cc: Roman Gushchin Signed-off-by: Andrew Morton --- include/linux/memcontrol.h | 9 +++++++++ include/linux/res_counter.h | 27 +++++++++++++++++++++++++++ mm/memcontrol.c | 23 +++++++++++++++++++++++ mm/vmscan.c | 34 +++++++++++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index eb65d29516ca..bfd54264e73a 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -92,6 +92,9 @@ bool __mem_cgroup_same_or_subtree(const struct mem_cgroup *root_memcg, bool task_in_mem_cgroup(struct task_struct *task, const struct mem_cgroup *memcg); +extern bool mem_cgroup_reclaim_eligible(struct mem_cgroup *memcg, + struct mem_cgroup *root); + extern struct mem_cgroup *try_get_mem_cgroup_from_page(struct page *page); extern struct mem_cgroup *mem_cgroup_from_task(struct task_struct *p); @@ -288,6 +291,12 @@ static inline struct lruvec *mem_cgroup_page_lruvec(struct page *page, return &zone->lruvec; } +static inline bool mem_cgroup_reclaim_eligible(struct mem_cgroup *memcg, + struct mem_cgroup *root) +{ + return true; +} + static inline struct mem_cgroup *try_get_mem_cgroup_from_page(struct page *page) { return NULL; diff --git a/include/linux/res_counter.h b/include/linux/res_counter.h index 56b7bc32db4f..408724eeec71 100644 --- a/include/linux/res_counter.h +++ b/include/linux/res_counter.h @@ -39,6 +39,11 @@ struct res_counter { * the limit that usage can be exceed */ unsigned long long soft_limit; + /* + * the limit under which the usage cannot be pushed + * due to external pressure. + */ + unsigned long long low_limit; /* * the number of unsuccessful attempts to consume the resource */ @@ -175,6 +180,28 @@ res_counter_soft_limit_excess(struct res_counter *cnt) return excess; } +/** + * Get the difference between the usage and the low limit + * @cnt: The counter + * + * Returns 0 if usage is less than or equal to low limit + * The difference between usage and low limit, otherwise. + */ +static inline unsigned long long +res_counter_low_limit_excess(struct res_counter *cnt) +{ + unsigned long long excess; + unsigned long flags; + + spin_lock_irqsave(&cnt->lock, flags); + if (cnt->usage <= cnt->low_limit) + excess = 0; + else + excess = cnt->usage - cnt->low_limit; + spin_unlock_irqrestore(&cnt->lock, flags); + return excess; +} + static inline void res_counter_reset_max(struct res_counter *cnt) { unsigned long flags; diff --git a/mm/memcontrol.c b/mm/memcontrol.c index d27e85742ac9..434ea328c9a3 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -2787,6 +2787,29 @@ static struct mem_cgroup *mem_cgroup_lookup(unsigned short id) return mem_cgroup_from_id(id); } +/** + * mem_cgroup_reclaim_eligible - checks whether given memcg is eligible for the + * reclaim + * @memcg: target memcg for the reclaim + * @root: root of the reclaim hierarchy (null for the global reclaim) + * + * The given group is reclaimable if it is above its low limit and the same + * applies for all parents up the hierarchy until root (including). + */ +bool mem_cgroup_reclaim_eligible(struct mem_cgroup *memcg, + struct mem_cgroup *root) +{ + do { + if (!res_counter_low_limit_excess(&memcg->res)) + return false; + if (memcg == root) + break; + + } while ((memcg = parent_mem_cgroup(memcg))); + + return true; +} + struct mem_cgroup *try_get_mem_cgroup_from_page(struct page *page) { struct mem_cgroup *memcg = NULL; diff --git a/mm/vmscan.c b/mm/vmscan.c index 4bfbb6bae913..6db301f3dd5d 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -2233,9 +2233,11 @@ static inline bool should_continue_reclaim(struct zone *zone, } } -static void shrink_zone(struct zone *zone, struct scan_control *sc) +static unsigned __shrink_zone(struct zone *zone, struct scan_control *sc, + bool follow_low_limit) { unsigned long nr_reclaimed, nr_scanned; + unsigned nr_scanned_groups = 0; do { struct mem_cgroup *root = sc->target_mem_cgroup; @@ -2252,7 +2254,23 @@ static void shrink_zone(struct zone *zone, struct scan_control *sc) do { struct lruvec *lruvec; + /* + * Memcg might be under its low limit so we have to + * skip it during the first reclaim round + */ + if (follow_low_limit && + !mem_cgroup_reclaim_eligible(memcg, root)) { + /* + * It would be more optimal to skip the memcg + * subtree now but we do not have a memcg iter + * helper for that. Anyone? + */ + memcg = mem_cgroup_iter(root, memcg, &reclaim); + continue; + } + lruvec = mem_cgroup_zone_lruvec(zone, memcg); + nr_scanned_groups++; shrink_lruvec(lruvec, sc); @@ -2280,6 +2298,20 @@ static void shrink_zone(struct zone *zone, struct scan_control *sc) } while (should_continue_reclaim(zone, sc->nr_reclaimed - nr_reclaimed, sc->nr_scanned - nr_scanned, sc)); + + return nr_scanned_groups; +} + +static void shrink_zone(struct zone *zone, struct scan_control *sc) +{ + if (!__shrink_zone(zone, sc, true)) { + /* + * First round of reclaim didn't find anything to reclaim + * because of low limit protection so try again and ignore + * the low limit this time. + */ + __shrink_zone(zone, sc, false); + } } /* Returns true if compaction should go ahead for a high-order request */ -- 2.39.5