Initialize nr_pages to 1 at the start of each loop iteration, like folio_referenced_one() does. Without this, nr_pages computed by a previous folio_unmap_pte_batch() call can be reused on a later iteration that does not run folio_unmap_pte_batch() again. I don’t think this is causing a bug today, but it is fragile. A real bug would require this sequence within the same try_to_unmap_one() call: 1. Hit the pte_present(pteval) branch and set nr_pages > 1. 2. Later hit the else branch and do pte_clear() for device-exclusive PTE, and execute rest of the code with nr_pages > 1. Executing the above would imply a lazyfree folio is mapped by a mix of present PTEs and device-exclusive PTEs. In practice, device-exclusive PTEs imply a GUP pin on the folio, and lazyfree unmapping aborts try_to_unmap_one() when it detects that condition. So today this likely does not manifest, but initializing nr_pages per-iteration is still the correct and safer behavior. Signed-off-by: Dev Jain Acked-by: Barry Song --- mm/rmap.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/rmap.c b/mm/rmap.c index fb3c351f8c458..a5f067a09de0f 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -1991,7 +1991,8 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, struct page *subpage; struct mmu_notifier_range range; enum ttu_flags flags = (enum ttu_flags)(long)arg; - unsigned long nr_pages = 1, end_addr; + unsigned long nr_pages; + unsigned long end_addr; unsigned long pfn; unsigned long hsz = 0; int ptes = 0; @@ -2030,6 +2031,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, mmu_notifier_invalidate_range_start(&range); while (page_vma_mapped_walk(&pvmw)) { + nr_pages = 1; /* * If the folio is in an mlock()d vma, we must not swap it out. */ -- 2.34.1 Simplify the code by refactoring the folio_test_hugetlb() branch into a new function. While at it, convert BUG helpers to WARN helpers. Signed-off-by: Dev Jain --- mm/rmap.c | 117 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 69 insertions(+), 48 deletions(-) diff --git a/mm/rmap.c b/mm/rmap.c index a5f067a09de0f..a98acdea0530a 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -1978,6 +1978,68 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, FPB_RESPECT_WRITE | FPB_RESPECT_SOFT_DIRTY); } +/* Returns false if unmap needs to be aborted */ +static inline bool unmap_hugetlb_folio(struct vm_area_struct *vma, + struct folio *folio, struct page_vma_mapped_walk *pvmw, + struct page *page, enum ttu_flags flags, pte_t *pteval, + struct mmu_notifier_range *range, bool *exit_walk) +{ + /* + * The try_to_unmap() is only passed a hugetlb page + * in the case where the hugetlb page is poisoned. + */ + VM_WARN_ON_PAGE(!PageHWPoison(page), page); + /* + * huge_pmd_unshare may unmap an entire PMD page. + * There is no way of knowing exactly which PMDs may + * be cached for this mm, so we must flush them all. + * start/end were already adjusted above to cover this + * range. + */ + flush_cache_range(vma, range->start, range->end); + + /* + * To call huge_pmd_unshare, i_mmap_rwsem must be + * held in write mode. Caller needs to explicitly + * do this outside rmap routines. + * + * We also must hold hugetlb vma_lock in write mode. + * Lock order dictates acquiring vma_lock BEFORE + * i_mmap_rwsem. We can only try lock here and fail + * if unsuccessful. + */ + if (!folio_test_anon(folio)) { + struct mmu_gather tlb; + + VM_WARN_ON(!(flags & TTU_RMAP_LOCKED)); + if (!hugetlb_vma_trylock_write(vma)) { + *exit_walk = true; + return false; + } + + tlb_gather_mmu_vma(&tlb, vma); + if (huge_pmd_unshare(&tlb, vma, pvmw->address, pvmw->pte)) { + hugetlb_vma_unlock_write(vma); + huge_pmd_unshare_flush(&tlb, vma); + tlb_finish_mmu(&tlb); + /* + * The PMD table was unmapped, + * consequently unmapping the folio. + */ + *exit_walk = true; + return true; + } + hugetlb_vma_unlock_write(vma); + tlb_finish_mmu(&tlb); + } + *pteval = huge_ptep_clear_flush(vma, pvmw->address, pvmw->pte); + if (pte_dirty(*pteval)) + folio_mark_dirty(folio); + + *exit_walk = false; + return true; +} + /* * @arg: enum ttu_flags will be passed to this argument */ @@ -2115,56 +2177,15 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, PageAnonExclusive(subpage); if (folio_test_hugetlb(folio)) { - bool anon = folio_test_anon(folio); - - /* - * The try_to_unmap() is only passed a hugetlb page - * in the case where the hugetlb page is poisoned. - */ - VM_BUG_ON_PAGE(!PageHWPoison(subpage), subpage); - /* - * huge_pmd_unshare may unmap an entire PMD page. - * There is no way of knowing exactly which PMDs may - * be cached for this mm, so we must flush them all. - * start/end were already adjusted above to cover this - * range. - */ - flush_cache_range(vma, range.start, range.end); + bool exit_walk; - /* - * To call huge_pmd_unshare, i_mmap_rwsem must be - * held in write mode. Caller needs to explicitly - * do this outside rmap routines. - * - * We also must hold hugetlb vma_lock in write mode. - * Lock order dictates acquiring vma_lock BEFORE - * i_mmap_rwsem. We can only try lock here and fail - * if unsuccessful. - */ - if (!anon) { - struct mmu_gather tlb; - - VM_BUG_ON(!(flags & TTU_RMAP_LOCKED)); - if (!hugetlb_vma_trylock_write(vma)) - goto walk_abort; - - tlb_gather_mmu_vma(&tlb, vma); - if (huge_pmd_unshare(&tlb, vma, address, pvmw.pte)) { - hugetlb_vma_unlock_write(vma); - huge_pmd_unshare_flush(&tlb, vma); - tlb_finish_mmu(&tlb); - /* - * The PMD table was unmapped, - * consequently unmapping the folio. - */ - goto walk_done; - } - hugetlb_vma_unlock_write(vma); - tlb_finish_mmu(&tlb); + ret = unmap_hugetlb_folio(vma, folio, &pvmw, subpage, + flags, &pteval, &range, + &exit_walk); + if (exit_walk) { + page_vma_mapped_walk_done(&pvmw); + break; } - pteval = huge_ptep_clear_flush(vma, address, pvmw.pte); - if (pte_dirty(pteval)) - folio_mark_dirty(folio); } else if (likely(pte_present(pteval))) { nr_pages = folio_unmap_pte_batch(folio, &pvmw, flags, pteval); end_addr = address + nr_pages * PAGE_SIZE; -- 2.34.1 For lazyfree folio unmapping, after clearing the ptes we must abort the operation if the folio got dirtied or it has unexpected references. Refactor this logic into a function which will return whether we need to abort or not. If we abort, we restore the ptes and bail out of try_to_unmap_one. Otherwise adjust the rss stats of the mm and jump to a label. Also rename that label from "discard" to "finish_unmap"; the former is appropriate in the lazyfree context, but the code following the label is executed for other successful unmap code paths too, so 'discard' does not sound correct for them. Signed-off-by: Dev Jain --- mm/rmap.c | 95 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/mm/rmap.c b/mm/rmap.c index a98acdea0530a..bd4e3639e26ed 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -1978,6 +1978,56 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, FPB_RESPECT_WRITE | FPB_RESPECT_SOFT_DIRTY); } +static inline bool can_unmap_lazyfree_folio_range(struct vm_area_struct *vma, + struct folio *folio, unsigned long address, pte_t *ptep, + pte_t pteval, unsigned long nr_pages) +{ + struct mm_struct *mm = vma->vm_mm; + int ref_count, map_count; + + /* + * Synchronize with gup_pte_range(): + * - clear PTE; barrier; read refcount + * - inc refcount; barrier; read PTE + */ + smp_mb(); + + ref_count = folio_ref_count(folio); + map_count = folio_mapcount(folio); + + /* + * Order reads for page refcount and dirty flag + * (see comments in __remove_mapping()). + */ + smp_rmb(); + + if (folio_test_dirty(folio) && !(vma->vm_flags & VM_DROPPABLE)) { + /* + * redirtied either using the page table or a previously + * obtained GUP reference. + */ + set_ptes(mm, address, ptep, pteval, nr_pages); + folio_set_swapbacked(folio); + return false; + } + + if (ref_count != 1 + map_count) { + /* + * Additional reference. Could be a GUP reference or any + * speculative reference. GUP users must mark the folio + * dirty if there was a modification. This folio cannot be + * reclaimed right now either way, so act just like nothing + * happened. + * We'll come back here later and detect if the folio was + * dirtied when the additional reference is gone. + */ + set_ptes(mm, address, ptep, pteval, nr_pages); + return false; + } + + return true; +} + /* Returns false if unmap needs to be aborted */ static inline bool unmap_hugetlb_folio(struct vm_area_struct *vma, struct folio *folio, struct page_vma_mapped_walk *pvmw, @@ -2259,47 +2309,12 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, /* MADV_FREE page check */ if (!folio_test_swapbacked(folio)) { - int ref_count, map_count; - - /* - * Synchronize with gup_pte_range(): - * - clear PTE; barrier; read refcount - * - inc refcount; barrier; read PTE - */ - smp_mb(); - - ref_count = folio_ref_count(folio); - map_count = folio_mapcount(folio); - - /* - * Order reads for page refcount and dirty flag - * (see comments in __remove_mapping()). - */ - smp_rmb(); - - if (folio_test_dirty(folio) && !(vma->vm_flags & VM_DROPPABLE)) { - /* - * redirtied either using the page table or a previously - * obtained GUP reference. - */ - set_ptes(mm, address, pvmw.pte, pteval, nr_pages); - folio_set_swapbacked(folio); + if (!can_unmap_lazyfree_folio_range(vma, folio, address, + pvmw.pte, pteval, nr_pages)) goto walk_abort; - } else if (ref_count != 1 + map_count) { - /* - * Additional reference. Could be a GUP reference or any - * speculative reference. GUP users must mark the folio - * dirty if there was a modification. This folio cannot be - * reclaimed right now either way, so act just like nothing - * happened. - * We'll come back here later and detect if the folio was - * dirtied when the additional reference is gone. - */ - set_ptes(mm, address, pvmw.pte, pteval, nr_pages); - goto walk_abort; - } + add_mm_counter(mm, MM_ANONPAGES, -nr_pages); - goto discard; + goto finish_unmap; } if (folio_dup_swap(folio, subpage) < 0) { @@ -2362,7 +2377,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, */ add_mm_counter(mm, mm_counter_file(folio), -nr_pages); } -discard: +finish_unmap: if (unlikely(folio_test_hugetlb(folio))) { hugetlb_remove_rmap(folio); } else { -- 2.34.1 In preparation for the next patch, enable batch setting of uffd-wp ptes. The code paths passing nr > 1 to zap_install_uffd_wp_if_needed() produce that nr through either folio_pte_batch or swap_pte_batch, guaranteeing that all ptes are the same w.r.t belonging to the same type of VMA (anonymous or non-anonymous, wp-armed or non-wp-armed), and all being marked with uffd-wp or all being not marked. Note that we will have to use set_pte_at() in a loop instead of set_ptes() since the latter cannot handle present->non-present conversion for nr_pages > 1. Convert documentation of install_uffd_wp_ptes_if_needed to kerneldoc format. No functional change is intended. Signed-off-by: Dev Jain --- include/linux/mm_inline.h | 34 +++++++++++++++++++++------------- mm/memory.c | 20 +------------------- mm/rmap.c | 2 +- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index a171070e15f05..6f7ecede2fb45 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -566,9 +566,17 @@ static inline pte_marker copy_pte_marker( return dstm; } -/* - * If this pte is wr-protected by uffd-wp in any form, arm the special pte to - * replace a none pte. NOTE! This should only be called when *pte is already +/** + * install_uffd_wp_ptes_if_needed - install uffd-wp marker on PTEs that map + * consecutive pages of the same large folio. + * @vma: The VMA the pages are mapped into. + * @addr: Address the first page of this batch is mapped at. + * @ptep: Page table pointer for the first entry of this batch. + * @pteval: old value of the entry pointed to by ptep. + * @nr_ptes: Number of entries to clear (batch size). + * + * If the ptes were wr-protected by uffd-wp in any form, arm special ptes to + * replace none ptes. NOTE! This should only be called when *pte is already * cleared so we will never accidentally replace something valuable. Meanwhile * none pte also means we are not demoting the pte so tlb flushed is not needed. * E.g., when pte cleared the caller should have taken care of the tlb flush. @@ -576,11 +584,11 @@ static inline pte_marker copy_pte_marker( * Must be called with pgtable lock held so that no thread will see the none * pte, and if they see it, they'll fault and serialize at the pgtable lock. * - * Returns true if an uffd-wp pte was installed, false otherwise. + * Returns true if uffd-wp ptes were installed, false otherwise. */ static inline bool -pte_install_uffd_wp_if_needed(struct vm_area_struct *vma, unsigned long addr, - pte_t *pte, pte_t pteval) +install_uffd_wp_ptes_if_needed(struct vm_area_struct *vma, unsigned long addr, + pte_t *ptep, pte_t pteval, unsigned long nr_ptes) { bool arm_uffd_pte = false; @@ -588,7 +596,7 @@ pte_install_uffd_wp_if_needed(struct vm_area_struct *vma, unsigned long addr, return false; /* The current status of the pte should be "cleared" before calling */ - WARN_ON_ONCE(!pte_none(ptep_get(pte))); + WARN_ON_ONCE(!pte_none(ptep_get(ptep))); /* * NOTE: userfaultfd_wp_unpopulated() doesn't need this whole @@ -610,13 +618,13 @@ pte_install_uffd_wp_if_needed(struct vm_area_struct *vma, unsigned long addr, if (unlikely(pte_swp_uffd_wp_any(pteval))) arm_uffd_pte = true; - if (unlikely(arm_uffd_pte)) { - set_pte_at(vma->vm_mm, addr, pte, - make_pte_marker(PTE_MARKER_UFFD_WP)); - return true; - } + if (likely(!arm_uffd_pte)) + return false; - return false; + for (int i = 0; i < nr_ptes; ++i, ++ptep, addr += PAGE_SIZE) + set_pte_at(vma->vm_mm, addr, ptep, make_pte_marker(PTE_MARKER_UFFD_WP)); + + return true; } static inline bool vma_has_recency(const struct vm_area_struct *vma) diff --git a/mm/memory.c b/mm/memory.c index 0c9d9c2cbf0e0..f14311c4d2001 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1610,29 +1610,11 @@ zap_install_uffd_wp_if_needed(struct vm_area_struct *vma, unsigned long addr, pte_t *pte, int nr, struct zap_details *details, pte_t pteval) { - bool was_installed = false; - - if (!uffd_supports_wp_marker()) - return false; - - /* Zap on anonymous always means dropping everything */ - if (vma_is_anonymous(vma)) - return false; - if (zap_drop_markers(details)) return false; - for (;;) { - /* the PFN in the PTE is irrelevant. */ - if (pte_install_uffd_wp_if_needed(vma, addr, pte, pteval)) - was_installed = true; - if (--nr == 0) - break; - pte++; - addr += PAGE_SIZE; - } + return install_uffd_wp_ptes_if_needed(vma, addr, pte, pteval, nr); - return was_installed; } static __always_inline void zap_present_folio_ptes(struct mmu_gather *tlb, diff --git a/mm/rmap.c b/mm/rmap.c index bd4e3639e26ed..b17dce752a1ea 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -2266,7 +2266,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, * we may want to replace a none pte with a marker pte if * it's file-backed, so we don't lose the tracking info. */ - pte_install_uffd_wp_if_needed(vma, address, pvmw.pte, pteval); + install_uffd_wp_ptes_if_needed(vma, address, pvmw.pte, pteval, 1); /* Update high watermark before we lower rss */ update_hiwater_rss(mm); -- 2.34.1 Commit a67fe41e214f ("mm: rmap: support batched unmapping for file large folios") extended batched unmapping for file folios. That also required making install_uffd_wp_ptes_if_needed() support batching, but that was left out for the time being, and correctness was maintained by stopping batching in case the VMA the folio belongs to is marked uffd-wp. Now that we have a batched version called install_uffd_wp_ptes_if_needed, simply call that. folio_unmap_pte_batch() ensures that the original state of the ptes is either all uffd or all non-uffd, so we maintain correctness. If uffd-wp bit is there, we have the following transitions of ptes after unmapping: 1) anon folio: present -> uffd-wp swap 2) file folio: present -> uffd-wp marker We must ensure that these ptes are not reprocessed by the while loop - if the batch length is less than the number of pages in the folio, then we must skip over this batch. The page_vma_mapped_walk API ensures this - check_pte() will return true only if any of [pvmw->pfn, pvmw->pfn + nr_pages) is mapped by the pte. There is no pfn underlying either a uffd-wp swap pte or a uffd-wp marker pte, so check_pte returns false and we keep skipping until we hit a present entry, which is where we want to batch from next. Signed-off-by: Dev Jain --- mm/rmap.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mm/rmap.c b/mm/rmap.c index b17dce752a1ea..25813e3605991 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -1965,9 +1965,6 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, if (pte_unused(pte)) return 1; - if (userfaultfd_wp(vma)) - return 1; - /* * If unmap fails, we need to restore the ptes. To avoid accidentally * upgrading write permissions for ptes that were not originally @@ -2266,7 +2263,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, * we may want to replace a none pte with a marker pte if * it's file-backed, so we don't lose the tracking info. */ - install_uffd_wp_ptes_if_needed(vma, address, pvmw.pte, pteval, 1); + install_uffd_wp_ptes_if_needed(vma, address, pvmw.pte, pteval, nr_pages); /* Update high watermark before we lower rss */ update_hiwater_rss(mm); -- 2.34.1 Add folio_dup_swap_pages to handle a batch of consecutive pages. Note that folio_dup_swap already can handle a subset of this: nr_pages == 1 and nr_pages == folio_nr_pages(folio). Generalize this to any nr_pages. Currently we have a not-so-nice logic of passing in subpage == NULL if we mean to exercise the logic on the entire folio, and subpage != NULL if we want to exercise the logic on only that subpage. Remove this indirection: the caller invokes folio_dup_swap_pages() if it wants to operate on a range of pages in the folio (i.e nr_pages may be anything between 1 till folio_nr_pages()), and invokes folio_dup_swap() if it wants to operate on the entire folio. Signed-off-by: Dev Jain --- mm/rmap.c | 2 +- mm/shmem.c | 2 +- mm/swap.h | 12 ++++++++++-- mm/swapfile.c | 20 ++++++++++++-------- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/mm/rmap.c b/mm/rmap.c index 25813e3605991..352ba77d90f67 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -2314,7 +2314,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, goto finish_unmap; } - if (folio_dup_swap(folio, subpage) < 0) { + if (folio_dup_swap_pages(folio, subpage, 1) < 0) { set_pte_at(mm, address, pvmw.pte, pteval); goto walk_abort; } diff --git a/mm/shmem.c b/mm/shmem.c index bab3529af23c5..5e4f521399847 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1698,7 +1698,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, spin_unlock(&shmem_swaplist_lock); } - folio_dup_swap(folio, NULL); + folio_dup_swap(folio); shmem_delete_from_page_cache(folio, swp_to_radix_entry(folio->swap)); BUG_ON(folio_mapped(folio)); diff --git a/mm/swap.h b/mm/swap.h index a77016f2423b9..3c25f914e908b 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -206,7 +206,9 @@ extern int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp); * folio_put_swap(): does the opposite thing of folio_dup_swap(). */ int folio_alloc_swap(struct folio *folio); -int folio_dup_swap(struct folio *folio, struct page *subpage); +int folio_dup_swap(struct folio *folio); +int folio_dup_swap_pages(struct folio *folio, struct page *page, + unsigned long nr_pages); void folio_put_swap(struct folio *folio, struct page *subpage); /* For internal use */ @@ -390,7 +392,13 @@ static inline int folio_alloc_swap(struct folio *folio) return -EINVAL; } -static inline int folio_dup_swap(struct folio *folio, struct page *page) +static inline int folio_dup_swap(struct folio *folio) +{ + return -EINVAL; +} + +static inline int folio_dup_swap_pages(struct folio *folio, struct page *page, + unsigned long nr_pages) { return -EINVAL; } diff --git a/mm/swapfile.c b/mm/swapfile.c index c7e173b93e11d..28daf92839e77 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -1740,9 +1740,10 @@ int folio_alloc_swap(struct folio *folio) } /** - * folio_dup_swap() - Increase swap count of swap entries of a folio. + * folio_dup_swap_pages() - Increase swap count of swap entries of a folio. * @folio: folio with swap entries bounded. - * @subpage: if not NULL, only increase the swap count of this subpage. + * @page: the first page in the folio to increase the swap count for. + * @nr_pages: the number of pages in the folio to increase the swap count for. * * Typically called when the folio is unmapped and have its swap entry to * take its place: Swap entries allocated to a folio has count == 0 and pinned @@ -1756,23 +1757,26 @@ int folio_alloc_swap(struct folio *folio) * swap_put_entries_direct on its swap entry before this helper returns, or * the swap count may underflow. */ -int folio_dup_swap(struct folio *folio, struct page *subpage) +int folio_dup_swap_pages(struct folio *folio, struct page *page, + unsigned long nr_pages) { swp_entry_t entry = folio->swap; - unsigned long nr_pages = folio_nr_pages(folio); VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio); VM_WARN_ON_FOLIO(!folio_test_swapcache(folio), folio); - if (subpage) { - entry.val += folio_page_idx(folio, subpage); - nr_pages = 1; - } + entry.val += folio_page_idx(folio, page); return swap_dup_entries_cluster(swap_entry_to_info(entry), swp_offset(entry), nr_pages); } +int folio_dup_swap(struct folio *folio) +{ + return folio_dup_swap_pages(folio, folio_page(folio, 0), + folio_nr_pages(folio)); +} + /** * folio_put_swap() - Decrease swap count of swap entries of a folio. * @folio: folio with swap entries bounded, must be in swap cache and locked. -- 2.34.1 Add folio_put_swap_pages to handle a batch of consecutive pages. Note that folio_put_swap already can handle a subset of this: nr_pages == 1 and nr_pages == folio_nr_pages(folio). Generalize this to any nr_pages. Currently we have a not-so-nice logic of passing in subpage == NULL if we mean to exercise the logic on the entire folio, and subpage != NULL if we want to exercise the logic on only that subpage. Remove this indirection: the caller invokes folio_put_swap_pages() if it wants to operate on a range of pages in the folio (i.e nr_pages may be anything between 1 till folio_nr_pages()), and invokes folio_put_swap() if it wants to operate on the entire folio. Signed-off-by: Dev Jain --- mm/memory.c | 6 +++--- mm/rmap.c | 4 ++-- mm/shmem.c | 6 +++--- mm/swap.h | 11 +++++++++-- mm/swapfile.c | 22 +++++++++++++--------- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/mm/memory.c b/mm/memory.c index f14311c4d2001..c5605a779ce4d 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -5104,7 +5104,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) if (unlikely(folio != swapcache)) { folio_add_new_anon_rmap(folio, vma, address, RMAP_EXCLUSIVE); folio_add_lru_vma(folio, vma); - folio_put_swap(swapcache, NULL); + folio_put_swap(swapcache); } else if (!folio_test_anon(folio)) { /* * We currently only expect !anon folios that are fully @@ -5113,12 +5113,12 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) VM_WARN_ON_ONCE_FOLIO(folio_nr_pages(folio) != nr_pages, folio); VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio); folio_add_new_anon_rmap(folio, vma, address, rmap_flags); - folio_put_swap(folio, NULL); + folio_put_swap(folio); } else { VM_WARN_ON_ONCE(nr_pages != 1 && nr_pages != folio_nr_pages(folio)); folio_add_anon_rmap_ptes(folio, page, nr_pages, vma, address, rmap_flags); - folio_put_swap(folio, nr_pages == 1 ? page : NULL); + folio_put_swap_pages(folio, page, nr_pages); } VM_BUG_ON(!folio_test_anon(folio) || diff --git a/mm/rmap.c b/mm/rmap.c index 352ba77d90f67..7cbf850182187 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -2325,7 +2325,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, * so we'll not check/care. */ if (arch_unmap_one(mm, vma, address, pteval) < 0) { - folio_put_swap(folio, subpage); + folio_put_swap_pages(folio, subpage, 1); set_pte_at(mm, address, pvmw.pte, pteval); goto walk_abort; } @@ -2333,7 +2333,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, /* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive && folio_try_share_anon_rmap_pte(folio, subpage)) { - folio_put_swap(folio, subpage); + folio_put_swap_pages(folio, subpage, 1); set_pte_at(mm, address, pvmw.pte, pteval); goto walk_abort; } diff --git a/mm/shmem.c b/mm/shmem.c index 5e4f521399847..bb7e0fc305d87 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1719,7 +1719,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, /* Swap entry might be erased by racing shmem_free_swap() */ if (!error) { shmem_recalc_inode(inode, 0, -nr_pages); - folio_put_swap(folio, NULL); + folio_put_swap(folio); } /* @@ -2199,7 +2199,7 @@ static void shmem_set_folio_swapin_error(struct inode *inode, pgoff_t index, nr_pages = folio_nr_pages(folio); folio_wait_writeback(folio); - folio_put_swap(folio, NULL); + folio_put_swap(folio); swap_cache_del_folio(folio); /* * Don't treat swapin error folio as alloced. Otherwise inode->i_blocks @@ -2429,7 +2429,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index, if (sgp == SGP_WRITE) folio_mark_accessed(folio); - folio_put_swap(folio, NULL); + folio_put_swap(folio); swap_cache_del_folio(folio); folio_mark_dirty(folio); put_swap_device(si); diff --git a/mm/swap.h b/mm/swap.h index 3c25f914e908b..343547469927a 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -209,7 +209,9 @@ int folio_alloc_swap(struct folio *folio); int folio_dup_swap(struct folio *folio); int folio_dup_swap_pages(struct folio *folio, struct page *page, unsigned long nr_pages); -void folio_put_swap(struct folio *folio, struct page *subpage); +void folio_put_swap(struct folio *folio); +void folio_put_swap_pages(struct folio *folio, struct page *page, + unsigned long nr_pages); /* For internal use */ extern void __swap_cluster_free_entries(struct swap_info_struct *si, @@ -403,7 +405,12 @@ static inline int folio_dup_swap_pages(struct folio *folio, struct page *page, return -EINVAL; } -static inline void folio_put_swap(struct folio *folio, struct page *page) +static inline void folio_put_swap(struct folio *folio) +{ +} + +static inline void folio_put_swap_pages(struct folio *folio, struct page *page, + unsigned long nr_pages) { } diff --git a/mm/swapfile.c b/mm/swapfile.c index 28daf92839e77..ac576cc63b194 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -1778,31 +1778,34 @@ int folio_dup_swap(struct folio *folio) } /** - * folio_put_swap() - Decrease swap count of swap entries of a folio. + * folio_put_swap_pages() - Decrease swap count of swap entries of a folio. * @folio: folio with swap entries bounded, must be in swap cache and locked. - * @subpage: if not NULL, only decrease the swap count of this subpage. + * @page: the first page in the folio to decrease the swap count for. + * @nr_pages: the number of pages in the folio to decrease the swap count for. * * This won't free the swap slots even if swap count drops to zero, they are * still pinned by the swap cache. User may call folio_free_swap to free them. * Context: Caller must ensure the folio is locked and in the swap cache. */ -void folio_put_swap(struct folio *folio, struct page *subpage) +void folio_put_swap_pages(struct folio *folio, struct page *page, + unsigned long nr_pages) { swp_entry_t entry = folio->swap; - unsigned long nr_pages = folio_nr_pages(folio); struct swap_info_struct *si = __swap_entry_to_info(entry); VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio); VM_WARN_ON_FOLIO(!folio_test_swapcache(folio), folio); - if (subpage) { - entry.val += folio_page_idx(folio, subpage); - nr_pages = 1; - } + entry.val += folio_page_idx(folio, page); swap_put_entries_cluster(si, swp_offset(entry), nr_pages, false); } +void folio_put_swap(struct folio *folio) +{ + folio_put_swap_pages(folio, folio_page(folio, 0), folio_nr_pages(folio)); +} + /* * When we get a swap entry, if there aren't some other ways to * prevent swapoff, such as the folio in swap cache is locked, RCU @@ -2443,7 +2446,8 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd, new_pte = pte_mkuffd_wp(new_pte); setpte: set_pte_at(vma->vm_mm, addr, pte, new_pte); - folio_put_swap(swapcache, folio_file_page(swapcache, swp_offset(entry))); + folio_put_swap_pages(swapcache, + folio_file_page(swapcache, swp_offset(entry)), 1); out: if (pte) pte_unmap_unlock(pte, ptl); -- 2.34.1 To enable batched unmapping of anonymous folios, we need to handle the sharing of exclusive pages. Hence, a batched version of folio_try_share_anon_rmap_pte is required. Currently, the sole purpose of nr_pages in __folio_try_share_anon_rmap is to do some rmap sanity checks. Add helpers to clear the PageAnonExclusive bit on a batch of nr_pages. Note that __folio_try_share_anon_rmap can receive nr_pages == HPAGE_PMD_NR from the PMD path, but currently we only clear the bit on the head page. Retain this behaviour by setting nr_pages = 1 in case the caller is folio_try_share_anon_rmap_pmd. While at it, convert nr_pages to unsigned long to future-proof from overflow in case P4D-huge mappings etc get supported down the road. I haven't made such a change in each function receiving nr_pages in try_to_unmap_one - perhaps this can be done incrementally. Signed-off-by: Dev Jain --- include/linux/mm.h | 11 +++++++++++ include/linux/rmap.h | 27 ++++++++++++++++++++------- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index 31e27ff6a35fa..0b77329cf57a4 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -243,6 +243,17 @@ static inline unsigned long folio_page_idx(const struct folio *folio, return page - &folio->page; } +static __always_inline void folio_clear_pages_anon_exclusive(struct page *page, + unsigned long nr_pages) +{ + for (;;) { + ClearPageAnonExclusive(page); + if (--nr_pages == 0) + break; + ++page; + } +} + static inline struct folio *lru_to_folio(struct list_head *head) { return list_entry((head)->prev, struct folio, lru); diff --git a/include/linux/rmap.h b/include/linux/rmap.h index 8dc0871e5f001..f3b3ee3955afc 100644 --- a/include/linux/rmap.h +++ b/include/linux/rmap.h @@ -706,15 +706,19 @@ static inline int folio_try_dup_anon_rmap_pmd(struct folio *folio, } static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, - struct page *page, int nr_pages, enum pgtable_level level) + struct page *page, unsigned long nr_pages, enum pgtable_level level) { VM_WARN_ON_FOLIO(!folio_test_anon(folio), folio); VM_WARN_ON_FOLIO(!PageAnonExclusive(page), folio); __folio_rmap_sanity_checks(folio, page, nr_pages, level); + /* We only clear anon-exclusive from head page of PMD folio */ + if (level == PGTABLE_LEVEL_PMD) + nr_pages = 1; + /* device private folios cannot get pinned via GUP. */ if (unlikely(folio_is_device_private(folio))) { - ClearPageAnonExclusive(page); + folio_clear_pages_anon_exclusive(page, nr_pages); return 0; } @@ -766,7 +770,7 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, if (unlikely(folio_maybe_dma_pinned(folio))) return -EBUSY; - ClearPageAnonExclusive(page); + folio_clear_pages_anon_exclusive(page, nr_pages); /* * This is conceptually a smp_wmb() paired with the smp_rmb() in @@ -778,11 +782,12 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, } /** - * folio_try_share_anon_rmap_pte - try marking an exclusive anonymous page - * mapped by a PTE possibly shared to prepare + * folio_try_share_anon_rmap_ptes - try marking exclusive anonymous pages + * mapped by PTEs possibly shared to prepare * for KSM or temporary unmapping * @folio: The folio to share a mapping of - * @page: The mapped exclusive page + * @page: The first mapped exclusive page of the batch in the folio + * @nr_pages: The number of pages to share in the folio (batch size) * * The caller needs to hold the page table lock and has to have the page table * entries cleared/invalidated. @@ -797,11 +802,19 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, * * Returns 0 if marking the mapped page possibly shared succeeded. Returns * -EBUSY otherwise. + * + * The caller needs to hold the page table lock. */ +static inline int folio_try_share_anon_rmap_ptes(struct folio *folio, + struct page *page, unsigned long nr_pages) +{ + return __folio_try_share_anon_rmap(folio, page, nr_pages, PGTABLE_LEVEL_PTE); +} + static inline int folio_try_share_anon_rmap_pte(struct folio *folio, struct page *page) { - return __folio_try_share_anon_rmap(folio, page, 1, PGTABLE_LEVEL_PTE); + return folio_try_share_anon_rmap_ptes(folio, page, 1); } /** -- 2.34.1 Enable batch clearing of ptes, and batch swap setting of ptes for anon folio unmapping. Processing all ptes of a large folio in one go helps us batch across atomics (add_mm_counter etc), barriers (in the function __folio_try_share_anon_rmap), repeated calls to page_vma_mapped_walk(), to name a few. In general, batching helps us to execute similar code together, making the execution of the program more memory and CPU friendly. On arm64-contpte, batching also helps us avoid redundant ptep_get() calls and TLB flushes while breaking the contpte mapping. The handling of anon-exclusivity is very similar to commit cac1db8c3aad ("mm: optimize mprotect() by PTE batching"). Since folio_unmap_pte_batch() won't look at the bits of the underlying page, we need to process sub-batches of ptes pointing to pages which are same w.r.t exclusivity, and batch set only those ptes to swap ptes in one go. Hence export page_anon_exclusive_sub_batch() to internal.h and reuse it. arch_unmap_one() is only defined for sparc64; I am not comfortable regarding the nuances between retrieving the pfn from pte_pfn() or from (paddr = pte_val(oldpte) & _PAGE_PADDR_4V). (And, pte_next_pfn() can't even be called from arch_unmap_one() because that file does not include pgtable.h) So just disable the "sparc64-anon-swapbacked" case for now. We need to take care of rmap accounting (folio_remove_rmap_ptes) and reference accounting (folio_put_refs) when anon folio unmap succeeds. In case we partially batch the large folio and fail, we need to correctly do the accounting for pages which were successfully unmapped. So, put this accounting code in __unmap_anon_folio() itself, instead of doing some horrible goto jumping at the callsite of unmap_anon_folio(). Add a comment at relevant places to say that we are on a device-exclusive entry and not a present entry. If the batch length is less than the number of pages in the folio, then we must skip over this batch. The page_vma_mapped_walk API ensures this - check_pte() will return true only if any of [pvmw->pfn, pvmw->pfn + nr_pages) is mapped by the pte. There is no pfn underlying a swap pte, so check_pte returns false and we keep skipping until we hit a present pte, which is where we want to start unmapping from next. Signed-off-by: Dev Jain --- mm/internal.h | 26 +++++++ mm/mprotect.c | 17 ----- mm/rmap.c | 188 ++++++++++++++++++++++++++++++++++---------------- 3 files changed, 153 insertions(+), 78 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index 5a2ddcf68e0b6..65ccb959f6220 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -393,6 +393,32 @@ static inline unsigned int folio_pte_batch_flags(struct folio *folio, unsigned int folio_pte_batch(struct folio *folio, pte_t *ptep, pte_t pte, unsigned int max_nr); +/** + * page_anon_exclusive_sub_batch - Determine length of consecutive exclusive + * or maybe shared pages + * @start_idx: Starting index of the page array to scan from + * @max_len: Maximum length to look at + * @first_page: First page of the page array + * @expected_anon_exclusive: Whether to look for exclusive or !exclusive pages + * + * Determines length of consecutive ptes, pointing to pages being the same + * w.r.t the PageAnonExclusive bit. + * + * Context: The ptes point to consecutive pages of the same large folio. The + * ptes belong to the same PMD and VMA. + */ +static __always_inline int page_anon_exclusive_sub_batch(int start_idx, int max_len, + struct page *first_page, bool expected_anon_exclusive) +{ + int idx; + + for (idx = start_idx + 1; idx < start_idx + max_len; ++idx) { + if (expected_anon_exclusive != PageAnonExclusive(first_page + idx)) + break; + } + return idx - start_idx; +} + /** * pte_move_swp_offset - Move the swap entry offset field of a swap pte * forward or backward by delta diff --git a/mm/mprotect.c b/mm/mprotect.c index 9cbf932b028cf..949fd7022b5cf 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c @@ -138,23 +138,6 @@ static __always_inline void prot_commit_flush_ptes(struct vm_area_struct *vma, tlb_flush_pte_range(tlb, addr, nr_ptes * PAGE_SIZE); } -/* - * Get max length of consecutive ptes pointing to PageAnonExclusive() pages or - * !PageAnonExclusive() pages, starting from start_idx. Caller must enforce - * that the ptes point to consecutive pages of the same anon large folio. - */ -static __always_inline int page_anon_exclusive_sub_batch(int start_idx, int max_len, - struct page *first_page, bool expected_anon_exclusive) -{ - int idx; - - for (idx = start_idx + 1; idx < start_idx + max_len; ++idx) { - if (expected_anon_exclusive != PageAnonExclusive(first_page + idx)) - break; - } - return idx - start_idx; -} - /* * This function is a result of trying our very best to retain the * "avoid the write-fault handler" optimization. In can_change_pte_writable(), diff --git a/mm/rmap.c b/mm/rmap.c index 7cbf850182187..fc953f36d4527 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -1958,11 +1958,11 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, end_addr = pmd_addr_end(addr, vma->vm_end); max_nr = (end_addr - addr) >> PAGE_SHIFT; - /* We only support lazyfree or file folios batching for now ... */ - if (folio_test_anon(folio) && folio_test_swapbacked(folio)) + if (pte_unused(pte)) return 1; - if (pte_unused(pte)) + if (__is_defined(__HAVE_ARCH_UNMAP_ONE) && folio_test_anon(folio) && + folio_test_swapbacked(folio)) return 1; /* @@ -1975,6 +1975,122 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, FPB_RESPECT_WRITE | FPB_RESPECT_SOFT_DIRTY); } +static inline void set_swp_ptes(struct mm_struct *mm, unsigned long address, + pte_t *ptep, swp_entry_t entry, pte_t pteval, bool anon_exclusive, + unsigned long nr_pages) +{ + pte_t swp_pte = swp_entry_to_pte(entry); + + if (anon_exclusive) + swp_pte = pte_swp_mkexclusive(swp_pte); + + if (likely(pte_present(pteval))) { + if (pte_soft_dirty(pteval)) + swp_pte = pte_swp_mksoft_dirty(swp_pte); + if (pte_uffd_wp(pteval)) + swp_pte = pte_swp_mkuffd_wp(swp_pte); + } else { + /* Device-exclusive entry */ + VM_WARN_ON(nr_pages != 1); + if (pte_swp_soft_dirty(pteval)) + swp_pte = pte_swp_mksoft_dirty(swp_pte); + if (pte_swp_uffd_wp(pteval)) + swp_pte = pte_swp_mkuffd_wp(swp_pte); + } + + for (int i = 0; i < nr_pages; ++i, ++ptep, address += PAGE_SIZE) { + set_pte_at(mm, address, ptep, swp_pte); + swp_pte = pte_next_swp_offset(swp_pte); + } +} + +static inline void finish_folio_unmap(struct vm_area_struct *vma, + struct folio *folio, struct page *subpage, unsigned long nr_pages) +{ + if (unlikely(folio_test_hugetlb(folio))) + hugetlb_remove_rmap(folio); + else + folio_remove_rmap_ptes(folio, subpage, nr_pages, vma); + if (vma->vm_flags & VM_LOCKED) + mlock_drain_local(); + folio_put_refs(folio, nr_pages); +} + +static inline bool __unmap_anon_folio_range(struct vm_area_struct *vma, struct folio *folio, + struct page *subpage, unsigned long address, pte_t *ptep, + pte_t pteval, unsigned long nr_pages, bool anon_exclusive) +{ + swp_entry_t entry = page_swap_entry(subpage); + struct mm_struct *mm = vma->vm_mm; + + if (folio_dup_swap_pages(folio, subpage, nr_pages) < 0) { + set_ptes(mm, address, ptep, pteval, nr_pages); + return false; + } + + /* + * arch_unmap_one() is expected to be a NOP on + * architectures where we could have PFN swap PTEs, + * so we'll not check/care. + */ + if (arch_unmap_one(mm, vma, address, pteval) < 0) { + VM_WARN_ON(nr_pages != 1); + folio_put_swap_pages(folio, subpage, nr_pages); + set_pte_at(mm, address, ptep, pteval); + return false; + } + + /* See folio_try_share_anon_rmap(): clear PTE first. */ + if (anon_exclusive && folio_try_share_anon_rmap_ptes(folio, subpage, nr_pages)) { + folio_put_swap_pages(folio, subpage, nr_pages); + set_ptes(mm, address, ptep, pteval, nr_pages); + return false; + } + + if (list_empty(&mm->mmlist)) { + spin_lock(&mmlist_lock); + if (list_empty(&mm->mmlist)) + list_add(&mm->mmlist, &init_mm.mmlist); + spin_unlock(&mmlist_lock); + } + + add_mm_counter(mm, MM_ANONPAGES, -nr_pages); + add_mm_counter(mm, MM_SWAPENTS, nr_pages); + set_swp_ptes(mm, address, ptep, entry, pteval, anon_exclusive, nr_pages); + finish_folio_unmap(vma, folio, subpage, nr_pages); + return true; +} + +static inline bool unmap_anon_folio_range(struct vm_area_struct *vma, struct folio *folio, + struct page *first_page, unsigned long address, pte_t *ptep, + pte_t pteval, unsigned long nr_pages) +{ + bool expected_anon_exclusive; + int sub_batch_idx = 0; + int len, ret; + + for (;;) { + expected_anon_exclusive = PageAnonExclusive(first_page + sub_batch_idx); + len = page_anon_exclusive_sub_batch(sub_batch_idx, nr_pages, + first_page, expected_anon_exclusive); + ret = __unmap_anon_folio_range(vma, folio, first_page + sub_batch_idx, + address, ptep, pteval, len, expected_anon_exclusive); + if (!ret) + return ret; + + nr_pages -= len; + if (!nr_pages) + break; + + pteval = pte_advance_pfn(pteval, len); + address += len * PAGE_SIZE; + sub_batch_idx += len; + ptep += len; + } + + return true; +} + static inline bool can_unmap_lazyfree_folio_range(struct vm_area_struct *vma, struct folio *folio, unsigned long address, pte_t *ptep, pte_t pteval, unsigned long nr_pages) @@ -2095,7 +2211,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, { struct mm_struct *mm = vma->vm_mm; DEFINE_FOLIO_VMA_WALK(pvmw, folio, vma, address, 0); - bool anon_exclusive, ret = true; + bool ret = true; pte_t pteval; struct page *subpage; struct mmu_notifier_range range; @@ -2220,8 +2336,6 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, subpage = folio_page(folio, pfn - folio_pfn(folio)); address = pvmw.address; - anon_exclusive = folio_test_anon(folio) && - PageAnonExclusive(subpage); if (folio_test_hugetlb(folio)) { bool exit_walk; @@ -2255,6 +2369,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, if (pte_dirty(pteval)) folio_mark_dirty(folio); } else { + /* Device-exclusive entry */ pte_clear(mm, address, pvmw.pte); } @@ -2292,8 +2407,6 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, */ dec_mm_counter(mm, mm_counter(folio)); } else if (folio_test_anon(folio)) { - swp_entry_t entry = page_swap_entry(subpage); - pte_t swp_pte; /* * Store the swap location in the pte. * See handle_pte_fault() ... @@ -2309,57 +2422,17 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, if (!can_unmap_lazyfree_folio_range(vma, folio, address, pvmw.pte, pteval, nr_pages)) goto walk_abort; - add_mm_counter(mm, MM_ANONPAGES, -nr_pages); goto finish_unmap; } - if (folio_dup_swap_pages(folio, subpage, 1) < 0) { - set_pte_at(mm, address, pvmw.pte, pteval); + if (!unmap_anon_folio_range(vma, folio, subpage, address, + pvmw.pte, pteval, nr_pages)) goto walk_abort; - } - /* - * arch_unmap_one() is expected to be a NOP on - * architectures where we could have PFN swap PTEs, - * so we'll not check/care. - */ - if (arch_unmap_one(mm, vma, address, pteval) < 0) { - folio_put_swap_pages(folio, subpage, 1); - set_pte_at(mm, address, pvmw.pte, pteval); - goto walk_abort; - } - - /* See folio_try_share_anon_rmap(): clear PTE first. */ - if (anon_exclusive && - folio_try_share_anon_rmap_pte(folio, subpage)) { - folio_put_swap_pages(folio, subpage, 1); - set_pte_at(mm, address, pvmw.pte, pteval); - goto walk_abort; - } - if (list_empty(&mm->mmlist)) { - spin_lock(&mmlist_lock); - if (list_empty(&mm->mmlist)) - list_add(&mm->mmlist, &init_mm.mmlist); - spin_unlock(&mmlist_lock); - } - dec_mm_counter(mm, MM_ANONPAGES); - inc_mm_counter(mm, MM_SWAPENTS); - swp_pte = swp_entry_to_pte(entry); - if (anon_exclusive) - swp_pte = pte_swp_mkexclusive(swp_pte); - if (likely(pte_present(pteval))) { - if (pte_soft_dirty(pteval)) - swp_pte = pte_swp_mksoft_dirty(swp_pte); - if (pte_uffd_wp(pteval)) - swp_pte = pte_swp_mkuffd_wp(swp_pte); - } else { - if (pte_swp_soft_dirty(pteval)) - swp_pte = pte_swp_mksoft_dirty(swp_pte); - if (pte_swp_uffd_wp(pteval)) - swp_pte = pte_swp_mkuffd_wp(swp_pte); - } - set_pte_at(mm, address, pvmw.pte, swp_pte); + if (nr_pages == folio_nr_pages(folio)) + goto walk_done; + continue; } else { /* * This is a locked file-backed folio, @@ -2375,14 +2448,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, add_mm_counter(mm, mm_counter_file(folio), -nr_pages); } finish_unmap: - if (unlikely(folio_test_hugetlb(folio))) { - hugetlb_remove_rmap(folio); - } else { - folio_remove_rmap_ptes(folio, subpage, nr_pages, vma); - } - if (vma->vm_flags & VM_LOCKED) - mlock_drain_local(); - folio_put_refs(folio, nr_pages); + finish_folio_unmap(vma, folio, subpage, nr_pages); /* * If we are sure that we batched the entire folio and cleared -- 2.34.1