diff options
Diffstat (limited to 'mm/memory.c')
-rw-r--r-- | mm/memory.c | 100 |
1 files changed, 76 insertions, 24 deletions
diff --git a/mm/memory.c b/mm/memory.c index 11673c5d2c20..247b5c312b9b 100644 --- a/mm/memory.c +++ b/mm/memory.c | |||
@@ -1457,25 +1457,60 @@ static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma, | |||
1457 | { | 1457 | { |
1458 | struct page *old_page, *new_page; | 1458 | struct page *old_page, *new_page; |
1459 | pte_t entry; | 1459 | pte_t entry; |
1460 | int ret = VM_FAULT_MINOR; | 1460 | int reuse, ret = VM_FAULT_MINOR; |
1461 | 1461 | ||
1462 | old_page = vm_normal_page(vma, address, orig_pte); | 1462 | old_page = vm_normal_page(vma, address, orig_pte); |
1463 | if (!old_page) | 1463 | if (!old_page) |
1464 | goto gotten; | 1464 | goto gotten; |
1465 | 1465 | ||
1466 | if (PageAnon(old_page) && !TestSetPageLocked(old_page)) { | 1466 | if (unlikely((vma->vm_flags & (VM_SHARED|VM_WRITE)) == |
1467 | int reuse = can_share_swap_page(old_page); | 1467 | (VM_SHARED|VM_WRITE))) { |
1468 | unlock_page(old_page); | 1468 | if (vma->vm_ops && vma->vm_ops->page_mkwrite) { |
1469 | if (reuse) { | 1469 | /* |
1470 | flush_cache_page(vma, address, pte_pfn(orig_pte)); | 1470 | * Notify the address space that the page is about to |
1471 | entry = pte_mkyoung(orig_pte); | 1471 | * become writable so that it can prohibit this or wait |
1472 | entry = maybe_mkwrite(pte_mkdirty(entry), vma); | 1472 | * for the page to get into an appropriate state. |
1473 | ptep_set_access_flags(vma, address, page_table, entry, 1); | 1473 | * |
1474 | update_mmu_cache(vma, address, entry); | 1474 | * We do this without the lock held, so that it can |
1475 | lazy_mmu_prot_update(entry); | 1475 | * sleep if it needs to. |
1476 | ret |= VM_FAULT_WRITE; | 1476 | */ |
1477 | goto unlock; | 1477 | page_cache_get(old_page); |
1478 | pte_unmap_unlock(page_table, ptl); | ||
1479 | |||
1480 | if (vma->vm_ops->page_mkwrite(vma, old_page) < 0) | ||
1481 | goto unwritable_page; | ||
1482 | |||
1483 | page_cache_release(old_page); | ||
1484 | |||
1485 | /* | ||
1486 | * Since we dropped the lock we need to revalidate | ||
1487 | * the PTE as someone else may have changed it. If | ||
1488 | * they did, we just return, as we can count on the | ||
1489 | * MMU to tell us if they didn't also make it writable. | ||
1490 | */ | ||
1491 | page_table = pte_offset_map_lock(mm, pmd, address, | ||
1492 | &ptl); | ||
1493 | if (!pte_same(*page_table, orig_pte)) | ||
1494 | goto unlock; | ||
1478 | } | 1495 | } |
1496 | |||
1497 | reuse = 1; | ||
1498 | } else if (PageAnon(old_page) && !TestSetPageLocked(old_page)) { | ||
1499 | reuse = can_share_swap_page(old_page); | ||
1500 | unlock_page(old_page); | ||
1501 | } else { | ||
1502 | reuse = 0; | ||
1503 | } | ||
1504 | |||
1505 | if (reuse) { | ||
1506 | flush_cache_page(vma, address, pte_pfn(orig_pte)); | ||
1507 | entry = pte_mkyoung(orig_pte); | ||
1508 | entry = maybe_mkwrite(pte_mkdirty(entry), vma); | ||
1509 | ptep_set_access_flags(vma, address, page_table, entry, 1); | ||
1510 | update_mmu_cache(vma, address, entry); | ||
1511 | lazy_mmu_prot_update(entry); | ||
1512 | ret |= VM_FAULT_WRITE; | ||
1513 | goto unlock; | ||
1479 | } | 1514 | } |
1480 | 1515 | ||
1481 | /* | 1516 | /* |
@@ -1535,6 +1570,10 @@ oom: | |||
1535 | if (old_page) | 1570 | if (old_page) |
1536 | page_cache_release(old_page); | 1571 | page_cache_release(old_page); |
1537 | return VM_FAULT_OOM; | 1572 | return VM_FAULT_OOM; |
1573 | |||
1574 | unwritable_page: | ||
1575 | page_cache_release(old_page); | ||
1576 | return VM_FAULT_SIGBUS; | ||
1538 | } | 1577 | } |
1539 | 1578 | ||
1540 | /* | 1579 | /* |
@@ -2083,18 +2122,31 @@ retry: | |||
2083 | /* | 2122 | /* |
2084 | * Should we do an early C-O-W break? | 2123 | * Should we do an early C-O-W break? |
2085 | */ | 2124 | */ |
2086 | if (write_access && !(vma->vm_flags & VM_SHARED)) { | 2125 | if (write_access) { |
2087 | struct page *page; | 2126 | if (!(vma->vm_flags & VM_SHARED)) { |
2127 | struct page *page; | ||
2088 | 2128 | ||
2089 | if (unlikely(anon_vma_prepare(vma))) | 2129 | if (unlikely(anon_vma_prepare(vma))) |
2090 | goto oom; | 2130 | goto oom; |
2091 | page = alloc_page_vma(GFP_HIGHUSER, vma, address); | 2131 | page = alloc_page_vma(GFP_HIGHUSER, vma, address); |
2092 | if (!page) | 2132 | if (!page) |
2093 | goto oom; | 2133 | goto oom; |
2094 | copy_user_highpage(page, new_page, address); | 2134 | copy_user_highpage(page, new_page, address); |
2095 | page_cache_release(new_page); | 2135 | page_cache_release(new_page); |
2096 | new_page = page; | 2136 | new_page = page; |
2097 | anon = 1; | 2137 | anon = 1; |
2138 | |||
2139 | } else { | ||
2140 | /* if the page will be shareable, see if the backing | ||
2141 | * address space wants to know that the page is about | ||
2142 | * to become writable */ | ||
2143 | if (vma->vm_ops->page_mkwrite && | ||
2144 | vma->vm_ops->page_mkwrite(vma, new_page) < 0 | ||
2145 | ) { | ||
2146 | page_cache_release(new_page); | ||
2147 | return VM_FAULT_SIGBUS; | ||
2148 | } | ||
2149 | } | ||
2098 | } | 2150 | } |
2099 | 2151 | ||
2100 | page_table = pte_offset_map_lock(mm, pmd, address, &ptl); | 2152 | page_table = pte_offset_map_lock(mm, pmd, address, &ptl); |