On Thu, Oct 8, 2020 at 10:02 AM Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx> wrote: > > Here's the first patch anyway. If you actually have a test-case where > this matters, I guess I need to apply it now.. Actually, I removed the "__page_mapcount()" part of that patch, to keep it minimal and _only_ do remove the wrprotect trick. We can do the __page_mapcount() optimization and the mm sequence count for 5.10 (although so far nobody has actually written the seqcount patch - I think it would be a trivial few-liner, but I guess it won't make 5.10 at this point). So here's what I ended up with. Linus
From f3c64eda3e5097ec3198cb271f5f504d65d67131 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx> Date: Mon, 28 Sep 2020 12:50:03 -0700 Subject: [PATCH] mm: avoid early COW write protect games during fork() In commit 70e806e4e645 ("mm: Do early cow for pinned pages during fork() for ptes") we write-protected the PTE before doing the page pinning check, in order to avoid a race with concurrent fast-GUP pinning (which doesn't take the mm semaphore or the page table lock). That trick doesn't actually work - it doesn't handle memory ordering properly, and doing so would be prohibitively expensive. It also isn't really needed. While we're moving in the direction of allowing and supporting page pinning without marking the pinned area with MADV_DONTFORK, the fact is that we've never really supported this kind of odd "concurrent fork() and page pinning", and doing the serialization on a pte level is just wrong. We can add serialization with a per-mm sequence counter, so we know how to solve that race properly, but we'll do that at a more appropriate time. Right now this just removes the write protect games. It also turns out that the write protect games actually break on Power, as reported by Aneesh Kumar: "Architecture like ppc64 expects set_pte_at to be not used for updating a valid pte. This is further explained in commit 56eecdb912b5 ("mm: Use ptep/pmdp_set_numa() for updating _PAGE_NUMA bit")" and the code triggered a warning there: WARNING: CPU: 0 PID: 30613 at arch/powerpc/mm/pgtable.c:185 set_pte_at+0x2a8/0x3a0 arch/powerpc/mm/pgtable.c:185 Call Trace: copy_present_page mm/memory.c:857 [inline] copy_present_pte mm/memory.c:899 [inline] copy_pte_range mm/memory.c:1014 [inline] copy_pmd_range mm/memory.c:1092 [inline] copy_pud_range mm/memory.c:1127 [inline] copy_p4d_range mm/memory.c:1150 [inline] copy_page_range+0x1f6c/0x2cc0 mm/memory.c:1212 dup_mmap kernel/fork.c:592 [inline] dup_mm+0x77c/0xab0 kernel/fork.c:1355 copy_mm kernel/fork.c:1411 [inline] copy_process+0x1f00/0x2740 kernel/fork.c:2070 _do_fork+0xc4/0x10b0 kernel/fork.c:2429 Link: https://lore.kernel.org/lkml/CAHk-=wiWr+gO0Ro4LvnJBMs90OiePNyrE3E+pJvc9PzdBShdmw@xxxxxxxxxxxxxx/ Link: https://lore.kernel.org/linuxppc-dev/20201008092541.398079-1-aneesh.kumar@xxxxxxxxxxxxx/ Reported-by: Aneesh Kumar K.V <aneesh.kumar@xxxxxxxxxxxxx> Tested-by: Leon Romanovsky <leonro@xxxxxxxxxx> Cc: Peter Xu <peterx@xxxxxxxxxx> Cc: Jason Gunthorpe <jgg@xxxxxxxx> Cc: John Hubbard <jhubbard@xxxxxxxxxx> Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> Cc: Jan Kara <jack@xxxxxxx> Cc: Michal Hocko <mhocko@xxxxxxxx> Cc: Kirill Shutemov <kirill@xxxxxxxxxxxxx> Cc: Hugh Dickins <hughd@xxxxxxxxxx> Signed-off-by: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx> --- mm/memory.c | 41 ++++------------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/mm/memory.c b/mm/memory.c index fcfc4ca36eba..eeae590e526a 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -806,8 +806,6 @@ copy_present_page(struct mm_struct *dst_mm, struct mm_struct *src_mm, return 1; /* - * The trick starts. - * * What we want to do is to check whether this page may * have been pinned by the parent process. If so, * instead of wrprotect the pte on both sides, we copy @@ -815,47 +813,16 @@ copy_present_page(struct mm_struct *dst_mm, struct mm_struct *src_mm, * the pinned page won't be randomly replaced in the * future. * - * To achieve this, we do the following: - * - * 1. Write-protect the pte if it's writable. This is - * to protect concurrent write fast-gup with - * FOLL_PIN, so that we'll fail the fast-gup with - * the write bit removed. - * - * 2. Check page_maybe_dma_pinned() to see whether this - * page may have been pinned. - * - * The order of these steps is important to serialize - * against the fast-gup code (gup_pte_range()) on the - * pte check and try_grab_compound_head(), so that - * we'll make sure either we'll capture that fast-gup - * so we'll copy the pinned page here, or we'll fail - * that fast-gup. - * - * NOTE! Even if we don't end up copying the page, - * we won't undo this wrprotect(), because the normal - * reference copy will need it anyway. - */ - if (pte_write(pte)) - ptep_set_wrprotect(src_mm, addr, src_pte); - - /* - * These are the "normally we can just copy by reference" - * checks. + * The page pinning checks are just "has this mm ever + * seen pinning", along with the (inexact) check of + * the page count. That might give false positives for + * for pinning, but it will work correctly. */ if (likely(!atomic_read(&src_mm->has_pinned))) return 1; if (likely(!page_maybe_dma_pinned(page))) return 1; - /* - * Uhhuh. It looks like the page might be a pinned page, - * and we actually need to copy it. Now we can set the - * source pte back to being writable. - */ - if (pte_write(pte)) - set_pte_at(src_mm, addr, src_pte, pte); - new_page = *prealloc; if (!new_page) return -EAGAIN; -- 2.28.0.218.gc12ef3d349