Merge tag 'for-6.2/writeback-2022-12-12' of git://git.kernel.dk/linux
[linux-2.6-block.git] / mm / ksm.c
index 4efdc424a3fce4ff1a7a5516c81d9a36c9857384..dd02780c387f02b3176d088fdcc1bb9d6db487e8 100644 (file)
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -39,6 +39,7 @@
 #include <linux/freezer.h>
 #include <linux/oom.h>
 #include <linux/numa.h>
+#include <linux/pagewalk.h>
 
 #include <asm/tlbflush.h>
 #include "internal.h"
@@ -419,42 +420,68 @@ static inline bool ksm_test_exit(struct mm_struct *mm)
        return atomic_read(&mm->mm_users) == 0;
 }
 
+static int break_ksm_pmd_entry(pmd_t *pmd, unsigned long addr, unsigned long next,
+                       struct mm_walk *walk)
+{
+       struct page *page = NULL;
+       spinlock_t *ptl;
+       pte_t *pte;
+       int ret;
+
+       if (pmd_leaf(*pmd) || !pmd_present(*pmd))
+               return 0;
+
+       pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
+       if (pte_present(*pte)) {
+               page = vm_normal_page(walk->vma, addr, *pte);
+       } else if (!pte_none(*pte)) {
+               swp_entry_t entry = pte_to_swp_entry(*pte);
+
+               /*
+                * As KSM pages remain KSM pages until freed, no need to wait
+                * here for migration to end.
+                */
+               if (is_migration_entry(entry))
+                       page = pfn_swap_entry_to_page(entry);
+       }
+       ret = page && PageKsm(page);
+       pte_unmap_unlock(pte, ptl);
+       return ret;
+}
+
+static const struct mm_walk_ops break_ksm_ops = {
+       .pmd_entry = break_ksm_pmd_entry,
+};
+
 /*
- * We use break_ksm to break COW on a ksm page: it's a stripped down
- *
- *     if (get_user_pages(addr, 1, FOLL_WRITE, &page, NULL) == 1)
- *             put_page(page);
+ * We use break_ksm to break COW on a ksm page by triggering unsharing,
+ * such that the ksm page will get replaced by an exclusive anonymous page.
  *
- * but taking great care only to touch a ksm page, in a VM_MERGEABLE vma,
+ * We take great care only to touch a ksm page, in a VM_MERGEABLE vma,
  * in case the application has unmapped and remapped mm,addr meanwhile.
  * Could a ksm page appear anywhere else?  Actually yes, in a VM_PFNMAP
  * mmap of /dev/mem, where we would not want to touch it.
  *
- * FAULT_FLAG/FOLL_REMOTE are because we do this outside the context
+ * FAULT_FLAG_REMOTE/FOLL_REMOTE are because we do this outside the context
  * of the process that owns 'vma'.  We also do not want to enforce
  * protection keys here anyway.
  */
 static int break_ksm(struct vm_area_struct *vma, unsigned long addr)
 {
-       struct page *page;
        vm_fault_t ret = 0;
 
        do {
-               bool ksm_page = false;
+               int ksm_page;
 
                cond_resched();
-               page = follow_page(vma, addr,
-                               FOLL_GET | FOLL_MIGRATION | FOLL_REMOTE);
-               if (IS_ERR_OR_NULL(page))
-                       break;
-               if (PageKsm(page))
-                       ksm_page = true;
-               put_page(page);
-
+               ksm_page = walk_page_range_vma(vma, addr, addr + 1,
+                                              &break_ksm_ops, NULL);
+               if (WARN_ON_ONCE(ksm_page < 0))
+                       return ksm_page;
                if (!ksm_page)
                        return 0;
                ret = handle_mm_fault(vma, addr,
-                                     FAULT_FLAG_WRITE | FAULT_FLAG_REMOTE,
+                                     FAULT_FLAG_UNSHARE | FAULT_FLAG_REMOTE,
                                      NULL);
        } while (!(ret & (VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV | VM_FAULT_OOM)));
        /*