memcg: fix oom kill behavior
In current page-fault code,
handle_mm_fault()
-> ...
-> mem_cgroup_charge()
-> map page or handle error.
-> check return code.
If page fault's return code is VM_FAULT_OOM, page_fault_out_of_memory() is
called. But if it's caused by memcg, OOM should have been already
invoked.
Then, I added a patch: a636b327f7
. That
patch records last_oom_jiffies for memcg's sub-hierarchy and prevents
page_fault_out_of_memory from being invoked in near future.
But Nishimura-san reported that check by jiffies is not enough when the
system is terribly heavy.
This patch changes memcg's oom logic as.
* If memcg causes OOM-kill, continue to retry.
* remove jiffies check which is used now.
* add memcg-oom-lock which works like perzone oom lock.
* If current is killed(as a process), bypass charge.
Something more sophisticated can be added but this pactch does
fundamental things.
TODO:
- add oom notifier
- add permemcg disable-oom-kill flag and freezer at oom.
- more chances for wake up oom waiter (when changing memory limit etc..)
Reviewed-by: Daisuke Nishimura <nishimura@mxp.nes.nec.co.jp>
Tested-by: Daisuke Nishimura <nishimura@mxp.nes.nec.co.jp>
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Cc: Balbir Singh <balbir@in.ibm.com>
Cc: David Rientjes <rientjes@google.com>
Signed-off-by: Daisuke Nishimura <nishimura@mxp.nes.nec.co.jp>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
0263c12c12
commit
867578cbcc
@ -124,7 +124,6 @@ static inline bool mem_cgroup_disabled(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
extern bool mem_cgroup_oom_called(struct task_struct *task);
|
||||
void mem_cgroup_update_file_mapped(struct page *page, int val);
|
||||
unsigned long mem_cgroup_soft_limit_reclaim(struct zone *zone, int order,
|
||||
gfp_t gfp_mask, int nid,
|
||||
@ -258,11 +257,6 @@ static inline bool mem_cgroup_disabled(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool mem_cgroup_oom_called(struct task_struct *task)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int
|
||||
mem_cgroup_inactive_anon_is_low(struct mem_cgroup *memcg)
|
||||
{
|
||||
|
134
mm/memcontrol.c
134
mm/memcontrol.c
@ -203,7 +203,7 @@ struct mem_cgroup {
|
||||
* Should the accounting and control be hierarchical, per subtree?
|
||||
*/
|
||||
bool use_hierarchy;
|
||||
unsigned long last_oom_jiffies;
|
||||
atomic_t oom_lock;
|
||||
atomic_t refcnt;
|
||||
|
||||
unsigned int swappiness;
|
||||
@ -1246,32 +1246,102 @@ static int mem_cgroup_hierarchical_reclaim(struct mem_cgroup *root_mem,
|
||||
return total;
|
||||
}
|
||||
|
||||
bool mem_cgroup_oom_called(struct task_struct *task)
|
||||
static int mem_cgroup_oom_lock_cb(struct mem_cgroup *mem, void *data)
|
||||
{
|
||||
bool ret = false;
|
||||
struct mem_cgroup *mem;
|
||||
struct mm_struct *mm;
|
||||
int *val = (int *)data;
|
||||
int x;
|
||||
/*
|
||||
* Logically, we can stop scanning immediately when we find
|
||||
* a memcg is already locked. But condidering unlock ops and
|
||||
* creation/removal of memcg, scan-all is simple operation.
|
||||
*/
|
||||
x = atomic_inc_return(&mem->oom_lock);
|
||||
*val = max(x, *val);
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* Check OOM-Killer is already running under our hierarchy.
|
||||
* If someone is running, return false.
|
||||
*/
|
||||
static bool mem_cgroup_oom_lock(struct mem_cgroup *mem)
|
||||
{
|
||||
int lock_count = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
mm = task->mm;
|
||||
if (!mm)
|
||||
mm = &init_mm;
|
||||
mem = mem_cgroup_from_task(rcu_dereference(mm->owner));
|
||||
if (mem && time_before(jiffies, mem->last_oom_jiffies + HZ/10))
|
||||
ret = true;
|
||||
rcu_read_unlock();
|
||||
return ret;
|
||||
mem_cgroup_walk_tree(mem, &lock_count, mem_cgroup_oom_lock_cb);
|
||||
|
||||
if (lock_count == 1)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int record_last_oom_cb(struct mem_cgroup *mem, void *data)
|
||||
static int mem_cgroup_oom_unlock_cb(struct mem_cgroup *mem, void *data)
|
||||
{
|
||||
mem->last_oom_jiffies = jiffies;
|
||||
/*
|
||||
* When a new child is created while the hierarchy is under oom,
|
||||
* mem_cgroup_oom_lock() may not be called. We have to use
|
||||
* atomic_add_unless() here.
|
||||
*/
|
||||
atomic_add_unless(&mem->oom_lock, -1, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void record_last_oom(struct mem_cgroup *mem)
|
||||
static void mem_cgroup_oom_unlock(struct mem_cgroup *mem)
|
||||
{
|
||||
mem_cgroup_walk_tree(mem, NULL, record_last_oom_cb);
|
||||
mem_cgroup_walk_tree(mem, NULL, mem_cgroup_oom_unlock_cb);
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(memcg_oom_mutex);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(memcg_oom_waitq);
|
||||
|
||||
/*
|
||||
* try to call OOM killer. returns false if we should exit memory-reclaim loop.
|
||||
*/
|
||||
bool mem_cgroup_handle_oom(struct mem_cgroup *mem, gfp_t mask)
|
||||
{
|
||||
DEFINE_WAIT(wait);
|
||||
bool locked;
|
||||
|
||||
/* At first, try to OOM lock hierarchy under mem.*/
|
||||
mutex_lock(&memcg_oom_mutex);
|
||||
locked = mem_cgroup_oom_lock(mem);
|
||||
/*
|
||||
* Even if signal_pending(), we can't quit charge() loop without
|
||||
* accounting. So, UNINTERRUPTIBLE is appropriate. But SIGKILL
|
||||
* under OOM is always welcomed, use TASK_KILLABLE here.
|
||||
*/
|
||||
if (!locked)
|
||||
prepare_to_wait(&memcg_oom_waitq, &wait, TASK_KILLABLE);
|
||||
mutex_unlock(&memcg_oom_mutex);
|
||||
|
||||
if (locked)
|
||||
mem_cgroup_out_of_memory(mem, mask);
|
||||
else {
|
||||
schedule();
|
||||
finish_wait(&memcg_oom_waitq, &wait);
|
||||
}
|
||||
mutex_lock(&memcg_oom_mutex);
|
||||
mem_cgroup_oom_unlock(mem);
|
||||
/*
|
||||
* Here, we use global waitq .....more fine grained waitq ?
|
||||
* Assume following hierarchy.
|
||||
* A/
|
||||
* 01
|
||||
* 02
|
||||
* assume OOM happens both in A and 01 at the same time. Tthey are
|
||||
* mutually exclusive by lock. (kill in 01 helps A.)
|
||||
* When we use per memcg waitq, we have to wake up waiters on A and 02
|
||||
* in addtion to waiters on 01. We use global waitq for avoiding mess.
|
||||
* It will not be a big problem.
|
||||
* (And a task may be moved to other groups while it's waiting for OOM.)
|
||||
*/
|
||||
wake_up_all(&memcg_oom_waitq);
|
||||
mutex_unlock(&memcg_oom_mutex);
|
||||
|
||||
if (test_thread_flag(TIF_MEMDIE) || fatal_signal_pending(current))
|
||||
return false;
|
||||
/* Give chance to dying process */
|
||||
schedule_timeout(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1443,11 +1513,14 @@ static int __mem_cgroup_try_charge(struct mm_struct *mm,
|
||||
struct res_counter *fail_res;
|
||||
int csize = CHARGE_SIZE;
|
||||
|
||||
if (unlikely(test_thread_flag(TIF_MEMDIE))) {
|
||||
/* Don't account this! */
|
||||
*memcg = NULL;
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* Unlike gloval-vm's OOM-kill, we're not in memory shortage
|
||||
* in system level. So, allow to go ahead dying process in addition to
|
||||
* MEMDIE process.
|
||||
*/
|
||||
if (unlikely(test_thread_flag(TIF_MEMDIE)
|
||||
|| fatal_signal_pending(current)))
|
||||
goto bypass;
|
||||
|
||||
/*
|
||||
* We always charge the cgroup the mm_struct belongs to.
|
||||
@ -1560,11 +1633,15 @@ static int __mem_cgroup_try_charge(struct mm_struct *mm,
|
||||
}
|
||||
|
||||
if (!nr_retries--) {
|
||||
if (oom) {
|
||||
mem_cgroup_out_of_memory(mem_over_limit, gfp_mask);
|
||||
record_last_oom(mem_over_limit);
|
||||
if (!oom)
|
||||
goto nomem;
|
||||
if (mem_cgroup_handle_oom(mem_over_limit, gfp_mask)) {
|
||||
nr_retries = MEM_CGROUP_RECLAIM_RETRIES;
|
||||
continue;
|
||||
}
|
||||
goto nomem;
|
||||
/* When we reach here, current task is dying .*/
|
||||
css_put(&mem->css);
|
||||
goto bypass;
|
||||
}
|
||||
}
|
||||
if (csize > PAGE_SIZE)
|
||||
@ -1574,6 +1651,9 @@ done:
|
||||
nomem:
|
||||
css_put(&mem->css);
|
||||
return -ENOMEM;
|
||||
bypass:
|
||||
*memcg = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -603,13 +603,6 @@ void pagefault_out_of_memory(void)
|
||||
/* Got some memory back in the last second. */
|
||||
return;
|
||||
|
||||
/*
|
||||
* If this is from memcg, oom-killer is already invoked.
|
||||
* and not worth to go system-wide-oom.
|
||||
*/
|
||||
if (mem_cgroup_oom_called(current))
|
||||
goto rest_and_return;
|
||||
|
||||
if (sysctl_panic_on_oom)
|
||||
panic("out of memory from page fault. panic_on_oom is selected.\n");
|
||||
|
||||
@ -621,7 +614,6 @@ void pagefault_out_of_memory(void)
|
||||
* Give "p" a good chance of killing itself before we
|
||||
* retry to allocate memory.
|
||||
*/
|
||||
rest_and_return:
|
||||
if (!test_thread_flag(TIF_MEMDIE))
|
||||
schedule_timeout_uninterruptible(1);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user