The PTE loop combines page-table iteration with validation and processing of each folio batch. This hides the batching rule for large folios and the ownership transferred when a partial folio must be split.
Move one batch into a PTL-locked helper. A split candidate is returned locked and referenced so the caller can release the PTE mapping before calling split_folio(). No functional change intended. Assisted-by: LLM Signed-off-by: Gregory Price (Meta) <[email protected]> --- mm/madvise.c | 132 +++++++++++++++++++++++++-------------------------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/mm/madvise.c b/mm/madvise.c index 83b27258c9673..350b854ccc197 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -394,6 +394,54 @@ static bool madvise_lru_folio_is_filtered(struct folio *folio, (pageout_anon_only && !folio_test_anon(folio)); } +/* Return a split candidate locked and referenced for use after the PTL drop. */ +static struct folio * +madvise_lru_pte_batch_locked(pte_t *pte, unsigned long addr, + unsigned long end, struct mm_walk *walk, + struct list_head *folio_list, bool pageout_anon_only, int *nr) +{ + const struct madvise_walk_private *private = walk->private; + struct vm_area_struct *vma = walk->vma; + struct folio *folio; + pte_t ptent; + + *nr = 1; + ptent = ptep_get(pte); + if (pte_none(ptent) || !pte_present(ptent)) + return NULL; + + folio = vm_normal_folio(vma, addr, ptent); + if (!folio || folio_is_zone_device(folio)) + return NULL; + + /* Split PTE-mapped large folios before advising only part of them. */ + if (folio_test_large(folio)) { + *nr = madvise_folio_pte_batch(addr, end, folio, pte, &ptent); + if (*nr < folio_nr_pages(folio)) { + if (madvise_lru_folio_is_filtered(folio, pageout_anon_only)) + return NULL; + if (!folio_trylock(folio)) + return NULL; + folio_get(folio); + return folio; + } + } + + if (!folio_test_lru(folio) || + folio_mapcount(folio) != folio_nr_pages(folio)) + return NULL; + if (pageout_anon_only && !folio_test_anon(folio)) + return NULL; + + if (!private->pageout && pte_young(ptent)) { + clear_young_dirty_ptes(vma, addr, pte, *nr, CYDP_CLEAR_YOUNG); + tlb_remove_tlb_entries(private->tlb, pte, *nr, addr); + } + + madvise_lru_folio(folio, private->pageout, folio_list); + return NULL; +} + #ifdef CONFIG_TRANSPARENT_HUGEPAGE static void madvise_cold_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma, pmd_t *pmd, unsigned long addr, pmd_t orig_pmd) @@ -491,7 +539,7 @@ static int madvise_lru_pmd_entry(pmd_t *pmd, unsigned long addr, bool pageout = private->pageout; struct mm_struct *mm = tlb->mm; struct vm_area_struct *vma = walk->vma; - pte_t *start_pte, *pte, ptent; + pte_t *start_pte, *pte; spinlock_t *ptl; struct folio *folio = NULL; LIST_HEAD(folio_list); @@ -515,9 +563,6 @@ static int madvise_lru_pmd_entry(pmd_t *pmd, unsigned long addr, flush_tlb_batched_pending(mm); lazy_mmu_mode_enable(); for (; addr < end; pte += nr, addr += nr * PAGE_SIZE) { - nr = 1; - ptent = ptep_get(pte); - if (++batch_count == SWAP_CLUSTER_MAX) { batch_count = 0; if (need_resched()) { @@ -528,71 +573,24 @@ static int madvise_lru_pmd_entry(pmd_t *pmd, unsigned long addr, } } - if (pte_none(ptent)) - continue; - - if (!pte_present(ptent)) - continue; - - folio = vm_normal_folio(vma, addr, ptent); - if (!folio || folio_is_zone_device(folio)) - continue; - - /* - * If we encounter a large folio, only split it if it is not - * fully mapped within the range we are operating on. Otherwise - * leave it as is so that it can be swapped out whole. If we - * fail to split a folio, leave it in place and advance to the - * next pte in the range. - */ - if (folio_test_large(folio)) { - nr = madvise_folio_pte_batch(addr, end, folio, pte, &ptent); - if (nr < folio_nr_pages(folio)) { - int err; - - if (madvise_lru_folio_is_filtered(folio, pageout_anon_only)) - continue; - if (!folio_trylock(folio)) - continue; - folio_get(folio); - lazy_mmu_mode_disable(); - pte_unmap_unlock(start_pte, ptl); - start_pte = NULL; - err = split_folio(folio); - folio_unlock(folio); - folio_put(folio); - start_pte = pte = - pte_offset_map_lock(mm, pmd, addr, &ptl); - if (!start_pte) - break; - flush_tlb_batched_pending(mm); - lazy_mmu_mode_enable(); - if (!err) - nr = 0; - continue; - } - } - - /* - * Do not interfere with other mappings of this folio and - * non-LRU folio. If we have a large folio at this point, we - * know it is fully mapped so if its mapcount is the same as its - * number of pages, it must be exclusive. - */ - if (!folio_test_lru(folio) || - folio_mapcount(folio) != folio_nr_pages(folio)) - continue; - - if (pageout_anon_only && !folio_test_anon(folio)) + folio = madvise_lru_pte_batch_locked(pte, addr, end, walk, + &folio_list, pageout_anon_only, &nr); + if (!folio) continue; - if (!pageout && pte_young(ptent)) { - clear_young_dirty_ptes(vma, addr, pte, nr, - CYDP_CLEAR_YOUNG); - tlb_remove_tlb_entries(tlb, pte, nr, addr); - } - - madvise_lru_folio(folio, pageout, &folio_list); + lazy_mmu_mode_disable(); + pte_unmap_unlock(start_pte, ptl); + start_pte = NULL; + if (!split_folio(folio)) + nr = 0; + folio_unlock(folio); + folio_put(folio); + start_pte = pte_offset_map_lock(mm, pmd, addr, &ptl); + if (!start_pte) + break; + pte = start_pte; + flush_tlb_batched_pending(mm); + lazy_mmu_mode_enable(); } out: -- 2.53.0-Meta

