Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1407546 > unrolled thread
| Started by | Michal Hocko <mhocko@kernel.org> |
|---|---|
| First post | 2016-05-26 14:50 +0200 |
| Last post | 2016-05-27 13:10 +0200 |
| Articles | 6 — 2 participants |
Back to article view | Back to linux.kernel
This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by
below is the oldest one visible, not the original post.
[PATCH 6/6] mm, oom: fortify task_will_free_mem Michal Hocko <mhocko@kernel.org> - 2016-05-26 14:50 +0200
Re: [PATCH 6/6] mm, oom: fortify task_will_free_mem Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> - 2016-05-26 16:20 +0200
Re: [PATCH 6/6] mm, oom: fortify task_will_free_mem Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> - 2016-05-26 16:50 +0200
Re: [PATCH 6/6] mm, oom: fortify task_will_free_mem Michal Hocko <mhocko@kernel.org> - 2016-05-26 17:00 +0200
Re: [PATCH 6/6] mm, oom: fortify task_will_free_mem Michal Hocko <mhocko@kernel.org> - 2016-05-26 16:50 +0200
Re: [PATCH 6/6] mm, oom: fortify task_will_free_mem Michal Hocko <mhocko@kernel.org> - 2016-05-27 13:10 +0200
| From | Michal Hocko <mhocko@kernel.org> |
|---|---|
| Date | 2016-05-26 14:50 +0200 |
| Subject | [PATCH 6/6] mm, oom: fortify task_will_free_mem |
| Message-ID | <rD2TE-6uy-17@gated-at.bofh.it> |
From: Michal Hocko <mhocko@suse.com>
task_will_free_mem is rather weak. It doesn't really tell whether
the task has chance to drop its mm. 98748bd72200 ("oom: consider
multi-threaded tasks in task_will_free_mem") made a first step
into making it more robust for multi-threaded applications so now we
know that the whole process is going down. This builds on top for more
complex scenarios where mm is shared between different processes
(CLONE_VM without CLONE_THREAD resp CLONE_SIGHAND).
Make sure that all processes sharing the mm are killed or exiting. This
will allow us to replace try_oom_reaper by wake_oom_reaper. Therefore
all paths which bypass the oom killer are now reapable and so they
shouldn't lock up the oom killer.
Drop the mm checks for the bypass because those are not really
guaranteeing anything as the condition might change at any time
after task_lock is dropped.
Signed-off-by: Michal Hocko <mhocko@suse.com>
---
include/linux/oom.h | 72 +++++++++++++++++++++++++++++++++++++++++++++++++----
mm/memcontrol.c | 4 +--
mm/oom_kill.c | 65 ++++-------------------------------------------
3 files changed, 74 insertions(+), 67 deletions(-)
diff --git a/include/linux/oom.h b/include/linux/oom.h
index 83469522690a..412c4ecb42b1 100644
--- a/include/linux/oom.h
+++ b/include/linux/oom.h
@@ -70,9 +70,9 @@ static inline bool oom_task_origin(const struct task_struct *p)
extern void mark_oom_victim(struct task_struct *tsk);
#ifdef CONFIG_MMU
-extern void try_oom_reaper(struct task_struct *tsk);
+extern void wake_oom_reaper(struct task_struct *tsk);
#else
-static inline void try_oom_reaper(struct task_struct *tsk)
+static inline void wake_oom_reaper(struct task_struct *tsk)
{
}
#endif
@@ -105,7 +105,7 @@ extern void oom_killer_enable(void);
extern struct task_struct *find_lock_task_mm(struct task_struct *p);
-static inline bool task_will_free_mem(struct task_struct *task)
+static inline bool __task_will_free_mem(struct task_struct *task)
{
struct signal_struct *sig = task->signal;
@@ -117,13 +117,75 @@ static inline bool task_will_free_mem(struct task_struct *task)
if (sig->flags & SIGNAL_GROUP_COREDUMP)
return false;
- if (!(task->flags & PF_EXITING))
+ if (!(task->flags & PF_EXITING || fatal_signal_pending(task)))
return false;
/* Make sure that the whole thread group is going down */
- if (!thread_group_empty(task) && !(sig->flags & SIGNAL_GROUP_EXIT))
+ if (!thread_group_empty(task) &&
+ !(sig->flags & SIGNAL_GROUP_EXIT || fatal_signal_pending(task)))
+ return false;
+
+ return true;
+}
+
+/*
+ * Checks whether the given task is dying or exiting and likely to
+ * release its address space. This means that all threads and processes
+ * sharing the same mm have to be killed or exiting.
+ */
+static inline bool task_will_free_mem(struct task_struct *task)
+{
+ struct mm_struct *mm = NULL;
+ struct task_struct *p;
+
+ /*
+ * If the process has passed exit_mm we have to skip it because
+ * we have lost a link to other tasks sharing this mm, we do not
+ * have anything to reap and the task might then get stuck waiting
+ * for parent as zombie and we do not want it to hold TIF_MEMDIE
+ */
+ p = find_lock_task_mm(task);
+ if (!p)
return false;
+ if (!__task_will_free_mem(p)) {
+ task_unlock(p);
+ return false;
+ }
+
+ /*
+ * Check whether there are other processes sharing the mm - they all have
+ * to be killed or exiting.
+ */
+ if (atomic_read(&p->mm->mm_users) > get_nr_threads(p)) {
+ mm = p->mm;
+ /* pin the mm to not get freed and reused */
+ atomic_inc(&mm->mm_count);
+ }
+ task_unlock(p);
+
+ if (mm) {
+ rcu_read_lock();
+ for_each_process(p) {
+ bool vfork;
+
+ /*
+ * skip over vforked tasks because they are mostly
+ * independent and will drop the mm soon
+ */
+ task_lock(p);
+ vfork = p->vfork_done;
+ task_unlock(p);
+ if (vfork)
+ continue;
+
+ if (!__task_will_free_mem(p))
+ break;
+ }
+ rcu_read_unlock();
+ mmdrop(mm);
+ }
+
return true;
}
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index f6477a9dbe7a..878a4308164c 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1273,9 +1273,9 @@ static bool mem_cgroup_out_of_memory(struct mem_cgroup *memcg, gfp_t gfp_mask,
* select it. The goal is to allow it to allocate so that it may
* quickly exit and free its memory.
*/
- if (fatal_signal_pending(current) || task_will_free_mem(current)) {
+ if (task_will_free_mem(current)) {
mark_oom_victim(current);
- try_oom_reaper(current);
+ wake_oom_reaper(current);
goto unlock;
}
diff --git a/mm/oom_kill.c b/mm/oom_kill.c
index 008c5b4732de..428e34df9f49 100644
--- a/mm/oom_kill.c
+++ b/mm/oom_kill.c
@@ -573,7 +573,7 @@ static int oom_reaper(void *unused)
return 0;
}
-static void wake_oom_reaper(struct task_struct *tsk)
+void wake_oom_reaper(struct task_struct *tsk)
{
if (!oom_reaper_th)
return;
@@ -594,50 +594,6 @@ static void wake_oom_reaper(struct task_struct *tsk)
/* Check if we can reap the given task. This has to be called with stable
* tsk->mm
*/
-void try_oom_reaper(struct task_struct *tsk)
-{
- struct mm_struct *mm = tsk->mm;
- struct task_struct *p;
-
- if (!mm)
- return;
-
- /*
- * There might be other threads/processes which are either not
- * dying or even not killable.
- */
- if (atomic_read(&mm->mm_users) > 1) {
- rcu_read_lock();
- for_each_process(p) {
- bool exiting;
-
- if (!process_shares_mm(p, mm))
- continue;
- if (same_thread_group(p, tsk))
- continue;
- if (fatal_signal_pending(p))
- continue;
-
- /*
- * If the task is exiting make sure the whole thread group
- * is exiting and cannot acces mm anymore.
- */
- spin_lock_irq(&p->sighand->siglock);
- exiting = signal_group_exit(p->signal);
- spin_unlock_irq(&p->sighand->siglock);
- if (exiting)
- continue;
-
- /* Give up */
- rcu_read_unlock();
- return;
- }
- rcu_read_unlock();
- }
-
- wake_oom_reaper(tsk);
-}
-
static int __init oom_init(void)
{
oom_reaper_th = kthread_run(oom_reaper, NULL, "oom_reaper");
@@ -649,10 +605,6 @@ static int __init oom_init(void)
return 0;
}
subsys_initcall(oom_init)
-#else
-static void wake_oom_reaper(struct task_struct *tsk)
-{
-}
#endif
/**
@@ -750,15 +702,12 @@ void oom_kill_process(struct oom_control *oc, struct task_struct *p,
* If the task is already exiting, don't alarm the sysadmin or kill
* its children or threads, just set TIF_MEMDIE so it can die quickly
*/
- task_lock(p);
- if (p->mm && task_will_free_mem(p)) {
+ if (task_will_free_mem(p)) {
mark_oom_victim(p);
- try_oom_reaper(p);
- task_unlock(p);
+ wake_oom_reaper(p);
put_task_struct(p);
return;
}
- task_unlock(p);
if (__ratelimit(&oom_rs))
dump_header(oc, p, memcg);
@@ -945,14 +894,10 @@ bool out_of_memory(struct oom_control *oc)
* If current has a pending SIGKILL or is exiting, then automatically
* select it. The goal is to allow it to allocate so that it may
* quickly exit and free its memory.
- *
- * But don't select if current has already released its mm and cleared
- * TIF_MEMDIE flag at exit_mm(), otherwise an OOM livelock may occur.
*/
- if (current->mm &&
- (fatal_signal_pending(current) || task_will_free_mem(current))) {
+ if (task_will_free_mem(current)) {
mark_oom_victim(current);
- try_oom_reaper(current);
+ wake_oom_reaper(current);
return true;
}
--
2.8.1
[toc] | [next] | [standalone]
| From | Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> |
|---|---|
| Date | 2016-05-26 16:20 +0200 |
| Message-ID | <rD4iK-7u2-17@gated-at.bofh.it> |
| In reply to | #1407546 |
Michal Hocko wrote:
> +/*
> + * Checks whether the given task is dying or exiting and likely to
> + * release its address space. This means that all threads and processes
> + * sharing the same mm have to be killed or exiting.
> + */
> +static inline bool task_will_free_mem(struct task_struct *task)
> +{
> + struct mm_struct *mm = NULL;
> + struct task_struct *p;
> +
> + /*
> + * If the process has passed exit_mm we have to skip it because
> + * we have lost a link to other tasks sharing this mm, we do not
> + * have anything to reap and the task might then get stuck waiting
> + * for parent as zombie and we do not want it to hold TIF_MEMDIE
> + */
> + p = find_lock_task_mm(task);
> + if (!p)
> return false;
>
> + if (!__task_will_free_mem(p)) {
> + task_unlock(p);
> + return false;
> + }
> +
> + /*
> + * Check whether there are other processes sharing the mm - they all have
> + * to be killed or exiting.
> + */
> + if (atomic_read(&p->mm->mm_users) > get_nr_threads(p)) {
> + mm = p->mm;
> + /* pin the mm to not get freed and reused */
> + atomic_inc(&mm->mm_count);
> + }
> + task_unlock(p);
> +
> + if (mm) {
> + rcu_read_lock();
> + for_each_process(p) {
> + bool vfork;
> +
> + /*
> + * skip over vforked tasks because they are mostly
> + * independent and will drop the mm soon
> + */
> + task_lock(p);
> + vfork = p->vfork_done;
> + task_unlock(p);
> + if (vfork)
> + continue;
> +
> + if (!__task_will_free_mem(p))
> + break;
> + }
> + rcu_read_unlock();
> + mmdrop(mm);
Did you forget "if (something) return false;" here?
> + }
> +
If task_will_free_mem() == true is always followed by mark_oom_victim()
and wake_oom_reaper(), can we call them from here?
> return true;
> }
[toc] | [prev] | [next] | [standalone]
| From | Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> |
|---|---|
| Date | 2016-05-26 16:50 +0200 |
| Message-ID | <rD4LL-7DF-1@gated-at.bofh.it> |
| In reply to | #1407573 |
Michal Hocko wrote:
> +/*
> + * Checks whether the given task is dying or exiting and likely to
> + * release its address space. This means that all threads and processes
> + * sharing the same mm have to be killed or exiting.
> + */
> +static inline bool task_will_free_mem(struct task_struct *task)
> +{
> + struct mm_struct *mm = NULL;
> + struct task_struct *p;
> + bool ret = false;
If atomic_read(&p->mm->mm_users) <= get_nr_threads(p), this returns "false".
According to previous version, I think this is "bool ret = true;".
> +
> + /*
> + * If the process has passed exit_mm we have to skip it because
> + * we have lost a link to other tasks sharing this mm, we do not
> + * have anything to reap and the task might then get stuck waiting
> + * for parent as zombie and we do not want it to hold TIF_MEMDIE
> + */
> + p = find_lock_task_mm(task);
> + if (!p)
> + return false;
> +
> + if (!__task_will_free_mem(p)) {
> + task_unlock(p);
> + return false;
> + }
> +
> + /*
> + * Check whether there are other processes sharing the mm - they all have
> + * to be killed or exiting.
> + */
> + if (atomic_read(&p->mm->mm_users) > get_nr_threads(p)) {
> + mm = p->mm;
> + /* pin the mm to not get freed and reused */
> + atomic_inc(&mm->mm_count);
> + }
> + task_unlock(p);
> +
> + if (mm) {
> + rcu_read_lock();
> + for_each_process(p) {
> + bool vfork;
> +
> + /*
> + * skip over vforked tasks because they are mostly
> + * independent and will drop the mm soon
> + */
> + task_lock(p);
> + vfork = p->vfork_done;
> + task_unlock(p);
> + if (vfork)
> + continue;
> +
> + ret = __task_will_free_mem(p);
> + if (!ret)
> + break;
> + }
> + rcu_read_unlock();
> + mmdrop(mm);
> + }
> +
> + return ret;
> +}
[toc] | [prev] | [next] | [standalone]
| From | Michal Hocko <mhocko@kernel.org> |
|---|---|
| Date | 2016-05-26 17:00 +0200 |
| Message-ID | <rD4Vs-7H3-9@gated-at.bofh.it> |
| In reply to | #1407588 |
On Thu 26-05-16 23:41:54, Tetsuo Handa wrote:
> Michal Hocko wrote:
> > +/*
> > + * Checks whether the given task is dying or exiting and likely to
> > + * release its address space. This means that all threads and processes
> > + * sharing the same mm have to be killed or exiting.
> > + */
> > +static inline bool task_will_free_mem(struct task_struct *task)
> > +{
> > + struct mm_struct *mm = NULL;
> > + struct task_struct *p;
> > + bool ret = false;
>
> If atomic_read(&p->mm->mm_users) <= get_nr_threads(p), this returns "false".
> According to previous version, I think this is "bool ret = true;".
true. Thanks for catching this. Fixed locally.
--
Michal Hocko
SUSE Labs
[toc] | [prev] | [next] | [standalone]
| From | Michal Hocko <mhocko@kernel.org> |
|---|---|
| Date | 2016-05-26 16:50 +0200 |
| Message-ID | <rD4LL-7DF-3@gated-at.bofh.it> |
| In reply to | #1407573 |
On Thu 26-05-16 23:11:47, Tetsuo Handa wrote:
[...]
> > + if (mm) {
> > + rcu_read_lock();
> > + for_each_process(p) {
> > + bool vfork;
> > +
> > + /*
> > + * skip over vforked tasks because they are mostly
> > + * independent and will drop the mm soon
> > + */
> > + task_lock(p);
> > + vfork = p->vfork_done;
> > + task_unlock(p);
> > + if (vfork)
> > + continue;
> > +
> > + if (!__task_will_free_mem(p))
> > + break;
> > + }
> > + rcu_read_unlock();
> > + mmdrop(mm);
>
> Did you forget "if (something) return false;" here?
Dohh, I screwed when cleaning up the code before submission. Thanks for
catching that.
>
> > + }
> > +
>
> If task_will_free_mem() == true is always followed by mark_oom_victim()
> and wake_oom_reaper(), can we call them from here?
I would rather keep the function doing only the check. We never know
when this could be reused somewhere else. All the callsites are quite
trivial so we do not duplicate a lot of code.
---
From 0b94b4d6f749f322279e911e00abf1390fea4b2b Mon Sep 17 00:00:00 2001
From: Michal Hocko <mhocko@suse.com>
Date: Thu, 26 May 2016 09:38:32 +0200
Subject: [PATCH] mm, oom: fortify task_will_free_mem
task_will_free_mem is rather weak. It doesn't really tell whether
the task has chance to drop its mm. 98748bd72200 ("oom: consider
multi-threaded tasks in task_will_free_mem") made a first step
into making it more robust for multi-threaded applications so now we
know that the whole process is going down. This builds on top for more
complex scenarios where mm is shared between different processes
(CLONE_VM without CLONE_THREAD resp CLONE_SIGHAND).
Make sure that all processes sharing the mm are killed or exiting. This
will allow us to replace try_oom_reaper by wake_oom_reaper. Therefore
all paths which bypass the oom killer are now reapable and so they
shouldn't lock up the oom killer.
Drop the mm checks for the bypass because those are not really
guaranteeing anything as the condition might change at any time
after task_lock is dropped.
Signed-off-by: Michal Hocko <mhocko@suse.com>
---
include/linux/oom.h | 74 +++++++++++++++++++++++++++++++++++++++++++++++++----
mm/memcontrol.c | 4 +--
mm/oom_kill.c | 65 ++++------------------------------------------
3 files changed, 76 insertions(+), 67 deletions(-)
diff --git a/include/linux/oom.h b/include/linux/oom.h
index 83469522690a..91d90a5885dc 100644
--- a/include/linux/oom.h
+++ b/include/linux/oom.h
@@ -70,9 +70,9 @@ static inline bool oom_task_origin(const struct task_struct *p)
extern void mark_oom_victim(struct task_struct *tsk);
#ifdef CONFIG_MMU
-extern void try_oom_reaper(struct task_struct *tsk);
+extern void wake_oom_reaper(struct task_struct *tsk);
#else
-static inline void try_oom_reaper(struct task_struct *tsk)
+static inline void wake_oom_reaper(struct task_struct *tsk)
{
}
#endif
@@ -105,7 +105,7 @@ extern void oom_killer_enable(void);
extern struct task_struct *find_lock_task_mm(struct task_struct *p);
-static inline bool task_will_free_mem(struct task_struct *task)
+static inline bool __task_will_free_mem(struct task_struct *task)
{
struct signal_struct *sig = task->signal;
@@ -117,16 +117,80 @@ static inline bool task_will_free_mem(struct task_struct *task)
if (sig->flags & SIGNAL_GROUP_COREDUMP)
return false;
- if (!(task->flags & PF_EXITING))
+ if (!(task->flags & PF_EXITING || fatal_signal_pending(task)))
return false;
/* Make sure that the whole thread group is going down */
- if (!thread_group_empty(task) && !(sig->flags & SIGNAL_GROUP_EXIT))
+ if (!thread_group_empty(task) &&
+ !(sig->flags & SIGNAL_GROUP_EXIT || fatal_signal_pending(task)))
return false;
return true;
}
+/*
+ * Checks whether the given task is dying or exiting and likely to
+ * release its address space. This means that all threads and processes
+ * sharing the same mm have to be killed or exiting.
+ */
+static inline bool task_will_free_mem(struct task_struct *task)
+{
+ struct mm_struct *mm = NULL;
+ struct task_struct *p;
+ bool ret = false;
+
+ /*
+ * If the process has passed exit_mm we have to skip it because
+ * we have lost a link to other tasks sharing this mm, we do not
+ * have anything to reap and the task might then get stuck waiting
+ * for parent as zombie and we do not want it to hold TIF_MEMDIE
+ */
+ p = find_lock_task_mm(task);
+ if (!p)
+ return false;
+
+ if (!__task_will_free_mem(p)) {
+ task_unlock(p);
+ return false;
+ }
+
+ /*
+ * Check whether there are other processes sharing the mm - they all have
+ * to be killed or exiting.
+ */
+ if (atomic_read(&p->mm->mm_users) > get_nr_threads(p)) {
+ mm = p->mm;
+ /* pin the mm to not get freed and reused */
+ atomic_inc(&mm->mm_count);
+ }
+ task_unlock(p);
+
+ if (mm) {
+ rcu_read_lock();
+ for_each_process(p) {
+ bool vfork;
+
+ /*
+ * skip over vforked tasks because they are mostly
+ * independent and will drop the mm soon
+ */
+ task_lock(p);
+ vfork = p->vfork_done;
+ task_unlock(p);
+ if (vfork)
+ continue;
+
+ ret = __task_will_free_mem(p);
+ if (!ret)
+ break;
+ }
+ rcu_read_unlock();
+ mmdrop(mm);
+ }
+
+ return ret;
+}
+
/* sysctls */
extern int sysctl_oom_dump_tasks;
extern int sysctl_oom_kill_allocating_task;
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index f6477a9dbe7a..878a4308164c 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1273,9 +1273,9 @@ static bool mem_cgroup_out_of_memory(struct mem_cgroup *memcg, gfp_t gfp_mask,
* select it. The goal is to allow it to allocate so that it may
* quickly exit and free its memory.
*/
- if (fatal_signal_pending(current) || task_will_free_mem(current)) {
+ if (task_will_free_mem(current)) {
mark_oom_victim(current);
- try_oom_reaper(current);
+ wake_oom_reaper(current);
goto unlock;
}
diff --git a/mm/oom_kill.c b/mm/oom_kill.c
index 008c5b4732de..428e34df9f49 100644
--- a/mm/oom_kill.c
+++ b/mm/oom_kill.c
@@ -573,7 +573,7 @@ static int oom_reaper(void *unused)
return 0;
}
-static void wake_oom_reaper(struct task_struct *tsk)
+void wake_oom_reaper(struct task_struct *tsk)
{
if (!oom_reaper_th)
return;
@@ -594,50 +594,6 @@ static void wake_oom_reaper(struct task_struct *tsk)
/* Check if we can reap the given task. This has to be called with stable
* tsk->mm
*/
-void try_oom_reaper(struct task_struct *tsk)
-{
- struct mm_struct *mm = tsk->mm;
- struct task_struct *p;
-
- if (!mm)
- return;
-
- /*
- * There might be other threads/processes which are either not
- * dying or even not killable.
- */
- if (atomic_read(&mm->mm_users) > 1) {
- rcu_read_lock();
- for_each_process(p) {
- bool exiting;
-
- if (!process_shares_mm(p, mm))
- continue;
- if (same_thread_group(p, tsk))
- continue;
- if (fatal_signal_pending(p))
- continue;
-
- /*
- * If the task is exiting make sure the whole thread group
- * is exiting and cannot acces mm anymore.
- */
- spin_lock_irq(&p->sighand->siglock);
- exiting = signal_group_exit(p->signal);
- spin_unlock_irq(&p->sighand->siglock);
- if (exiting)
- continue;
-
- /* Give up */
- rcu_read_unlock();
- return;
- }
- rcu_read_unlock();
- }
-
- wake_oom_reaper(tsk);
-}
-
static int __init oom_init(void)
{
oom_reaper_th = kthread_run(oom_reaper, NULL, "oom_reaper");
@@ -649,10 +605,6 @@ static int __init oom_init(void)
return 0;
}
subsys_initcall(oom_init)
-#else
-static void wake_oom_reaper(struct task_struct *tsk)
-{
-}
#endif
/**
@@ -750,15 +702,12 @@ void oom_kill_process(struct oom_control *oc, struct task_struct *p,
* If the task is already exiting, don't alarm the sysadmin or kill
* its children or threads, just set TIF_MEMDIE so it can die quickly
*/
- task_lock(p);
- if (p->mm && task_will_free_mem(p)) {
+ if (task_will_free_mem(p)) {
mark_oom_victim(p);
- try_oom_reaper(p);
- task_unlock(p);
+ wake_oom_reaper(p);
put_task_struct(p);
return;
}
- task_unlock(p);
if (__ratelimit(&oom_rs))
dump_header(oc, p, memcg);
@@ -945,14 +894,10 @@ bool out_of_memory(struct oom_control *oc)
* If current has a pending SIGKILL or is exiting, then automatically
* select it. The goal is to allow it to allocate so that it may
* quickly exit and free its memory.
- *
- * But don't select if current has already released its mm and cleared
- * TIF_MEMDIE flag at exit_mm(), otherwise an OOM livelock may occur.
*/
- if (current->mm &&
- (fatal_signal_pending(current) || task_will_free_mem(current))) {
+ if (task_will_free_mem(current)) {
mark_oom_victim(current);
- try_oom_reaper(current);
+ wake_oom_reaper(current);
return true;
}
--
2.8.1
--
Michal Hocko
SUSE Labs
[toc] | [prev] | [next] | [standalone]
| From | Michal Hocko <mhocko@kernel.org> |
|---|---|
| Date | 2016-05-27 13:10 +0200 |
| Message-ID | <rDnOq-2G0-3@gated-at.bofh.it> |
| In reply to | #1407546 |
I have updated the patch to not rely on the mm_users check because it is
not reliable as pointed by Tetsuo and we really want this function to be
reliable. I do not have a good and reliable way to check for existence
of external users sharing the mm so we are checking the whole list
unconditionally if mm_users > 1. This should be acceptable because we
are doing this only in the slow path of task_will_free_mem. We can
surely make this more optimum later.
---
From 24149128304fcd90cb656abbfaef91e734db242a Mon Sep 17 00:00:00 2001
From: Michal Hocko <mhocko@suse.com>
Date: Thu, 26 May 2016 09:38:32 +0200
Subject: [PATCH] mm, oom: fortify task_will_free_mem
task_will_free_mem is rather weak. It doesn't really tell whether
the task has chance to drop its mm. 98748bd72200 ("oom: consider
multi-threaded tasks in task_will_free_mem") made a first step
into making it more robust for multi-threaded applications so now we
know that the whole process is going down and probably drop the mm.
This patch builds on top for more complex scenarios where mm is shared
between different processes - CLONE_VM without CLONE_THREAD resp
CLONE_SIGHAND, or in kernel use_mm().
Make sure that all processes sharing the mm are killed or exiting. This
will allow us to replace try_oom_reaper by wake_oom_reaper. Therefore
all paths which bypass the oom killer are now reapable and so they
shouldn't lock up the oom killer.
Signed-off-by: Michal Hocko <mhocko@suse.com>
---
include/linux/oom.h | 75 +++++++++++++++++++++++++++++++++++++++++++++++++----
mm/memcontrol.c | 4 +--
mm/oom_kill.c | 66 ++++------------------------------------------
3 files changed, 77 insertions(+), 68 deletions(-)
diff --git a/include/linux/oom.h b/include/linux/oom.h
index 83469522690a..4acafc201e78 100644
--- a/include/linux/oom.h
+++ b/include/linux/oom.h
@@ -70,9 +70,9 @@ static inline bool oom_task_origin(const struct task_struct *p)
extern void mark_oom_victim(struct task_struct *tsk);
#ifdef CONFIG_MMU
-extern void try_oom_reaper(struct task_struct *tsk);
+extern void wake_oom_reaper(struct task_struct *tsk);
#else
-static inline void try_oom_reaper(struct task_struct *tsk)
+static inline void wake_oom_reaper(struct task_struct *tsk)
{
}
#endif
@@ -105,7 +105,7 @@ extern void oom_killer_enable(void);
extern struct task_struct *find_lock_task_mm(struct task_struct *p);
-static inline bool task_will_free_mem(struct task_struct *task)
+static inline bool __task_will_free_mem(struct task_struct *task)
{
struct signal_struct *sig = task->signal;
@@ -117,16 +117,81 @@ static inline bool task_will_free_mem(struct task_struct *task)
if (sig->flags & SIGNAL_GROUP_COREDUMP)
return false;
- if (!(task->flags & PF_EXITING))
+ if (!(task->flags & PF_EXITING || fatal_signal_pending(task)))
return false;
/* Make sure that the whole thread group is going down */
- if (!thread_group_empty(task) && !(sig->flags & SIGNAL_GROUP_EXIT))
+ if (!thread_group_empty(task) &&
+ !(sig->flags & SIGNAL_GROUP_EXIT || fatal_signal_pending(task)))
return false;
return true;
}
+/*
+ * Checks whether the given task is dying or exiting and likely to
+ * release its address space. This means that all threads and processes
+ * sharing the same mm have to be killed or exiting.
+ */
+static inline bool task_will_free_mem(struct task_struct *task)
+{
+ struct mm_struct *mm = NULL;
+ struct task_struct *p;
+ bool ret;
+
+ /*
+ * If the process has passed exit_mm we have to skip it because
+ * we have lost a link to other tasks sharing this mm, we do not
+ * have anything to reap and the task might then get stuck waiting
+ * for parent as zombie and we do not want it to hold TIF_MEMDIE
+ */
+ p = find_lock_task_mm(task);
+ if (!p)
+ return false;
+
+ if (!__task_will_free_mem(p)) {
+ task_unlock(p);
+ return false;
+ }
+
+ mm = p->mm;
+ if (atomic_read(&mm->mm_users) <= 1) {
+ task_unlock(p);
+ return true;
+ }
+
+ /* pin the mm to not get freed and reused */
+ atomic_inc(&mm->mm_count);
+ task_unlock(p);
+
+ /*
+ * This is really pessimistic but we do not have any reliable way
+ * to check that external processes share with our mm
+ */
+ rcu_read_lock();
+ for_each_process(p) {
+ bool vfork;
+
+ /*
+ * skip over vforked tasks because they are mostly
+ * independent and will drop the mm soon
+ */
+ task_lock(p);
+ vfork = p->vfork_done;
+ task_unlock(p);
+ if (vfork)
+ continue;
+
+ ret = __task_will_free_mem(p);
+ if (!ret)
+ break;
+ }
+ rcu_read_unlock();
+ mmdrop(mm);
+
+ return ret;
+}
+
/* sysctls */
extern int sysctl_oom_dump_tasks;
extern int sysctl_oom_kill_allocating_task;
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 37ba604984c9..1353920e065e 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1275,9 +1275,9 @@ static bool mem_cgroup_out_of_memory(struct mem_cgroup *memcg, gfp_t gfp_mask,
* select it. The goal is to allow it to allocate so that it may
* quickly exit and free its memory.
*/
- if (fatal_signal_pending(current) || task_will_free_mem(current)) {
+ if (task_will_free_mem(current)) {
mark_oom_victim(current);
- try_oom_reaper(current);
+ wake_oom_reaper(current);
goto unlock;
}
diff --git a/mm/oom_kill.c b/mm/oom_kill.c
index d48ec58cf4d2..bcb6d3b26c94 100644
--- a/mm/oom_kill.c
+++ b/mm/oom_kill.c
@@ -590,7 +590,7 @@ static int oom_reaper(void *unused)
return 0;
}
-static void wake_oom_reaper(struct task_struct *tsk)
+void wake_oom_reaper(struct task_struct *tsk)
{
if (!oom_reaper_th)
return;
@@ -608,51 +608,6 @@ static void wake_oom_reaper(struct task_struct *tsk)
wake_up(&oom_reaper_wait);
}
-/* Check if we can reap the given task. This has to be called with stable
- * tsk->mm
- */
-void try_oom_reaper(struct task_struct *tsk)
-{
- struct mm_struct *mm = tsk->mm;
- struct task_struct *p;
-
- if (!mm)
- return;
-
- /*
- * There might be other threads/processes which are either not
- * dying or even not killable.
- */
- if (atomic_read(&mm->mm_users) > 1) {
- rcu_read_lock();
- for_each_process(p) {
- bool exiting;
-
- if (!process_shares_mm(p, mm))
- continue;
- if (fatal_signal_pending(p))
- continue;
-
- /*
- * If the task is exiting make sure the whole thread group
- * is exiting and cannot acces mm anymore.
- */
- spin_lock_irq(&p->sighand->siglock);
- exiting = signal_group_exit(p->signal);
- spin_unlock_irq(&p->sighand->siglock);
- if (exiting)
- continue;
-
- /* Give up */
- rcu_read_unlock();
- return;
- }
- rcu_read_unlock();
- }
-
- wake_oom_reaper(tsk);
-}
-
static int __init oom_init(void)
{
oom_reaper_th = kthread_run(oom_reaper, NULL, "oom_reaper");
@@ -664,10 +619,6 @@ static int __init oom_init(void)
return 0;
}
subsys_initcall(oom_init)
-#else
-static void wake_oom_reaper(struct task_struct *tsk)
-{
-}
#endif
/**
@@ -765,15 +716,12 @@ void oom_kill_process(struct oom_control *oc, struct task_struct *p,
* If the task is already exiting, don't alarm the sysadmin or kill
* its children or threads, just set TIF_MEMDIE so it can die quickly
*/
- task_lock(p);
- if (p->mm && task_will_free_mem(p)) {
+ if (task_will_free_mem(p)) {
mark_oom_victim(p);
- try_oom_reaper(p);
- task_unlock(p);
+ wake_oom_reaper(p);
put_task_struct(p);
return;
}
- task_unlock(p);
if (__ratelimit(&oom_rs))
dump_header(oc, p, memcg);
@@ -952,14 +900,10 @@ bool out_of_memory(struct oom_control *oc)
* If current has a pending SIGKILL or is exiting, then automatically
* select it. The goal is to allow it to allocate so that it may
* quickly exit and free its memory.
- *
- * But don't select if current has already released its mm and cleared
- * TIF_MEMDIE flag at exit_mm(), otherwise an OOM livelock may occur.
*/
- if (current->mm &&
- (fatal_signal_pending(current) || task_will_free_mem(current))) {
+ if (task_will_free_mem(current)) {
mark_oom_victim(current);
- try_oom_reaper(current);
+ wake_oom_reaper(current);
return true;
}
--
2.8.1
--
Michal Hocko
SUSE Labs
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web