mm: some enhancements to the page fault mechanism
Subsystems (25)
Peter Xu
reviewerv3 cover mm: some enhancements to the page fault mechanism
v3:
- fix up issues that krobot reported, rebase
This is an RFC series as cleanup and enhancements to current page fault logic. The whole idea comes from the discussion between Andrea and Linus on the bug reported by syzbot here:
https://lkml.org/lkml/2017/11/2/833
Basically it does two things:
(a) Allows the page fault logic to be more interactive on not only SIGKILL, but also the rest of userspace signals, and,
(b) Allows the page fault retry (VM_FAULT_RETRY) to happen for more than once.
For (a): with the changes we should be able to react faster when page faults are working in parallel with userspace signals like SIGSTOP and SIGCONT (and more), and with that we can remove the buggy part in userfaultfd and benefit the whole page fault mechanism on faster signal processing to reach the userspace.
For (b), we should be able to allow the page fault handler to loop for even more than twice. Some context: for now since we have FAULT_FLAG_ALLOW_RETRY we can allow to retry the page fault once with the same interrupt context, however never more than twice. This can be not only a potential cleanup to remove this assumption since AFAIU the code itself doesn't really have this twice-only limitation (though that should be a protective approach in the past), at the same time it'll greatly simplify future works like userfaultfd write-protect where it's possible to retry for more than twice (please have a look at [1] below for a possible user that might require the page fault to be handled for a third time; if we can remove the retry limitation we can simply drop that patch and those complexity).
Some more details on each of the patch (even more in commit messages):
Patch 1: A cleanup of existing GUP code to rename the confusing
"nonblocking" parameter to "locked" which seems suite more.
Patch 2: Complete the page fault faster for non-sigkill signals
Patch 3: Remove the limitation to only allow to retry page fault for
twice (page fault part)
Patch 4: Similar work of patch 3, but for GUP.
The series is only lightly tested. Before running more tests, I'd be really glad to see whether there's any feedback first.
Looking forward to your comments. Thanks,
Peter Xu (4):
mm: gup: rename "nonblocking" to "locked" where proper
mm: userfault: return VM_FAULT_RETRY on signals
mm: allow VM_FAULT_RETRY for multiple times
mm: gup: allow VM_FAULT_RETRY for multiple times
arch/alpha/mm/fault.c | 4 +-- arch/arc/mm/fault.c | 12 ++++---- arch/arm/mm/fault.c | 17 ++++++----- arch/arm64/mm/fault.c | 11 ++----- arch/hexagon/mm/vm_fault.c | 3 +- arch/ia64/mm/fault.c | 3 +- arch/m68k/mm/fault.c | 5 +--- arch/microblaze/mm/fault.c | 3 +- arch/mips/mm/fault.c | 3 +- arch/nds32/mm/fault.c | 7 ++--- arch/nios2/mm/fault.c | 5 +--- arch/openrisc/mm/fault.c | 3 +- arch/parisc/mm/fault.c | 4 +-- arch/powerpc/mm/fault.c | 9 ++---- arch/riscv/mm/fault.c | 9 ++---- arch/s390/mm/fault.c | 14 ++++----- arch/sh/mm/fault.c | 5 +++- arch/sparc/mm/fault_32.c | 4 ++- arch/sparc/mm/fault_64.c | 4 ++- arch/um/kernel/trap.c | 6 ++-- arch/unicore32/mm/fault.c | 10 ++----- arch/x86/mm/fault.c | 13 ++++++-- arch/xtensa/mm/fault.c | 4 ++- fs/userfaultfd.c | 24 --------------- mm/gup.c | 61 +++++++++++++++++++++----------------- mm/hugetlb.c | 8 ++--- 26 files changed, 114 insertions(+), 137 deletions(-)
Signature · 1 line
2.17.1
Peter Xu
reviewerRFC PATCH v3 1/4 mm: gup: rename "nonblocking" to "locked" where proper
There's plenty of places around __get_user_pages() that has a parameter "nonblocking" which does not really mean that "it won't block" (because it can really block) but instead it shows whether the mmap_sem is released by up_read() during the page fault handling mostly when VM_FAULT_RETRY is returned.
We have the correct naming in e.g. get_user_pages_locked() or get_user_pages_remote() as "locked", however there're still many places that are using the "nonblocking" as name.
Renaming the places to "locked" where proper to better suite the functionality of the variable. While at it, fixing up some of the comments accordingly.
--- mm/gup.c | 44 +++++++++++++++++++++----------------------- mm/hugetlb.c | 8 ++++---- 2 files changed, 25 insertions(+), 27 deletions(-)
mm/gup.c +21 -23
@@ -506,12 +506,12 @@ static int get_gate_page(struct mm_struct *mm, unsigned long address, }/* -* mmap_sem must be held on entry. If @nonblocking != NULL and -* *@flags does not include FOLL_NOWAIT, the mmap_sem may be released. -* If it is, *@nonblocking will be set to 0 and -EBUSY returned. +* mmap_sem must be held on entry. If @locked != NULL and *@flags +* does not include FOLL_NOWAIT, the mmap_sem may be released. If it +* is, *@locked will be set to 0 and -EBUSY returned. */ static int faultin_page (struct task_struct *tsk,struct vm_area_struct *vma, -unsigned long address,unsigned int *flags,int *nonblocking) +unsigned long address,unsigned int *flags,int *locked) {unsigned int fault_flags =0 ;vm_fault_t ret;
@@ -523,7 +523,7 @@ static int faultin_page(struct task_struct *tsk, struct vm_area_struct *vma, fault_flags |=FAULT_FLAG_WRITE ;if (*flags &FOLL_REMOTE ) fault_flags |=FAULT_FLAG_REMOTE ; -if (nonblocking) +if (locked) fault_flags |=FAULT_FLAG_ALLOW_RETRY ;if (*flags &FOLL_NOWAIT ) fault_flags |=FAULT_FLAG_ALLOW_RETRY |FAULT_FLAG_RETRY_NOWAIT ;
@@ -549,8 +549,8 @@ static int faultin_page(struct task_struct *tsk, struct vm_area_struct *vma, }if (ret &VM_FAULT_RETRY ) { -if (nonblocking && !(fault_flags &FAULT_FLAG_RETRY_NOWAIT )) - *nonblocking =0 ; +if (locked && !(fault_flags &FAULT_FLAG_RETRY_NOWAIT )) + *locked =0 ;return -EBUSY ; }
@@ -627,7 +627,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)* only intends to ensure the pages are faulted in. * @vmas: array of pointers to vmas corresponding to each page. * Or NULL if the caller does not require them. -* @nonblocking: whether waiting for disk IO or mmap_sem contention +* @locked: whether we're still with the mmap_sem held * * Returns number of pages pinned. This may be fewer than the number * requested. If nr_pages is 0 or negative, returns 0. If no pages
@@ -656,13 +656,11 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)* appropriate) must be called after the page is finished with, and * before put_page is called. * -* If @nonblocking != NULL, __get_user_pages will not wait for disk IO -* or mmap_sem contention, and if waiting is needed to pin all pages, -* *@nonblocking will be set to 0. Further, if @gup_flags does not -* include FOLL_NOWAIT, the mmap_sem will be released via up_read() in -* this case. +* If @locked != NULL, *@locked will be set to 0 when mmap_sem is +* released by an up_read(). That can happen if @gup_flags does not +* has FOLL_NOWAIT. * -* A caller using such a combination of @nonblocking and @gup_flags +* A caller using such a combination of @locked and @gup_flags * must therefore hold the mmap_sem for reading only, and recognize * when it's been released. Otherwise, it must be held for either * reading or writing and will not be released.
@@ -674,7 +672,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)static long __get_user_pages (struct task_struct *tsk,struct mm_struct *mm,unsigned long start,unsigned long nr_pages,unsigned int gup_flags,struct page **pages, -struct vm_area_struct **vmas,int *nonblocking) +struct vm_area_struct **vmas,int *locked) {long ret =0 , i =0 ;struct vm_area_struct *vma =NULL ;
@@ -719,7 +717,7 @@ static long __get_user_pages(struct task_struct *tsk, struct mm_struct *mm,if (is_vm_hugetlb_page (vma)) { i =follow_hugetlb_page (mm, vma, pages, vmas, &start, &nr_pages, i, - gup_flags, nonblocking); + gup_flags, locked);continue ; } }
@@ -737,7 +735,7 @@ static long __get_user_pages(struct task_struct *tsk, struct mm_struct *mm, page =follow_page_mask (vma, start, foll_flags, &ctx);if (!page) { ret =faultin_page (tsk, vma, start, &foll_flags, - nonblocking); + locked);switch (ret) {case 0 : goto retry;
@@ -1196,7 +1194,7 @@ EXPORT_SYMBOL(get_user_pages_longterm);* @vma: target vma * @start: start address * @end: end address -* @nonblocking: +* @locked: whether the mmap_sem is still held * * This takes care of mlocking the pages too if VM_LOCKED is set. *
@@ -1204,14 +1202,14 @@ EXPORT_SYMBOL(get_user_pages_longterm);* * vma->vm_mm->mmap_sem must be held. * -* If @nonblocking is NULL, it may be held for read or write and will +* If @locked is NULL, it may be held for read or write and will * be unperturbed. * -* If @nonblocking is non-NULL, it must held for read only and may be -* released. If it's released, *@nonblocking will be set to 0. +* If @locked is non-NULL, it must held for read only and may be +* released. If it's released, *@locked will be set to 0. */ long populate_vma_page_range (struct vm_area_struct *vma, -unsigned long start,unsigned long end,int *nonblocking) +unsigned long start,unsigned long end,int *locked) {struct mm_struct *mm = vma->vm_mm;unsigned long nr_pages = (end - start) /PAGE_SIZE ;
@@ -1246,7 +1244,7 @@ long populate_vma_page_range(struct vm_area_struct *vma,* not result in a stack expansion that recurses back here. */ return __get_user_pages (current, mm, start, nr_pages, gup_flags, -NULL ,NULL , nonblocking); +NULL ,NULL , locked); }/*
mm/hugetlb.c +4 -4
@@ -4181,7 +4181,7 @@ int hugetlb_mcopy_atomic_pte(struct mm_struct *dst_mm,long follow_hugetlb_page (struct mm_struct *mm,struct vm_area_struct *vma,struct page **pages,struct vm_area_struct **vmas,unsigned long *position,unsigned long *nr_pages, -long i,unsigned int flags,int *nonblocking) +long i,unsigned int flags,int *locked) {unsigned long pfn_offset;unsigned long vaddr = *position;
@@ -4252,7 +4252,7 @@ long follow_hugetlb_page(struct mm_struct *mm, struct vm_area_struct *vma,spin_unlock (ptl);if (flags &FOLL_WRITE ) fault_flags |=FAULT_FLAG_WRITE ; -if (nonblocking) +if (locked) fault_flags |=FAULT_FLAG_ALLOW_RETRY ;if (flags &FOLL_NOWAIT ) fault_flags |= FAULT_FLAG_ALLOW_RETRY |
@@ -4269,8 +4269,8 @@ long follow_hugetlb_page(struct mm_struct *mm, struct vm_area_struct *vma,break ; }if (ret &VM_FAULT_RETRY ) { -if (nonblocking) - *nonblocking =0 ; +if (locked) + *locked =0 ; *nr_pages =0 ;/* * VM_FAULT_RETRY must not return an
Signature · 1 line
2.17.1
Peter Xu
reviewerRFC PATCH v3 2/4 mm: userfault: return VM_FAULT_RETRY on signals
In reply to Peter XuThere was a special path in handle_userfault() in the past that we'll return a VM_FAULT_NOPAGE when we detected non-fatal signals when waiting for userfault handling. We did that by reacquiring the mmap_sem before returning. However that brings a risk in that the vmas might have changed when we retake the mmap_sem and even we could be holding an invalid vma structure. The problem was reported by syzbot.
This patch removes the special path and we'll return a VM_FAULT_RETRY with the common path even if we have got such signals. Then for all the architectures that is passing in VM_FAULT_ALLOW_RETRY into handle_mm_fault(), we check not only for SIGKILL but for all the rest of userspace pending signals right after we returned from handle_mm_fault().
The idea comes from the upstream discussion between Linus and Andrea:
https://lkml.org/lkml/2017/10/30/560
(This patch contains a potential fix for a double-free of mmap_sem on
ARC architecture; please see https://lkml.org/lkml/2018/11/1/723 for more information)
--- arch/alpha/mm/fault.c | 2 +- arch/arc/mm/fault.c | 11 +++++++---- arch/arm/mm/fault.c | 14 ++++++++++---- arch/arm64/mm/fault.c | 6 +++--- arch/hexagon/mm/vm_fault.c | 2 +- arch/ia64/mm/fault.c | 2 +- arch/m68k/mm/fault.c | 2 +- arch/microblaze/mm/fault.c | 2 +- arch/mips/mm/fault.c | 2 +- arch/nds32/mm/fault.c | 6 +++--- arch/nios2/mm/fault.c | 2 +- arch/openrisc/mm/fault.c | 2 +- arch/parisc/mm/fault.c | 2 +- arch/powerpc/mm/fault.c | 4 +++- arch/riscv/mm/fault.c | 4 ++-- arch/s390/mm/fault.c | 9 ++++++--- arch/sh/mm/fault.c | 4 ++++ arch/sparc/mm/fault_32.c | 3 +++ arch/sparc/mm/fault_64.c | 3 +++ arch/um/kernel/trap.c | 5 ++++- arch/unicore32/mm/fault.c | 4 ++-- arch/x86/mm/fault.c | 12 +++++++++++- arch/xtensa/mm/fault.c | 3 +++ fs/userfaultfd.c | 24 ------------------------ 24 files changed, 73 insertions(+), 57 deletions(-)
arch/alpha/mm/fault.c +1 -1
@@ -150,7 +150,7 @@ do_page_fault(unsigned long address, unsigned long mmcsr,the fault. */ fault =handle_mm_fault (vma, address, flags); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return ;if (unlikely (fault &VM_FAULT_ERROR )) {
arch/arc/mm/fault.c +7 -4
@@ -142,11 +142,14 @@ void do_page_fault(unsigned long address, struct pt_regs *regs) fault =handle_mm_fault (vma, address, flags);/* If Pagefault was interrupted by SIGKILL, exit page fault "early" */ -if (unlikely (fatal_signal_pending (current))) { -if ((fault &VM_FAULT_ERROR ) && !(fault &VM_FAULT_RETRY )) +if (unlikely (fatal_signal_pending (current) &&user_mode (regs))) { +/* +* VM_FAULT_RETRY means we have released the mmap_sem, +* otherwise we need to drop it before leaving +*/ +if (!(fault &VM_FAULT_RETRY ))up_read (&mm->mmap_sem); -if (user_mode (regs)) -return ; +return ; }perf_sw_event (PERF_COUNT_SW_PAGE_FAULTS ,1 , regs, address);
arch/arm/mm/fault.c +10 -4
@@ -308,14 +308,20 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) fault =__do_page_fault (mm, addr, fsr, flags, tsk); -/* If we need to retry but a fatal signal is pending, handle the +/* If we need to retry but a signal is pending, handle the * signal first. We do not need to release the mmap_sem because * it would already be released in __lock_page_or_retry in * mm/filemap.c. */ -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) { -if (!user_mode (regs)) +if (fault &VM_FAULT_RETRY ) { +if (fatal_signal_pending (current) && !user_mode (regs)) goto no_context; -return 0 ; +else if (signal_pending (current)) +/* +* It's either a common signal, or a fatal +* signal but for the userspace, we return +* immediately. +*/ +return 0 ; }/*
arch/arm64/mm/fault.c +3 -3
@@ -499,13 +499,13 @@ static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,if (fault &VM_FAULT_RETRY ) {/* -* If we need to retry but a fatal signal is pending, +* If we need to retry but a signal is pending, * handle the signal first. We do not need to release * the mmap_sem because it would already be released * in __lock_page_or_retry in mm/filemap.c. */ -if (fatal_signal_pending (current)) { -if (!user_mode (regs)) +if (signal_pending (current)) { +if (fatal_signal_pending (current) && !user_mode (regs)) goto no_context;return 0 ; }
arch/hexagon/mm/vm_fault.c +1 -1
@@ -104,7 +104,7 @@ void do_page_fault(unsigned long address, long cause, struct pt_regs *regs) fault =handle_mm_fault (vma, address, flags); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return ;/* The most common case -- we are done. */
arch/ia64/mm/fault.c +1 -1
@@ -163,7 +163,7 @@ ia64_do_page_fault (unsigned long address, unsigned long isr, struct pt_regs *re*/ fault =handle_mm_fault (vma, address, flags); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return ;if (unlikely (fault &VM_FAULT_ERROR )) {
arch/m68k/mm/fault.c +1 -1
@@ -138,7 +138,7 @@ int do_page_fault(struct pt_regs *regs, unsigned long address, fault =handle_mm_fault (vma, address, flags);pr_debug ("handle_mm_fault returns %x\n" , fault); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return 0 ;if (unlikely (fault &VM_FAULT_ERROR )) {
arch/microblaze/mm/fault.c +1 -1
@@ -217,7 +217,7 @@ void do_page_fault(struct pt_regs *regs, unsigned long address,*/ fault =handle_mm_fault (vma, address, flags); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return ;if (unlikely (fault &VM_FAULT_ERROR )) {
arch/mips/mm/fault.c +1 -1
@@ -154,7 +154,7 @@ static void __kprobes __do_page_fault(struct pt_regs *regs, unsigned long write,*/ fault =handle_mm_fault (vma, address, flags); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return ;perf_sw_event (PERF_COUNT_SW_PAGE_FAULTS ,1 , regs, address);
arch/nds32/mm/fault.c +3 -3
@@ -207,12 +207,12 @@ void do_page_fault(unsigned long entry, unsigned long addr, fault =handle_mm_fault (vma, addr, flags);/* -* If we need to retry but a fatal signal is pending, handle the +* If we need to retry but a signal is pending, handle the * signal first. We do not need to release the mmap_sem because it * would already be released in __lock_page_or_retry in mm/filemap.c. */ -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) { -if (!user_mode (regs)) +if (fault &VM_FAULT_RETRY &&signal_pending (current)) { +if (fatal_signal_pending (current) && !user_mode (regs)) goto no_context;return ; }
arch/nios2/mm/fault.c +1 -1
@@ -134,7 +134,7 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long cause,*/ fault =handle_mm_fault (vma, address, flags); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return ;if (unlikely (fault &VM_FAULT_ERROR )) {
arch/openrisc/mm/fault.c +1 -1
@@ -165,7 +165,7 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long address, fault =handle_mm_fault (vma, address, flags); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return ;if (unlikely (fault &VM_FAULT_ERROR )) {
arch/parisc/mm/fault.c +1 -1
@@ -303,7 +303,7 @@ void do_page_fault(struct pt_regs *regs, unsigned long code, fault =handle_mm_fault (vma, address, flags); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return ;if (unlikely (fault &VM_FAULT_ERROR )) {
arch/powerpc/mm/fault.c +3 -1
@@ -575,8 +575,10 @@ static int __do_page_fault(struct pt_regs *regs, unsigned long address,*/ flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ; -if (!fatal_signal_pending (current)) +if (!signal_pending (current)) goto retry; +else if (!fatal_signal_pending (current) && is_user) +return 0 ; }/*
arch/riscv/mm/fault.c +2 -2
@@ -123,11 +123,11 @@ asmlinkage void do_page_fault(struct pt_regs *regs) fault =handle_mm_fault (vma, addr, flags);/* -* If we need to retry but a fatal signal is pending, handle the +* If we need to retry but a signal is pending, handle the * signal first. We do not need to release the mmap_sem because it * would already be released in __lock_page_or_retry in mm/filemap.c. */ -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (tsk)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (tsk))return ;if (unlikely (fault &VM_FAULT_ERROR )) {
arch/s390/mm/fault.c +6 -3
@@ -500,9 +500,12 @@ static inline vm_fault_t do_exception(struct pt_regs *regs, int access)* the fault. */ fault =handle_mm_fault (vma, address, flags); -/* No reason to continue if interrupted by SIGKILL. */ -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) { - fault =VM_FAULT_SIGNAL ; +/* Do not continue if interrupted by signals. */ +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current)) { +if (fatal_signal_pending (current)) + fault =VM_FAULT_SIGNAL ; +else + fault =0 ;if (flags &FAULT_FLAG_RETRY_NOWAIT ) goto out_up; goto out;
arch/sh/mm/fault.c +4 -0
@@ -506,6 +506,10 @@ asmlinkage void __kprobes do_page_fault(struct pt_regs *regs,* have already released it in __lock_page_or_retry * in mm/filemap.c. */ + +if (user_mode (regs) &&signal_pending (tsk)) +return ; + goto retry; } }
arch/sparc/mm/fault_32.c +3 -0
@@ -269,6 +269,9 @@ asmlinkage void do_sparc_fault(struct pt_regs *regs, int text_fault, int write,* in mm/filemap.c. */ +if (user_mode (regs) &&signal_pending (tsk)) +return ; + goto retry; } }
arch/sparc/mm/fault_64.c +3 -0
@@ -467,6 +467,9 @@ asmlinkage void __kprobes do_sparc64_fault(struct pt_regs *regs)* in mm/filemap.c. */ +if (user_mode (regs) &&signal_pending (current)) +return ; + goto retry; } }
arch/um/kernel/trap.c +4 -1
@@ -76,8 +76,11 @@ int handle_page_fault(unsigned long address, unsigned long ip, fault =handle_mm_fault (vma, address, flags); -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if (fault &VM_FAULT_RETRY &&signal_pending (current)) { +if (is_user && !fatal_signal_pending (current)) + err =0 ; goto out_nosemaphore; + }if (unlikely (fault &VM_FAULT_ERROR )) {if (fault &VM_FAULT_OOM ) {
arch/unicore32/mm/fault.c +2 -2
@@ -248,11 +248,11 @@ static int do_pf(unsigned long addr, unsigned int fsr, struct pt_regs *regs) fault =__do_pf (mm, addr, fsr, flags, tsk); -/* If we need to retry but a fatal signal is pending, handle the +/* If we need to retry but a signal is pending, handle the * signal first. We do not need to release the mmap_sem because * it would already be released in __lock_page_or_retry in * mm/filemap.c. */ -if ((fault &VM_FAULT_RETRY ) &&fatal_signal_pending (current)) +if ((fault &VM_FAULT_RETRY ) &&signal_pending (current))return 0 ;if (!(fault &VM_FAULT_ERROR ) && (flags &FAULT_FLAG_ALLOW_RETRY )) {
arch/x86/mm/fault.c +11 -1
@@ -1433,8 +1433,18 @@ void do_user_addr_fault(struct pt_regs *regs,if (flags &FAULT_FLAG_ALLOW_RETRY ) { flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ; -if (!fatal_signal_pending (tsk)) +if (!signal_pending (tsk)) goto retry; +else if (!fatal_signal_pending (tsk)) +/* +* There is a signal for the task but +* it's not fatal, let's return +* directly to the userspace. This +* gives chance for signals like +* SIGSTOP/SIGCONT to be handled +* faster, e.g., with GDB. +*/ +return ; }/* User mode? Just return to handle the fatal exception */
arch/xtensa/mm/fault.c +3 -0
@@ -136,6 +136,9 @@ void do_page_fault(struct pt_regs *regs)* in mm/filemap.c. */ +if (user_mode (regs) &&signal_pending (current)) +return ; + goto retry; } }
fs/userfaultfd.c +0 -24
@@ -515,30 +515,6 @@ vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)__set_current_state (TASK_RUNNING ); -if (return_to_userland) { -if (signal_pending (current) && - !fatal_signal_pending (current)) { -/* -* If we got a SIGSTOP or SIGCONT and this is -* a normal userland page fault, just let -* userland return so the signal will be -* handled and gdb debugging works. The page -* fault code immediately after we return from -* this function is going to release the -* mmap_sem and it's not depending on it -* (unlike gup would if we were not to return -* VM_FAULT_RETRY). -* -* If a fatal signal is pending we still take -* the streamlined VM_FAULT_RETRY failure path -* and there's no need to retake the mmap_sem -* in such case. -*/ -down_read (&mm->mmap_sem); - ret =VM_FAULT_NOPAGE ; - } - } -/* * Here we race with the list_del; list_add in * userfaultfd_ctx_read(), however because we don't ever run
Signature · 1 line
2.17.1
Peter Xu
reviewerRFC PATCH v3 3/4 mm: allow VM_FAULT_RETRY for multiple times
In reply to Peter XuThe idea comes from a discussion between Linus and Andrea [1].
Before this patch we only allow a page fault to retry once. We achieved this by clearing the FAULT_FLAG_ALLOW_RETRY flag when doing handle_mm_fault() the second time. This was majorly used to avoid unexpected starvation of the system by looping over forever to handle the page fault on a single page. However that should hardly happen, and after all for each code path to return a VM_FAULT_RETRY we'll first wait for a condition (during which time we should possibly yield the cpu) to happen before VM_FAULT_RETRY is really returned.
This patch removes the restriction by keeping the FAULT_FLAG_ALLOW_RETRY flag when we receive VM_FAULT_RETRY. It means that the page fault handler now can retry the page fault for multiple times if necessary without the need to generate another page fault event. Meanwhile we still keep the FAULT_FLAG_TRIED flag so page fault handler can still identify whether a page fault is the first attempt or not.
GUP code is not touched yet and will be covered in follow up patch.
This will be a nice enhancement for current code at the same time a supporting material for the future userfaultfd-writeprotect work since in that work there will always be an explicit userfault writeprotect retry for protected pages, and if that cannot resolve the page fault (e.g., when userfaultfd-writeprotect is used in conjunction with shared memory) then we'll possibly need a 3rd retry of the page fault. It might also benefit other potential users who will have similar requirement like userfault write-protection.
Please read the thread below for more information.
[1] https://lkml.org/lkml/2017/11/2/833
--- arch/alpha/mm/fault.c | 2 +- arch/arc/mm/fault.c | 1 - arch/arm/mm/fault.c | 3 --- arch/arm64/mm/fault.c | 5 ----- arch/hexagon/mm/vm_fault.c | 1 - arch/ia64/mm/fault.c | 1 - arch/m68k/mm/fault.c | 3 --- arch/microblaze/mm/fault.c | 1 - arch/mips/mm/fault.c | 1 - arch/nds32/mm/fault.c | 1 - arch/nios2/mm/fault.c | 3 --- arch/openrisc/mm/fault.c | 1 - arch/parisc/mm/fault.c | 2 -- arch/powerpc/mm/fault.c | 5 ----- arch/riscv/mm/fault.c | 5 ----- arch/s390/mm/fault.c | 5 +---- arch/sh/mm/fault.c | 1 - arch/sparc/mm/fault_32.c | 1 - arch/sparc/mm/fault_64.c | 1 - arch/um/kernel/trap.c | 1 - arch/unicore32/mm/fault.c | 6 +----- arch/x86/mm/fault.c | 1 - arch/xtensa/mm/fault.c | 1 - 23 files changed, 3 insertions(+), 49 deletions(-)
arch/alpha/mm/fault.c +1 -1
@@ -169,7 +169,7 @@ do_page_fault(unsigned long address, unsigned long mmcsr,else current->min_flt++;if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; + flags |=FAULT_FLAG_TRIED ;/* No need to up_read(&mm->mmap_sem) as we would * have already released it in __lock_page_or_retry
arch/arc/mm/fault.c +0 -1
@@ -168,7 +168,6 @@ void do_page_fault(unsigned long address, struct pt_regs *regs) }if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ; goto retry; }
arch/arm/mm/fault.c +0 -3
@@ -342,9 +342,6 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) regs, addr); }if (fault &VM_FAULT_RETRY ) { -/* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk -* of starvation. */ - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ; goto retry; }
arch/arm64/mm/fault.c +0 -5
@@ -510,12 +510,7 @@ static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,return 0 ; } -/* -* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk of -* starvation. -*/ if (mm_flags &FAULT_FLAG_ALLOW_RETRY ) { - mm_flags &= ~FAULT_FLAG_ALLOW_RETRY ; mm_flags |=FAULT_FLAG_TRIED ; goto retry; }
arch/hexagon/mm/vm_fault.c +0 -1
@@ -115,7 +115,6 @@ void do_page_fault(unsigned long address, long cause, struct pt_regs *regs)else current->min_flt++;if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ; goto retry; }
arch/ia64/mm/fault.c +0 -1
@@ -189,7 +189,6 @@ ia64_do_page_fault (unsigned long address, unsigned long isr, struct pt_regs *reelse current->min_flt++;if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/* No need to up_read(&mm->mmap_sem) as we would
arch/m68k/mm/fault.c +0 -3
@@ -162,9 +162,6 @@ int do_page_fault(struct pt_regs *regs, unsigned long address,else current->min_flt++;if (fault &VM_FAULT_RETRY ) { -/* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk -* of starvation. */ - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/*
arch/microblaze/mm/fault.c +0 -1
@@ -236,7 +236,6 @@ void do_page_fault(struct pt_regs *regs, unsigned long address,else current->min_flt++;if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/*
arch/mips/mm/fault.c +0 -1
@@ -178,7 +178,6 @@ static void __kprobes __do_page_fault(struct pt_regs *regs, unsigned long write, tsk->min_flt++; }if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/*
arch/nds32/mm/fault.c +0 -1
@@ -237,7 +237,6 @@ void do_page_fault(unsigned long entry, unsigned long addr,else tsk->min_flt++;if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/* No need to up_read(&mm->mmap_sem) as we would
arch/nios2/mm/fault.c +0 -3
@@ -158,9 +158,6 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long cause,else current->min_flt++;if (fault &VM_FAULT_RETRY ) { -/* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk -* of starvation. */ - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/*
arch/openrisc/mm/fault.c +0 -1
@@ -185,7 +185,6 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long address,else tsk->min_flt++;if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/* No need to up_read(&mm->mmap_sem) as we would
arch/parisc/mm/fault.c +0 -2
@@ -327,8 +327,6 @@ void do_page_fault(struct pt_regs *regs, unsigned long code,else current->min_flt++;if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; -/* * No need to up_read(&mm->mmap_sem) as we would * have already released it in __lock_page_or_retry
arch/powerpc/mm/fault.c +0 -5
@@ -569,11 +569,6 @@ static int __do_page_fault(struct pt_regs *regs, unsigned long address,if (unlikely (fault &VM_FAULT_RETRY )) {/* We retry only once */ if (flags &FAULT_FLAG_ALLOW_RETRY ) { -/* -* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk -* of starvation. -*/ - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;if (!signal_pending (current)) goto retry;
arch/riscv/mm/fault.c +0 -5
@@ -154,11 +154,6 @@ asmlinkage void do_page_fault(struct pt_regs *regs) 1, regs, addr); }if (fault &VM_FAULT_RETRY ) { -/* -* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk -* of starvation. -*/ - flags &= ~(FAULT_FLAG_ALLOW_RETRY ); flags |=FAULT_FLAG_TRIED ;/*
arch/s390/mm/fault.c +1 -4
@@ -537,10 +537,7 @@ static inline vm_fault_t do_exception(struct pt_regs *regs, int access) fault =VM_FAULT_PFAULT ; goto out_up; } -/* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk -* of starvation. */ - flags &= ~(FAULT_FLAG_ALLOW_RETRY | -FAULT_FLAG_RETRY_NOWAIT ); + flags &= ~FAULT_FLAG_RETRY_NOWAIT ; flags |=FAULT_FLAG_TRIED ;down_read (&mm->mmap_sem); goto retry;
arch/sh/mm/fault.c +0 -1
@@ -498,7 +498,6 @@ asmlinkage void __kprobes do_page_fault(struct pt_regs *regs, regs, address); }if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/*
arch/sparc/mm/fault_32.c +0 -1
@@ -261,7 +261,6 @@ asmlinkage void do_sparc_fault(struct pt_regs *regs, int text_fault, int write, 1, regs, address); }if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/* No need to up_read(&mm->mmap_sem) as we would
arch/sparc/mm/fault_64.c +0 -1
@@ -459,7 +459,6 @@ asmlinkage void __kprobes do_sparc64_fault(struct pt_regs *regs) 1, regs, address); }if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/* No need to up_read(&mm->mmap_sem) as we would
arch/um/kernel/trap.c +0 -1
@@ -99,7 +99,6 @@ int handle_page_fault(unsigned long address, unsigned long ip,else current->min_flt++;if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ; goto retry;
arch/unicore32/mm/fault.c +1 -5
@@ -260,12 +260,8 @@ static int do_pf(unsigned long addr, unsigned int fsr, struct pt_regs *regs) tsk->maj_flt++; else tsk->min_flt++; -if (fault &VM_FAULT_RETRY ) { -/* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk -* of starvation. */ - flags &= ~FAULT_FLAG_ALLOW_RETRY ; +if (fault &VM_FAULT_RETRY ) goto retry; - } }up_read (&mm->mmap_sem);
arch/x86/mm/fault.c +0 -1
@@ -1431,7 +1431,6 @@ void do_user_addr_fault(struct pt_regs *regs,if (unlikely (fault &VM_FAULT_RETRY )) {/* Retry at most once */ if (flags &FAULT_FLAG_ALLOW_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;if (!signal_pending (tsk)) goto retry;
arch/xtensa/mm/fault.c +0 -1
@@ -128,7 +128,6 @@ void do_page_fault(struct pt_regs *regs)else current->min_flt++;if (fault &VM_FAULT_RETRY ) { - flags &= ~FAULT_FLAG_ALLOW_RETRY ; flags |=FAULT_FLAG_TRIED ;/* No need to up_read(&mm->mmap_sem) as we would
Signature · 1 line
2.17.1
Peter Xu
reviewerRFC PATCH v3 4/4 mm: gup: allow VM_FAULT_RETRY for multiple times
In reply to Peter XuThis is the gup counterpart of the change that allows the VM_FAULT_RETRY to happen for more than once.
--- mm/gup.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)
mm/gup.c +13 -4
@@ -528,7 +528,10 @@ static int faultin_page(struct task_struct *tsk, struct vm_area_struct *vma,if (*flags &FOLL_NOWAIT ) fault_flags |=FAULT_FLAG_ALLOW_RETRY |FAULT_FLAG_RETRY_NOWAIT ;if (*flags &FOLL_TRIED ) { -VM_WARN_ON_ONCE (fault_flags &FAULT_FLAG_ALLOW_RETRY ); +/* +* Note: FAULT_FLAG_ALLOW_RETRY and FAULT_FLAG_TRIED +* can co-exist +*/ fault_flags |=FAULT_FLAG_TRIED ; }
@@ -944,17 +947,23 @@ static __always_inline long __get_user_pages_locked(struct task_struct *tsk,/* VM_FAULT_RETRY triggered, so seek to the faulting offset */ pages += ret; start += ret <<PAGE_SHIFT ; + lock_dropped = true; +retry:/* * Repeat on the address that fired VM_FAULT_RETRY -* without FAULT_FLAG_ALLOW_RETRY but with +* with both FAULT_FLAG_ALLOW_RETRY and * FAULT_FLAG_TRIED. */ *locked =1 ; - lock_dropped = true;down_read (&mm->mmap_sem); ret =__get_user_pages (tsk, mm, start,1 , flags |FOLL_TRIED , - pages,NULL ,NULL ); + pages,NULL , locked); +if (!*locked) { +/* Continue to retry until we succeeded */ +BUG_ON (ret !=0 ); + goto retry; + }if (ret !=1 ) {BUG_ON (ret >1 );if (!pages_done)
Signature · 1 line
2.17.1