summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHugh Dickins <hugh.dickins@tiscali.co.uk>2009-09-21 17:02:20 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2009-09-22 07:17:32 -0700
commit9ba6929480088a85c1ff60a4b1f1c9fc80dbd2b7 (patch)
tree39aab8cdffae598b55e35c578f70820712286ab4
parentcd551f97519d35855be5a8720a47cc802ee4fd06 (diff)
downloadop-kernel-dev-9ba6929480088a85c1ff60a4b1f1c9fc80dbd2b7.zip
op-kernel-dev-9ba6929480088a85c1ff60a4b1f1c9fc80dbd2b7.tar.gz
ksm: fix oom deadlock
There's a now-obvious deadlock in KSM's out-of-memory handling: imagine ksmd or KSM_RUN_UNMERGE handling, holding ksm_thread_mutex, trying to allocate a page to break KSM in an mm which becomes the OOM victim (quite likely in the unmerge case): it's killed and goes to exit, and hangs there waiting to acquire ksm_thread_mutex. Clearly we must not require ksm_thread_mutex in __ksm_exit, simple though that made everything else: perhaps use mmap_sem somehow? And part of the answer lies in the comments on unmerge_ksm_pages: __ksm_exit should also leave all the rmap_item removal to ksmd. But there's a fundamental problem, that KSM relies upon mmap_sem to guarantee the consistency of the mm it's dealing with, yet exit_mmap tears down an mm without taking mmap_sem. And bumping mm_users won't help at all, that just ensures that the pages the OOM killer assumes are on their way to being freed will not be freed. The best answer seems to be, to move the ksm_exit callout from just before exit_mmap, to the middle of exit_mmap: after the mm's pages have been freed (if the mmu_gather is flushed), but before its page tables and vma structures have been freed; and down_write,up_write mmap_sem there to serialize with KSM's own reliance on mmap_sem. But KSM then needs to be careful, whenever it downs mmap_sem, to check that the mm is not already exiting: there's a danger of using find_vma on a layout that's being torn apart, or writing into page tables which have been freed for reuse; and even do_anonymous_page and __do_fault need to check they're not being called by break_ksm to reinstate a pte after zap_pte_range has zapped that page table. Though it might be clearer to add an exiting flag, set while holding mmap_sem in __ksm_exit, that wouldn't cover the issue of reinstating a zapped pte. All we need is to check whether mm_users is 0 - but must remember that ksmd may detect that before __ksm_exit is reached. So, ksm_test_exit(mm) added to comment such checks on mm->mm_users. __ksm_exit now has to leave clearing up the rmap_items to ksmd, that needs ksm_thread_mutex; but shift the exiting mm just after the ksm_scan cursor so that it will soon be dealt with. __ksm_enter raise mm_count to hold the mm_struct, ksmd's exit processing (exactly like its processing when it finds all VM_MERGEABLEs unmapped) mmdrop it, similar procedure for KSM_RUN_UNMERGE (which has stopped ksmd). But also give __ksm_exit a fast path: when there's no complication (no rmap_items attached to mm and it's not at the ksm_scan cursor), it can safely do all the exiting work itself. This is not just an optimization: when ksmd is not running, the raised mm_count would otherwise leak mm_structs. Signed-off-by: Hugh Dickins <hugh.dickins@tiscali.co.uk> Acked-by: Izik Eidus <ieidus@redhat.com> Cc: Andrea Arcangeli <aarcange@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--include/linux/ksm.h31
-rw-r--r--kernel/fork.c1
-rw-r--r--mm/ksm.c144
-rw-r--r--mm/memory.c5
-rw-r--r--mm/mmap.c9
5 files changed, 137 insertions, 53 deletions
diff --git a/include/linux/ksm.h b/include/linux/ksm.h
index a485c14..2d64ff3 100644
--- a/include/linux/ksm.h
+++ b/include/linux/ksm.h
@@ -12,11 +12,14 @@
#include <linux/sched.h>
#include <linux/vmstat.h>
+struct mmu_gather;
+
#ifdef CONFIG_KSM
int ksm_madvise(struct vm_area_struct *vma, unsigned long start,
unsigned long end, int advice, unsigned long *vm_flags);
int __ksm_enter(struct mm_struct *mm);
-void __ksm_exit(struct mm_struct *mm);
+void __ksm_exit(struct mm_struct *mm,
+ struct mmu_gather **tlbp, unsigned long end);
static inline int ksm_fork(struct mm_struct *mm, struct mm_struct *oldmm)
{
@@ -25,10 +28,24 @@ static inline int ksm_fork(struct mm_struct *mm, struct mm_struct *oldmm)
return 0;
}
-static inline void ksm_exit(struct mm_struct *mm)
+/*
+ * For KSM to handle OOM without deadlock when it's breaking COW in a
+ * likely victim of the OOM killer, exit_mmap() has to serialize with
+ * ksm_exit() after freeing mm's pages but before freeing its page tables.
+ * That leaves a window in which KSM might refault pages which have just
+ * been finally unmapped: guard against that with ksm_test_exit(), and
+ * use it after getting mmap_sem in ksm.c, to check if mm is exiting.
+ */
+static inline bool ksm_test_exit(struct mm_struct *mm)
+{
+ return atomic_read(&mm->mm_users) == 0;
+}
+
+static inline void ksm_exit(struct mm_struct *mm,
+ struct mmu_gather **tlbp, unsigned long end)
{
if (test_bit(MMF_VM_MERGEABLE, &mm->flags))
- __ksm_exit(mm);
+ __ksm_exit(mm, tlbp, end);
}
/*
@@ -64,7 +81,13 @@ static inline int ksm_fork(struct mm_struct *mm, struct mm_struct *oldmm)
return 0;
}
-static inline void ksm_exit(struct mm_struct *mm)
+static inline bool ksm_test_exit(struct mm_struct *mm)
+{
+ return 0;
+}
+
+static inline void ksm_exit(struct mm_struct *mm,
+ struct mmu_gather **tlbp, unsigned long end)
{
}
diff --git a/kernel/fork.c b/kernel/fork.c
index 73a442b..42f20f5 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -501,7 +501,6 @@ void mmput(struct mm_struct *mm)
if (atomic_dec_and_test(&mm->mm_users)) {
exit_aio(mm);
- ksm_exit(mm);
exit_mmap(mm);
set_mm_exe_file(mm, NULL);
if (!list_empty(&mm->mmlist)) {
diff --git a/mm/ksm.c b/mm/ksm.c
index 7e4d255d..722e3f2 100644
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -32,6 +32,7 @@
#include <linux/mmu_notifier.h>
#include <linux/ksm.h>
+#include <asm/tlb.h>
#include <asm/tlbflush.h>
/*
@@ -347,6 +348,8 @@ static void break_cow(struct mm_struct *mm, unsigned long addr)
struct vm_area_struct *vma;
down_read(&mm->mmap_sem);
+ if (ksm_test_exit(mm))
+ goto out;
vma = find_vma(mm, addr);
if (!vma || vma->vm_start > addr)
goto out;
@@ -365,6 +368,8 @@ static struct page *get_mergeable_page(struct rmap_item *rmap_item)
struct page *page;
down_read(&mm->mmap_sem);
+ if (ksm_test_exit(mm))
+ goto out;
vma = find_vma(mm, addr);
if (!vma || vma->vm_start > addr)
goto out;
@@ -439,11 +444,11 @@ static void remove_rmap_item_from_tree(struct rmap_item *rmap_item)
} else if (rmap_item->address & NODE_FLAG) {
unsigned char age;
/*
- * ksm_thread can and must skip the rb_erase, because
+ * Usually ksmd can and must skip the rb_erase, because
* root_unstable_tree was already reset to RB_ROOT.
- * But __ksm_exit has to be careful: do the rb_erase
- * if it's interrupting a scan, and this rmap_item was
- * inserted by this scan rather than left from before.
+ * But be careful when an mm is exiting: do the rb_erase
+ * if this rmap_item was inserted by this scan, rather
+ * than left over from before.
*/
age = (unsigned char)(ksm_scan.seqnr - rmap_item->address);
BUG_ON(age > 1);
@@ -491,6 +496,8 @@ static int unmerge_ksm_pages(struct vm_area_struct *vma,
int err = 0;
for (addr = start; addr < end && !err; addr += PAGE_SIZE) {
+ if (ksm_test_exit(vma->vm_mm))
+ break;
if (signal_pending(current))
err = -ERESTARTSYS;
else
@@ -507,34 +514,50 @@ static int unmerge_and_remove_all_rmap_items(void)
int err = 0;
spin_lock(&ksm_mmlist_lock);
- mm_slot = list_entry(ksm_mm_head.mm_list.next,
+ ksm_scan.mm_slot = list_entry(ksm_mm_head.mm_list.next,
struct mm_slot, mm_list);
spin_unlock(&ksm_mmlist_lock);
- while (mm_slot != &ksm_mm_head) {
+ for (mm_slot = ksm_scan.mm_slot;
+ mm_slot != &ksm_mm_head; mm_slot = ksm_scan.mm_slot) {
mm = mm_slot->mm;
down_read(&mm->mmap_sem);
for (vma = mm->mmap; vma; vma = vma->vm_next) {
+ if (ksm_test_exit(mm))
+ break;
if (!(vma->vm_flags & VM_MERGEABLE) || !vma->anon_vma)
continue;
err = unmerge_ksm_pages(vma,
vma->vm_start, vma->vm_end);
- if (err) {
- up_read(&mm->mmap_sem);
- goto out;
- }
+ if (err)
+ goto error;
}
+
remove_trailing_rmap_items(mm_slot, mm_slot->rmap_list.next);
- up_read(&mm->mmap_sem);
spin_lock(&ksm_mmlist_lock);
- mm_slot = list_entry(mm_slot->mm_list.next,
+ ksm_scan.mm_slot = list_entry(mm_slot->mm_list.next,
struct mm_slot, mm_list);
- spin_unlock(&ksm_mmlist_lock);
+ if (ksm_test_exit(mm)) {
+ hlist_del(&mm_slot->link);
+ list_del(&mm_slot->mm_list);
+ spin_unlock(&ksm_mmlist_lock);
+
+ free_mm_slot(mm_slot);
+ clear_bit(MMF_VM_MERGEABLE, &mm->flags);
+ up_read(&mm->mmap_sem);
+ mmdrop(mm);
+ } else {
+ spin_unlock(&ksm_mmlist_lock);
+ up_read(&mm->mmap_sem);
+ }
}
ksm_scan.seqnr = 0;
-out:
+ return 0;
+
+error:
+ up_read(&mm->mmap_sem);
spin_lock(&ksm_mmlist_lock);
ksm_scan.mm_slot = &ksm_mm_head;
spin_unlock(&ksm_mmlist_lock);
@@ -755,6 +778,9 @@ static int try_to_merge_with_ksm_page(struct mm_struct *mm1,
int err = -EFAULT;
down_read(&mm1->mmap_sem);
+ if (ksm_test_exit(mm1))
+ goto out;
+
vma = find_vma(mm1, addr1);
if (!vma || vma->vm_start > addr1)
goto out;
@@ -796,6 +822,10 @@ static int try_to_merge_two_pages(struct mm_struct *mm1, unsigned long addr1,
return err;
down_read(&mm1->mmap_sem);
+ if (ksm_test_exit(mm1)) {
+ up_read(&mm1->mmap_sem);
+ goto out;
+ }
vma = find_vma(mm1, addr1);
if (!vma || vma->vm_start > addr1) {
up_read(&mm1->mmap_sem);
@@ -1174,7 +1204,12 @@ next_mm:
mm = slot->mm;
down_read(&mm->mmap_sem);
- for (vma = find_vma(mm, ksm_scan.address); vma; vma = vma->vm_next) {
+ if (ksm_test_exit(mm))
+ vma = NULL;
+ else
+ vma = find_vma(mm, ksm_scan.address);
+
+ for (; vma; vma = vma->vm_next) {
if (!(vma->vm_flags & VM_MERGEABLE))
continue;
if (ksm_scan.address < vma->vm_start)
@@ -1183,6 +1218,8 @@ next_mm:
ksm_scan.address = vma->vm_end;
while (ksm_scan.address < vma->vm_end) {
+ if (ksm_test_exit(mm))
+ break;
*page = follow_page(vma, ksm_scan.address, FOLL_GET);
if (*page && PageAnon(*page)) {
flush_anon_page(vma, *page, ksm_scan.address);
@@ -1205,6 +1242,11 @@ next_mm:
}
}
+ if (ksm_test_exit(mm)) {
+ ksm_scan.address = 0;
+ ksm_scan.rmap_item = list_entry(&slot->rmap_list,
+ struct rmap_item, link);
+ }
/*
* Nuke all the rmap_items that are above this current rmap:
* because there were no VM_MERGEABLE vmas with such addresses.
@@ -1219,24 +1261,29 @@ next_mm:
* We've completed a full scan of all vmas, holding mmap_sem
* throughout, and found no VM_MERGEABLE: so do the same as
* __ksm_exit does to remove this mm from all our lists now.
+ * This applies either when cleaning up after __ksm_exit
+ * (but beware: we can reach here even before __ksm_exit),
+ * or when all VM_MERGEABLE areas have been unmapped (and
+ * mmap_sem then protects against race with MADV_MERGEABLE).
*/
hlist_del(&slot->link);
list_del(&slot->mm_list);
+ spin_unlock(&ksm_mmlist_lock);
+
free_mm_slot(slot);
clear_bit(MMF_VM_MERGEABLE, &mm->flags);
+ up_read(&mm->mmap_sem);
+ mmdrop(mm);
+ } else {
+ spin_unlock(&ksm_mmlist_lock);
+ up_read(&mm->mmap_sem);
}
- spin_unlock(&ksm_mmlist_lock);
- up_read(&mm->mmap_sem);
/* Repeat until we've completed scanning the whole list */
slot = ksm_scan.mm_slot;
if (slot != &ksm_mm_head)
goto next_mm;
- /*
- * Bump seqnr here rather than at top, so that __ksm_exit
- * can skip rb_erase on unstable tree until we run again.
- */
ksm_scan.seqnr++;
return NULL;
}
@@ -1361,6 +1408,7 @@ int __ksm_enter(struct mm_struct *mm)
spin_unlock(&ksm_mmlist_lock);
set_bit(MMF_VM_MERGEABLE, &mm->flags);
+ atomic_inc(&mm->mm_count);
if (needs_wakeup)
wake_up_interruptible(&ksm_thread_wait);
@@ -1368,41 +1416,45 @@ int __ksm_enter(struct mm_struct *mm)
return 0;
}
-void __ksm_exit(struct mm_struct *mm)
+void __ksm_exit(struct mm_struct *mm,
+ struct mmu_gather **tlbp, unsigned long end)
{
struct mm_slot *mm_slot;
+ int easy_to_free = 0;
/*
- * This process is exiting: doesn't hold and doesn't need mmap_sem;
- * but we do need to exclude ksmd and other exiters while we modify
- * the various lists and trees.
+ * This process is exiting: if it's straightforward (as is the
+ * case when ksmd was never running), free mm_slot immediately.
+ * But if it's at the cursor or has rmap_items linked to it, use
+ * mmap_sem to synchronize with any break_cows before pagetables
+ * are freed, and leave the mm_slot on the list for ksmd to free.
+ * Beware: ksm may already have noticed it exiting and freed the slot.
*/
- mutex_lock(&ksm_thread_mutex);
+
spin_lock(&ksm_mmlist_lock);
mm_slot = get_mm_slot(mm);
- if (!list_empty(&mm_slot->rmap_list)) {
- spin_unlock(&ksm_mmlist_lock);
- remove_trailing_rmap_items(mm_slot, mm_slot->rmap_list.next);
- spin_lock(&ksm_mmlist_lock);
- }
-
- if (ksm_scan.mm_slot == mm_slot) {
- ksm_scan.mm_slot = list_entry(
- mm_slot->mm_list.next, struct mm_slot, mm_list);
- ksm_scan.address = 0;
- ksm_scan.rmap_item = list_entry(
- &ksm_scan.mm_slot->rmap_list, struct rmap_item, link);
- if (ksm_scan.mm_slot == &ksm_mm_head)
- ksm_scan.seqnr++;
+ if (mm_slot && ksm_scan.mm_slot != mm_slot) {
+ if (list_empty(&mm_slot->rmap_list)) {
+ hlist_del(&mm_slot->link);
+ list_del(&mm_slot->mm_list);
+ easy_to_free = 1;
+ } else {
+ list_move(&mm_slot->mm_list,
+ &ksm_scan.mm_slot->mm_list);
+ }
}
-
- hlist_del(&mm_slot->link);
- list_del(&mm_slot->mm_list);
spin_unlock(&ksm_mmlist_lock);
- free_mm_slot(mm_slot);
- clear_bit(MMF_VM_MERGEABLE, &mm->flags);
- mutex_unlock(&ksm_thread_mutex);
+ if (easy_to_free) {
+ free_mm_slot(mm_slot);
+ clear_bit(MMF_VM_MERGEABLE, &mm->flags);
+ mmdrop(mm);
+ } else if (mm_slot) {
+ tlb_finish_mmu(*tlbp, 0, end);
+ down_write(&mm->mmap_sem);
+ up_write(&mm->mmap_sem);
+ *tlbp = tlb_gather_mmu(mm, 1);
+ }
}
#define KSM_ATTR_RO(_name) \
diff --git a/mm/memory.c b/mm/memory.c
index 1a435b8..f47ffe9 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -2648,8 +2648,9 @@ static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,
entry = maybe_mkwrite(pte_mkdirty(entry), vma);
page_table = pte_offset_map_lock(mm, pmd, address, &ptl);
- if (!pte_none(*page_table))
+ if (!pte_none(*page_table) || ksm_test_exit(mm))
goto release;
+
inc_mm_counter(mm, anon_rss);
page_add_new_anon_rmap(page, vma, address);
set_pte_at(mm, address, page_table, entry);
@@ -2791,7 +2792,7 @@ static int __do_fault(struct mm_struct *mm, struct vm_area_struct *vma,
* handle that later.
*/
/* Only go through if we didn't race with anybody else... */
- if (likely(pte_same(*page_table, orig_pte))) {
+ if (likely(pte_same(*page_table, orig_pte) && !ksm_test_exit(mm))) {
flush_icache_page(vma, page);
entry = mk_pte(page, vma->vm_page_prot);
if (flags & FAULT_FLAG_WRITE)
diff --git a/mm/mmap.c b/mm/mmap.c
index 376492e..e02f1aa 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -27,6 +27,7 @@
#include <linux/mount.h>
#include <linux/mempolicy.h>
#include <linux/rmap.h>
+#include <linux/ksm.h>
#include <linux/mmu_notifier.h>
#include <linux/perf_event.h>
@@ -2111,6 +2112,14 @@ void exit_mmap(struct mm_struct *mm)
/* Use -1 here to ensure all VMAs in the mm are unmapped */
end = unmap_vmas(&tlb, vma, 0, -1, &nr_accounted, NULL);
vm_unacct_memory(nr_accounted);
+
+ /*
+ * For KSM to handle OOM without deadlock when it's breaking COW in a
+ * likely victim of the OOM killer, we must serialize with ksm_exit()
+ * after freeing mm's pages but before freeing its page tables.
+ */
+ ksm_exit(mm, &tlb, end);
+
free_pgtables(tlb, vma, FIRST_USER_ADDRESS, 0);
tlb_finish_mmu(tlb, 0, end);
OpenPOWER on IntegriCloud