oom: kill all threads sharing oom killed task's mm
David Rientjes [Tue, 26 Oct 2010 21:21:24 +0000 (14:21 -0700)]
It's necessary to kill all threads that share an oom killed task's mm if
the goal is to lead to future memory freeing.

This patch reintroduces the code removed in 8c5cd6f3 (oom: oom_kill
doesn't kill vfork parent (or child)) since it is obsoleted.

It's now guaranteed that any task passed to oom_kill_task() does not share
an mm with any thread that is unkillable.  Thus, we're safe to issue a
SIGKILL to any thread sharing the same mm.

This is especially necessary to solve an mm->mmap_sem livelock issue
whereas an oom killed thread must acquire the lock in the exit path while
another thread is holding it in the page allocator while trying to
allocate memory itself (and will preempt the oom killer since a task was
already killed).  Since tasks with pending fatal signals are now granted
access to memory reserves, the thread holding the lock may quickly
allocate and release the lock so that the oom killed task may exit.

This mainly is for threads that are cloned with CLONE_VM but not
CLONE_THREAD, so they are in a different thread group.  Non-NPTL threads
exist in the wild and this change is necessary to prevent the livelock in
such cases.  We care more about preventing the livelock than incurring the
additional tasklist in the oom killer when a task has been killed.
Systems that are sufficiently large to not want the tasklist scan in the
oom killer in the first place already have the option of enabling
/proc/sys/vm/oom_kill_allocating_task, which was designed specifically for
that purpose.

This code had existed in the oom killer for over eight years dating back
to the 2.4 kernel.

[akpm@linux-foundation.org: add nice comment]
Signed-off-by: David Rientjes <rientjes@google.com>
Acked-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Cc: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Ying Han <yinghan@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

mm/oom_kill.c

index 4395f37..7dcca55 100644 (file)
@@ -404,16 +404,40 @@ static void dump_header(struct task_struct *p, gfp_t gfp_mask, int order,
 #define K(x) ((x) << (PAGE_SHIFT-10))
 static int oom_kill_task(struct task_struct *p, struct mem_cgroup *mem)
 {
+       struct task_struct *q;
+       struct mm_struct *mm;
+
        p = find_lock_task_mm(p);
        if (!p)
                return 1;
 
+       /* mm cannot be safely dereferenced after task_unlock(p) */
+       mm = p->mm;
+
        pr_err("Killed process %d (%s) total-vm:%lukB, anon-rss:%lukB, file-rss:%lukB\n",
                task_pid_nr(p), p->comm, K(p->mm->total_vm),
                K(get_mm_counter(p->mm, MM_ANONPAGES)),
                K(get_mm_counter(p->mm, MM_FILEPAGES)));
        task_unlock(p);
 
+       /*
+        * Kill all processes sharing p->mm in other thread groups, if any.
+        * They don't get access to memory reserves or a higher scheduler
+        * priority, though, to avoid depletion of all memory or task
+        * starvation.  This prevents mm->mmap_sem livelock when an oom killed
+        * task cannot exit because it requires the semaphore and its contended
+        * by another thread trying to allocate memory itself.  That thread will
+        * now get access to memory reserves since it has a pending fatal
+        * signal.
+        */
+       for_each_process(q)
+               if (q->mm == mm && !same_thread_group(q, p)) {
+                       task_lock(q);   /* Protect ->comm from prctl() */
+                       pr_err("Kill process %d (%s) sharing same memory\n",
+                               task_pid_nr(q), q->comm);
+                       task_unlock(q);
+                       force_sig(SIGKILL, q);
+               }
 
        set_tsk_thread_flag(p, TIF_MEMDIE);
        force_sig(SIGKILL, p);