diff options
author | Hugh Dickins <hugh@veritas.com> | 2009-01-06 17:39:33 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-01-06 18:59:03 -0500 |
commit | ab967d86015a19777955370deebc8262d50fed63 (patch) | |
tree | be0807bef32c0d28ac6dbcf7c8b938840d2f2a3f | |
parent | 878b63ac889df706d01048f2c110e322ad2f996d (diff) |
mm: wp lock page before deciding cow
An application may rely on get_user_pages() to give it pages writable from
userspace and shared with a driver, GUP breaking COW if necessary. It may
mprotect() the pages' writability, off and on, from time to time.
Normally this works fine (so long as the app does not fork); but just
occasionally, under memory pressure, a readonly pte in a newly writable
area is COWed unnecessarily, breaking the link with the driver: because
do_wp_page() does trylock_page, and falls back to COW whenever that fails.
For reliable behaviour in the unshared case, when the trylock_page fails,
now unlock pagetable, lock page and relock pagetable, before deciding
whether Copy-On-Write is really necessary.
Reported-by: Zhou Yingchao
Signed-off-by: Hugh Dickins <hugh@veritas.com>
Cc: Lee Schermerhorn <lee.schermerhorn@hp.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Nick Piggin <nickpiggin@yahoo.com.au>
Cc: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Cc: Robin Holt <holt@sgi.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | mm/memory.c | 17 |
1 files changed, 14 insertions, 3 deletions
diff --git a/mm/memory.c b/mm/memory.c index f594bb65a9f1..3922ffcf3dff 100644 --- a/mm/memory.c +++ b/mm/memory.c | |||
@@ -1848,10 +1848,21 @@ static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma, | |||
1848 | * not dirty accountable. | 1848 | * not dirty accountable. |
1849 | */ | 1849 | */ |
1850 | if (PageAnon(old_page)) { | 1850 | if (PageAnon(old_page)) { |
1851 | if (trylock_page(old_page)) { | 1851 | if (!trylock_page(old_page)) { |
1852 | reuse = can_share_swap_page(old_page); | 1852 | page_cache_get(old_page); |
1853 | unlock_page(old_page); | 1853 | pte_unmap_unlock(page_table, ptl); |
1854 | lock_page(old_page); | ||
1855 | page_table = pte_offset_map_lock(mm, pmd, address, | ||
1856 | &ptl); | ||
1857 | if (!pte_same(*page_table, orig_pte)) { | ||
1858 | unlock_page(old_page); | ||
1859 | page_cache_release(old_page); | ||
1860 | goto unlock; | ||
1861 | } | ||
1862 | page_cache_release(old_page); | ||
1854 | } | 1863 | } |
1864 | reuse = can_share_swap_page(old_page); | ||
1865 | unlock_page(old_page); | ||
1855 | } else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED)) == | 1866 | } else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED)) == |
1856 | (VM_WRITE|VM_SHARED))) { | 1867 | (VM_WRITE|VM_SHARED))) { |
1857 | /* | 1868 | /* |