[PATCH 23/29] mm: implement speculative handling in do_swap_page()

From: Michel Lespinasse
Date: Fri Apr 30 2021 - 15:54:25 EST


If the pte is larger than long, use pte_spinlock() to lock the page table
when verifying the pte - pte_spinlock() is necessary to ensure the page
table is still valid when we are locking it.

Abort speculative faults if the pte is not a swap entry, or if the desired
page is not found in swap cache, to keep things as simple as possible.

Only use trylock when locking the swapped page - again to keep things
simple, and also the usual lock_page_or_retry would otherwise try to
release the mmap lock which is not held in the speculative case.

Use pte_map_lock() to ensure proper synchronization when finally committing
the faulted page to the mm address space.

Signed-off-by: Michel Lespinasse <michel@xxxxxxxxxxxxxx>
---
mm/memory.c | 74 ++++++++++++++++++++++++++++++-----------------------
1 file changed, 42 insertions(+), 32 deletions(-)

diff --git a/mm/memory.c b/mm/memory.c
index c3cd29d3acc6..a3708b4a616c 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -2654,30 +2654,6 @@ bool __pte_map_lock(struct vm_fault *vmf)

#endif /* CONFIG_SPECULATIVE_PAGE_FAULT */

-/*
- * handle_pte_fault chooses page fault handler according to an entry which was
- * read non-atomically. Before making any commitment, on those architectures
- * or configurations (e.g. i386 with PAE) which might give a mix of unmatched
- * parts, do_swap_page must check under lock before unmapping the pte and
- * proceeding (but do_wp_page is only called after already making such a check;
- * and do_anonymous_page can safely check later on).
- */
-static inline int pte_unmap_same(struct mm_struct *mm, pmd_t *pmd,
- pte_t *page_table, pte_t orig_pte)
-{
- int same = 1;
-#if defined(CONFIG_SMP) || defined(CONFIG_PREEMPTION)
- if (sizeof(pte_t) > sizeof(unsigned long)) {
- spinlock_t *ptl = pte_lockptr(mm, pmd);
- spin_lock(ptl);
- same = pte_same(*page_table, orig_pte);
- spin_unlock(ptl);
- }
-#endif
- pte_unmap(page_table);
- return same;
-}
-
static inline bool cow_user_page(struct page *dst, struct page *src,
struct vm_fault *vmf)
{
@@ -3386,12 +3362,34 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
return VM_FAULT_RETRY;
}

- if (!pte_unmap_same(vma->vm_mm, vmf->pmd, vmf->pte, vmf->orig_pte))
- goto out;
+#if defined(CONFIG_SMP) || defined(CONFIG_PREEMPTION)
+ if (sizeof(pte_t) > sizeof(unsigned long)) {
+ /*
+ * vmf->orig_pte was read non-atomically. Before making
+ * any commitment, on those architectures or configurations
+ * (e.g. i386 with PAE) which might give a mix of
+ * unmatched parts, we must check under lock before
+ * unmapping the pte and proceeding.
+ *
+ * (but do_wp_page is only called after already making
+ * such a check; and do_anonymous_page can safely
+ * check later on).
+ */
+ if (!pte_spinlock(vmf))
+ return VM_FAULT_RETRY;
+ if (!pte_same(*vmf->pte, vmf->orig_pte))
+ goto unlock;
+ spin_unlock(vmf->ptl);
+ }
+#endif
+ pte_unmap(vmf->pte);
+ vmf->pte = NULL;

entry = pte_to_swp_entry(vmf->orig_pte);
if (unlikely(non_swap_entry(entry))) {
- if (is_migration_entry(entry)) {
+ if (vmf->flags & FAULT_FLAG_SPECULATIVE) {
+ ret = VM_FAULT_RETRY;
+ } else if (is_migration_entry(entry)) {
migration_entry_wait(vma->vm_mm, vmf->pmd,
vmf->address);
} else if (is_device_private_entry(entry)) {
@@ -3412,8 +3410,14 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
swapcache = page;

if (!page) {
- struct swap_info_struct *si = swp_swap_info(entry);
+ struct swap_info_struct *si;

+ if (vmf->flags & FAULT_FLAG_SPECULATIVE) {
+ delayacct_clear_flag(DELAYACCT_PF_SWAPIN);
+ return VM_FAULT_RETRY;
+ }
+
+ si = swp_swap_info(entry);
if (data_race(si->flags & SWP_SYNCHRONOUS_IO) &&
__swap_count(entry) == 1) {
/* skip swapcache */
@@ -3476,7 +3480,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
goto out_release;
}

- locked = lock_page_or_retry(page, vma->vm_mm, vmf->flags);
+ if (vmf->flags & FAULT_FLAG_SPECULATIVE)
+ locked = trylock_page(page);
+ else
+ locked = lock_page_or_retry(page, vma->vm_mm, vmf->flags);

delayacct_clear_flag(DELAYACCT_PF_SWAPIN);
if (!locked) {
@@ -3504,10 +3511,13 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
cgroup_throttle_swaprate(page, GFP_KERNEL);

/*
- * Back out if somebody else already faulted in this pte.
+ * Back out if the VMA has changed in our back during a speculative
+ * page fault or if somebody else already faulted in this pte.
*/
- vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd, vmf->address,
- &vmf->ptl);
+ if (!pte_map_lock(vmf)) {
+ ret = VM_FAULT_RETRY;
+ goto out_page;
+ }
if (unlikely(!pte_same(*vmf->pte, vmf->orig_pte)))
goto out_nomap;

--
2.20.1