Thread (119 messages) flat view 119 messages, 9 authors, 6d ago
COOLING6d

[RFC PATCH 28/57] mm/collapse: remove the mechanism the engine replaces

From: Kiryl Shutsemau <hidden>
Date: 2026-08-16 22:47:12
Also in: bpf, linux-kselftest, linux-mm, lkml
Subsystem: memory management, memory management - thp (transparent huge page), the rest · Maintainers: Andrew Morton, David Hildenbrand, Linus Torvalds

From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>

Nothing reaches the old anonymous collapse any more: the entry point was
rewired to the engine, and every function below it lost its last caller.

Delete the chain: the scan, the mTHP order walk, the collapse itself,
isolation, swap-in, the copy with its success and failure paths, the PTE
release helpers, folio_pte_referenced() and the pmd-still-valid check.

What stays is what the file paths and MADV_COLLAPSE still call:
alloc_charge_folio() for a file collapse's destination,
hugepage_vma_revalidate() for the VMA check after MADV_COLLAPSE drops
mmap_lock, and count_collapse_event() and collapse_control_init_scan()
for the file scan.

Four tracepoints lose their only emitter here: mm_khugepaged_scan_pmd,
mm_collapse_huge_page, mm_collapse_huge_page_isolate and
mm_collapse_huge_page_swapin.  Their definitions stay, now without an
emitter, and the engine reports through mm_collapse_candidate.

Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
 mm/khugepaged.c | 984 +-----------------------------------------------
 1 file changed, 9 insertions(+), 975 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 895183d92fb8..6203473f4953 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -545,370 +545,6 @@ void __khugepaged_exit(struct mm_struct *mm)
 	}
 }
 
-static void collapse_control_init_scan(struct collapse_control *cc)
-{
-	memset(cc->node_load, 0, sizeof(cc->node_load));
-	nodes_clear(cc->alloc_nmask);
-	bitmap_zero(cc->eligible_ptes, MAX_PTRS_PER_PTE);
-}
-
-static void release_pte_folio(struct folio *folio)
-{
-	node_stat_mod_folio(folio,
-			NR_ISOLATED_ANON + folio_is_file_lru(folio),
-			-folio_nr_pages(folio));
-	folio_unlock(folio);
-	folio_putback_lru(folio);
-}
-
-static void release_pte_pages(pte_t *pte, pte_t *_pte,
-		struct list_head *compound_pagelist)
-{
-	struct folio *folio, *tmp;
-
-	while (--_pte >= pte) {
-		pte_t pteval = ptep_get(_pte);
-		unsigned long pfn;
-
-		if (pte_none(pteval))
-			continue;
-		VM_WARN_ON_ONCE(!pte_present(pteval));
-		pfn = pte_pfn(pteval);
-		if (is_zero_pfn(pfn))
-			continue;
-		folio = pfn_folio(pfn);
-		if (folio_test_large(folio))
-			continue;
-		release_pte_folio(folio);
-	}
-
-	list_for_each_entry_safe(folio, tmp, compound_pagelist, lru) {
-		list_del(&folio->lru);
-		release_pte_folio(folio);
-	}
-}
-
-/*
- * folio_pte_referenced() - Check if a folio or its PTE mapping was recently used
- *
- * Return: true if recent access was observed through either the folio state
- * or the current PTE mapping.
- */
-static inline bool folio_pte_referenced(struct folio *folio,
-		struct vm_area_struct *vma, unsigned long addr, pte_t pteval)
-{
-	/* The folio was referenced previously ... */
-	if (folio_test_young(folio) || folio_test_referenced(folio))
-		return true;
-	/* ... or the PTE mapping was recently used */
-	return pte_young(pteval) || mmu_notifier_test_young(vma->vm_mm, addr);
-}
-
-static void count_collapse_event(unsigned int order, enum vm_event_item vm_event,
-		enum mthp_stat_item mthp_event)
-{
-	if (is_pmd_order(order))
-		count_vm_event(vm_event);
-	count_mthp_stat(order, mthp_event);
-}
-
-static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
-		unsigned long start_addr, pte_t *pte, struct collapse_control *cc,
-		unsigned int order, struct list_head *compound_pagelist)
-{
-	const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, order);
-	const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, order);
-	const unsigned long nr_pages = 1UL << order;
-	struct page *page = NULL;
-	struct folio *folio = NULL;
-	unsigned long addr = start_addr;
-	pte_t *_pte;
-	int none_or_zero = 0, shared = 0, referenced = 0;
-	enum scan_result result = SCAN_FAIL;
-
-	for (_pte = pte; _pte < pte + nr_pages;
-	     _pte++, addr += PAGE_SIZE) {
-		pte_t pteval = ptep_get(_pte);
-		if (pte_none_or_zero(pteval)) {
-			if (++none_or_zero > max_ptes_none) {
-				result = SCAN_EXCEED_NONE_PTE;
-				count_collapse_event(order, THP_SCAN_EXCEED_NONE_PTE,
-						     MTHP_STAT_COLLAPSE_EXCEED_NONE);
-				goto out;
-			}
-			continue;
-		}
-		if (!pte_present(pteval)) {
-			result = SCAN_PTE_NON_PRESENT;
-			goto out;
-		}
-		if (pte_uffd(pteval)) {
-			result = SCAN_PTE_UFFD;
-			goto out;
-		}
-		page = vm_normal_page(vma, addr, pteval);
-		if (unlikely(!page) || unlikely(is_zone_device_page(page))) {
-			result = SCAN_PAGE_NULL;
-			goto out;
-		}
-
-		folio = page_folio(page);
-		VM_BUG_ON_FOLIO(!folio_test_anon(folio), folio);
-
-		/*
-		 * If the vma has the VM_DROPPABLE flag, the collapse will
-		 * preserve the lazyfree property without needing to skip.
-		 */
-		if (cc->policy.skip_lazyfree && !(vma->vm_flags & VM_DROPPABLE) &&
-		    folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
-			result = SCAN_PAGE_LAZYFREE;
-			goto out;
-		}
-
-		/* See collapse_scan_pmd(). */
-		if (folio_maybe_mapped_shared(folio)) {
-			/*
-			 * TODO: Support shared pages without leading to further
-			 * mTHP collapses. Currently bringing in new pages via
-			 * shared may cause a future higher order collapse on a
-			 * rescan of the same range.
-			 */
-			if (++shared > max_ptes_shared) {
-				result = SCAN_EXCEED_SHARED_PTE;
-				count_collapse_event(order, THP_SCAN_EXCEED_SHARED_PTE,
-						     MTHP_STAT_COLLAPSE_EXCEED_SHARED);
-				goto out;
-			}
-		}
-		/*
-		 * TODO: In some cases of partially-mapped folios, we'd actually
-		 * want to collapse.
-		 */
-		if (!is_pmd_order(order) && folio_order(folio) >= order) {
-			result = SCAN_PTE_MAPPED_HUGEPAGE;
-			goto out;
-		}
-
-		if (folio_test_large(folio)) {
-			struct folio *f;
-
-			/*
-			 * Check if we have dealt with the compound page
-			 * already
-			 */
-			list_for_each_entry(f, compound_pagelist, lru) {
-				if (folio == f)
-					goto next;
-			}
-		}
-
-		/*
-		 * We can do it before folio_isolate_lru because the
-		 * folio can't be freed from under us. NOTE: folio lock
-		 * is needed to serialize against split_huge_page()
-		 * when invoked from the VM.
-		 */
-		if (!folio_trylock(folio)) {
-			result = SCAN_PAGE_LOCK;
-			goto out;
-		}
-
-		/*
-		 * Check if the page has any GUP (or other external) pins.
-		 *
-		 * The page table that maps the page has been already unlinked
-		 * from the page table tree and this process cannot get
-		 * an additional pin on the page.
-		 *
-		 * New pins can come later if the page is shared across fork,
-		 * but not from this process. The other process cannot write to
-		 * the page, only trigger CoW.
-		 */
-		if (folio_expected_ref_count(folio) != folio_ref_count(folio)) {
-			folio_unlock(folio);
-			result = SCAN_PAGE_COUNT;
-			goto out;
-		}
-
-		/*
-		 * Isolate the folio to avoid collapsing a hugepage
-		 * currently in use by the VM.
-		 */
-		if (!folio_isolate_lru(folio)) {
-			folio_unlock(folio);
-			result = SCAN_DEL_PAGE_LRU;
-			goto out;
-		}
-		node_stat_mod_folio(folio,
-				NR_ISOLATED_ANON + folio_is_file_lru(folio),
-				folio_nr_pages(folio));
-		VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
-		VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
-
-		if (folio_test_large(folio))
-			list_add_tail(&folio->lru, compound_pagelist);
-next:
-		if (cc->policy.require_referenced &&
-		    folio_pte_referenced(folio, vma, addr, pteval))
-			referenced++;
-	}
-
-	if (unlikely(cc->policy.require_referenced && !referenced)) {
-		result = SCAN_LACK_REFERENCED_PAGE;
-	} else {
-		result = SCAN_SUCCEED;
-		trace_mm_collapse_huge_page_isolate(folio, none_or_zero,
-						    referenced, result, order);
-		return result;
-	}
-out:
-	release_pte_pages(pte, _pte, compound_pagelist);
-	trace_mm_collapse_huge_page_isolate(folio, none_or_zero,
-					    referenced, result, order);
-	return result;
-}
-
-static void __collapse_huge_page_copy_succeeded(pte_t *pte,
-		struct vm_area_struct *vma, unsigned long address,
-		spinlock_t *ptl, unsigned int order,
-		struct list_head *compound_pagelist)
-{
-	const unsigned long nr_pages = 1UL << order;
-	unsigned long end = address + (PAGE_SIZE * nr_pages);
-	struct folio *src, *tmp;
-	pte_t pteval;
-	pte_t *_pte;
-	unsigned int nr_ptes;
-
-	for (_pte = pte; _pte < pte + nr_pages; _pte += nr_ptes,
-	     address += nr_ptes * PAGE_SIZE) {
-		nr_ptes = 1;
-		pteval = ptep_get(_pte);
-		if (pte_none_or_zero(pteval)) {
-			add_mm_counter(vma->vm_mm, MM_ANONPAGES, 1);
-			if (pte_none(pteval))
-				continue;
-			/*
-			 * ptl mostly unnecessary.
-			 */
-			spin_lock(ptl);
-			ptep_clear(vma->vm_mm, address, _pte);
-			spin_unlock(ptl);
-			ksm_might_unmap_zero_page(vma->vm_mm, pteval);
-		} else {
-			struct page *src_page = pte_page(pteval);
-
-			src = page_folio(src_page);
-
-			if (folio_test_large(src)) {
-				unsigned int max_nr_ptes = (end - address) >> PAGE_SHIFT;
-
-				nr_ptes = folio_pte_batch(src, _pte, pteval, max_nr_ptes);
-			} else {
-				release_pte_folio(src);
-			}
-
-			/*
-			 * ptl mostly unnecessary, but preempt has to
-			 * be disabled to update the per-cpu stats
-			 * inside folio_remove_rmap_pte().
-			 */
-			spin_lock(ptl);
-			clear_ptes(vma->vm_mm, address, _pte, nr_ptes);
-			folio_remove_rmap_ptes(src, src_page, nr_ptes, vma);
-			spin_unlock(ptl);
-			free_swap_cache(src);
-			folio_put_refs(src, nr_ptes);
-		}
-	}
-
-	list_for_each_entry_safe(src, tmp, compound_pagelist, lru) {
-		list_del(&src->lru);
-		node_stat_sub_folio(src, NR_ISOLATED_ANON +
-				folio_is_file_lru(src));
-		folio_unlock(src);
-		free_swap_cache(src);
-		folio_putback_lru(src);
-	}
-}
-
-static void __collapse_huge_page_copy_failed(pte_t *pte,
-		pmd_t *pmd, pmd_t orig_pmd, struct vm_area_struct *vma,
-		unsigned int order, struct list_head *compound_pagelist)
-{
-	const unsigned long nr_pages = 1UL << order;
-	spinlock_t *pmd_ptl;
-
-	/*
-	 * Re-establish the PMD to point to the original page table
-	 * entry. Restoring PMD needs to be done prior to releasing
-	 * pages. Since pages are still isolated and locked here,
-	 * acquiring anon_vma_lock_write() is unnecessary.
-	 */
-	pmd_ptl = pmd_lock(vma->vm_mm, pmd);
-	pmd_populate(vma->vm_mm, pmd, pmd_pgtable(orig_pmd));
-	spin_unlock(pmd_ptl);
-	/*
-	 * Release both raw and compound pages isolated
-	 * in __collapse_huge_page_isolate.
-	 */
-	release_pte_pages(pte, pte + nr_pages, compound_pagelist);
-}
-
-/*
- * __collapse_huge_page_copy - attempts to copy memory contents from raw
- * pages to a hugepage. Cleans up the raw pages if copying succeeds;
- * otherwise restores the original page table and releases isolated raw pages.
- * Returns SCAN_SUCCEED if copying succeeds, otherwise returns SCAN_COPY_MC.
- *
- * @pte: starting of the PTEs to copy from
- * @folio: the new hugepage to copy contents to
- * @pmd: pointer to the new hugepage's PMD
- * @orig_pmd: the original raw pages' PMD
- * @vma: the original raw pages' virtual memory area
- * @address: starting address to copy
- * @ptl: lock on raw pages' PTEs
- * @compound_pagelist: list that stores compound pages
- */
-static enum scan_result __collapse_huge_page_copy(pte_t *pte, struct folio *folio,
-		pmd_t *pmd, pmd_t orig_pmd, struct vm_area_struct *vma,
-		unsigned long address, spinlock_t *ptl, unsigned int order,
-		struct list_head *compound_pagelist)
-{
-	const unsigned long nr_pages = 1UL << order;
-	unsigned int i;
-	enum scan_result result = SCAN_SUCCEED;
-
-	/*
-	 * Copying pages' contents is subject to memory poison at any iteration.
-	 */
-	for (i = 0; i < nr_pages; i++) {
-		pte_t pteval = ptep_get(pte + i);
-		struct page *page = folio_page(folio, i);
-		unsigned long src_addr = address + i * PAGE_SIZE;
-		struct page *src_page;
-
-		if (pte_none_or_zero(pteval)) {
-			clear_user_highpage(page, src_addr);
-			continue;
-		}
-		src_page = pte_page(pteval);
-		if (copy_mc_user_highpage(page, src_page, src_addr, vma) > 0) {
-			result = SCAN_COPY_MC;
-			break;
-		}
-	}
-
-	if (likely(result == SCAN_SUCCEED))
-		__collapse_huge_page_copy_succeeded(pte, vma, address, ptl,
-						    order, compound_pagelist);
-	else
-		__collapse_huge_page_copy_failed(pte, pmd, orig_pmd, vma,
-						 order, compound_pagelist);
-
-	return result;
-}
-
 static void khugepaged_alloc_sleep(void)
 {
 	DEFINE_WAIT(wait);
@@ -1089,119 +725,19 @@ enum scan_result find_pmd_or_thp_or_none(struct mm_struct *mm,
 	return check_pmd_state(*pmd);
 }
 
-static enum scan_result check_pmd_still_valid(struct mm_struct *mm,
-		unsigned long address, pmd_t *pmd)
+static void count_collapse_event(unsigned int order, enum vm_event_item vm_event,
+		enum mthp_stat_item mthp_event)
 {
-	pmd_t *new_pmd;
-	enum scan_result result = find_pmd_or_thp_or_none(mm, address, &new_pmd);
-
-	if (result != SCAN_SUCCEED)
-		return result;
-	if (new_pmd != pmd)
-		return SCAN_FAIL;
-	return SCAN_SUCCEED;
+	if (is_pmd_order(order))
+		count_vm_event(vm_event);
+	count_mthp_stat(order, mthp_event);
 }
 
-/*
- * Bring missing pages in from swap, to complete THP collapse.
- * Only done if collapse_scan_pmd() believes it is worthwhile.
- *
- * For mTHP orders the function bails on the first swap entry, because
- * faulting pages back in during collapse could re-populate PTEs that
- * push a later scan over the threshold for a higher-order collapse.
- *
- * Called and returns without pte mapped or spinlocks held.
- * Returns result: if not SCAN_SUCCEED, mmap_lock has been released.
- */
-static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
-		struct vm_area_struct *vma, unsigned long start_addr,
-		pmd_t *pmd, int referenced, unsigned int order)
+static void collapse_control_init_scan(struct collapse_control *cc)
 {
-	int swapped_in = 0;
-	vm_fault_t ret = 0;
-	unsigned long addr, end = start_addr + (PAGE_SIZE << order);
-	enum scan_result result;
-	pte_t *pte = NULL;
-	spinlock_t *ptl;
-
-	for (addr = start_addr; addr < end; addr += PAGE_SIZE) {
-		struct vm_fault vmf = {
-			.vma = vma,
-			.address = addr,
-			.pgoff = linear_page_index(vma, addr),
-			.flags = FAULT_FLAG_ALLOW_RETRY,
-			.pmd = pmd,
-		};
-
-		if (!pte++) {
-			/*
-			 * Here the ptl is only used to check pte_same() in
-			 * do_swap_page(), so readonly version is enough.
-			 */
-			pte = pte_offset_map_ro_nolock(mm, pmd, addr, &ptl);
-			if (!pte) {
-				mmap_read_unlock(mm);
-				result = SCAN_NO_PTE_TABLE;
-				goto out;
-			}
-		}
-
-		vmf.orig_pte = ptep_get_lockless(pte);
-		if (pte_none(vmf.orig_pte) ||
-		    pte_present(vmf.orig_pte))
-			continue;
-
-		/*
-		 * TODO: Support swapin without leading to further mTHP
-		 * collapses. Currently bringing in new pages via swapin may
-		 * cause a future higher order collapse on a rescan of the same
-		 * range.
-		 */
-		if (!is_pmd_order(order)) {
-			count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_SWAP);
-			pte_unmap(pte);
-			mmap_read_unlock(mm);
-			result = SCAN_EXCEED_SWAP_PTE;
-			goto out;
-		}
-
-		vmf.pte = pte;
-		vmf.ptl = ptl;
-		ret = do_swap_page(&vmf);
-		/* Which unmaps pte (after perhaps re-checking the entry) */
-		pte = NULL;
-
-		/*
-		 * do_swap_page() returns VM_FAULT_RETRY with released mmap_lock.
-		 * Note we treat VM_FAULT_RETRY as VM_FAULT_ERROR here because
-		 * we do not retry here and swap entry will remain in pagetable
-		 * resulting in later failure.
-		 */
-		if (ret & VM_FAULT_RETRY) {
-			/* Likely, but not guaranteed, that page lock failed */
-			result = SCAN_PAGE_LOCK;
-			goto out;
-		}
-		if (ret & VM_FAULT_ERROR) {
-			mmap_read_unlock(mm);
-			result = SCAN_FAIL;
-			goto out;
-		}
-		swapped_in++;
-	}
-
-	if (pte)
-		pte_unmap(pte);
-
-	/* Drain LRU cache to remove extra pin on the swapped in pages */
-	if (swapped_in)
-		lru_add_drain();
-
-	result = SCAN_SUCCEED;
-out:
-	trace_mm_collapse_huge_page_swapin(mm, swapped_in, referenced, result,
-					   order);
-	return result;
+	memset(cc->node_load, 0, sizeof(cc->node_load));
+	nodes_clear(cc->alloc_nmask);
+	bitmap_zero(cc->eligible_ptes, MAX_PTRS_PER_PTE);
 }
 
 static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_struct *mm,
@@ -1234,197 +770,6 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru
 	return SCAN_SUCCEED;
 }
 
-/*
- * collapse_huge_page() expects the mmap_lock to be unlocked before entering and
- * will always return with the lock unlocked, to avoid holding the mmap_lock
- * while allocating a THP, as that could trigger direct reclaim/compaction.
- * Note that the VMA must be rechecked after grabbing the mmap_lock again.
- */
-static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long start_addr,
-		int referenced, int unmapped, struct collapse_control *cc,
-		unsigned int order)
-{
-	const unsigned long pmd_addr = start_addr & HPAGE_PMD_MASK;
-	const unsigned long end_addr = start_addr + (PAGE_SIZE << order);
-	LIST_HEAD(compound_pagelist);
-	pmd_t *pmd, _pmd;
-	pte_t *pte = NULL;
-	pgtable_t pgtable;
-	struct folio *folio;
-	spinlock_t *pmd_ptl, *pte_ptl;
-	enum scan_result result = SCAN_FAIL;
-	struct vm_area_struct *vma;
-	struct mmu_notifier_range range;
-	bool anon_vma_locked = false;
-
-	result = alloc_charge_folio(&folio, mm, cc, order);
-	if (result != SCAN_SUCCEED)
-		goto out_nolock;
-
-	if (folio_memcg_alloc_deferred(folio)) {
-		result = SCAN_ALLOC_HUGE_PAGE_FAIL;
-		goto out_nolock;
-	}
-
-	mmap_read_lock(mm);
-	result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
-					 &vma, cc, order);
-	if (result != SCAN_SUCCEED) {
-		mmap_read_unlock(mm);
-		goto out_nolock;
-	}
-
-	result = find_pmd_or_thp_or_none(mm, pmd_addr, &pmd);
-	if (result != SCAN_SUCCEED) {
-		mmap_read_unlock(mm);
-		goto out_nolock;
-	}
-
-	if (unmapped) {
-		/*
-		 * __collapse_huge_page_swapin() will return with mmap_lock
-		 * released when it fails. So we jump out_nolock directly in
-		 * that case.  Continuing to collapse causes inconsistency.
-		 */
-		result = __collapse_huge_page_swapin(mm, vma, start_addr, pmd,
-						     referenced, order);
-		if (result != SCAN_SUCCEED)
-			goto out_nolock;
-	}
-
-	mmap_read_unlock(mm);
-	/*
-	 * Prevent all access to pagetables with the exception of
-	 * gup_fast later handled by the pmdp_collapse_flush() and the VM
-	 * handled by the anon_vma lock + folio lock.
-	 *
-	 * UFFDIO_MOVE is prevented to race as well thanks to the
-	 * mmap_lock.
-	 */
-	mmap_write_lock(mm);
-	result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
-					 &vma, cc, order);
-	if (result != SCAN_SUCCEED)
-		goto out_up_write;
-	/* check if the pmd is still valid */
-	vma_start_write(vma);
-	result = check_pmd_still_valid(mm, pmd_addr, pmd);
-	if (result != SCAN_SUCCEED)
-		goto out_up_write;
-
-	anon_vma_lock_write(vma->anon_vma);
-	anon_vma_locked = true;
-
-	/*
-	 * Only notify about the PTE range we will actually modify. While we
-	 * temporary unmap the whole PTE table for mTHP collapse, we'll remap
-	 * it later, leaving other PTEs effectively unmodified. The locks we
-	 * hold prevent anybody from stumbling over such temporarily unmapped
-	 * PTE tables.
-	 */
-	mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, mm, start_addr,
-				end_addr);
-	mmu_notifier_invalidate_range_start(&range);
-
-	pmd_ptl = pmd_lock(mm, pmd); /* probably unnecessary */
-	/*
-	 * This removes any huge TLB entry from the CPU so we won't allow
-	 * huge and small TLB entries for the same virtual address to
-	 * avoid the risk of CPU bugs in that area.
-	 *
-	 * Parallel GUP-fast is fine since GUP-fast will back off when
-	 * it detects PMD is changed.
-	 */
-	_pmd = pmdp_collapse_flush(vma, pmd_addr, pmd);
-	spin_unlock(pmd_ptl);
-	mmu_notifier_invalidate_range_end(&range);
-	tlb_remove_table_sync_one();
-
-	pte = pte_offset_map_lock(mm, &_pmd, start_addr, &pte_ptl);
-	if (pte) {
-		result = __collapse_huge_page_isolate(vma, start_addr, pte, cc,
-						      order, &compound_pagelist);
-		spin_unlock(pte_ptl);
-	} else {
-		result = SCAN_NO_PTE_TABLE;
-	}
-
-	if (unlikely(result != SCAN_SUCCEED)) {
-		spin_lock(pmd_ptl);
-		VM_WARN_ON_ONCE(!pmd_none(*pmd));
-		/*
-		 * We can only use set_pmd_at() when establishing
-		 * hugepmds and never for establishing regular pmds that
-		 * points to regular pagetables. Use pmd_populate() for that
-		 */
-		pmd_populate(mm, pmd, pmd_pgtable(_pmd));
-		spin_unlock(pmd_ptl);
-		goto out_up_write;
-	}
-
-	/*
-	 * For PMD collapse all pages are isolated and locked so anon_vma
-	 * rmap can't run anymore. For mTHP collapse the PMD entry has been
-	 * removed and not all pages are isolated and locked, so we must hold
-	 * the lock to prevent neighboring folios from attempting to access
-	 * this PMD until its reinstalled.
-	 */
-	if (is_pmd_order(order)) {
-		anon_vma_unlock_write(vma->anon_vma);
-		anon_vma_locked = false;
-	}
-
-	result = __collapse_huge_page_copy(pte, folio, pmd, _pmd,
-					   vma, start_addr, pte_ptl,
-					   order, &compound_pagelist);
-	if (unlikely(result != SCAN_SUCCEED))
-		goto out_up_write;
-
-	/*
-	 * The smp_wmb() inside __folio_mark_uptodate() ensures the
-	 * copy_huge_page writes become visible before the set_pmd_at()
-	 * write.
-	 */
-	__folio_mark_uptodate(folio);
-	spin_lock(pmd_ptl);
-	VM_WARN_ON_ONCE(!pmd_none(*pmd));
-	if (is_pmd_order(order)) {
-		pgtable = pmd_pgtable(_pmd);
-		pgtable_trans_huge_deposit(mm, pmd, pgtable);
-		map_anon_folio_pmd_nopf(folio, pmd, vma, pmd_addr);
-	} else {
-		/*
-		 * Some architectures (e.g. MIPS) walk the live page table in
-		 * their implementation. update_mmu_cache_range() must be called
-		 * with a valid page table hierarchy and the PTE lock held.
-		 * Acquire it nested inside pmd_ptl when they are distinct locks.
-		 */
-		if (pte_ptl != pmd_ptl)
-			spin_lock_nested(pte_ptl, SINGLE_DEPTH_NESTING);
-		pmd_populate(mm, pmd, pmd_pgtable(_pmd));
-		map_anon_folio_pte_nopf(folio, pte, vma, start_addr,
-					  /*uffd_wp=*/ false);
-		if (pte_ptl != pmd_ptl)
-			spin_unlock(pte_ptl);
-	}
-	spin_unlock(pmd_ptl);
-
-	folio = NULL;
-
-	result = SCAN_SUCCEED;
-out_up_write:
-	if (pte)
-		pte_unmap(pte);
-	if (anon_vma_locked)
-		anon_vma_unlock_write(vma->anon_vma);
-	mmap_write_unlock(mm);
-out_nolock:
-	if (folio)
-		folio_put(folio);
-	trace_mm_collapse_huge_page(mm, result == SCAN_SUCCEED, result, order);
-	return result;
-}
-
 /* Return the highest naturally aligned order that fits at @offset within a PMD. */
 unsigned int max_order_from_offset(unsigned int offset)
 {
@@ -1434,317 +779,6 @@ unsigned int max_order_from_offset(unsigned int offset)
 	return min_t(unsigned int, __ffs(offset), HPAGE_PMD_ORDER);
 }
 
-/*
- * mthp_collapse() consumes the bitmap that is generated during
- * collapse_scan_pmd() to determine what regions and mTHP orders fit best.
- *
- * Each bit in cc->eligible_ptes marks a PTE the scan accepted as a collapse
- * source. We start at the PMD order and check if it is eligible for collapse;
- * if not, we check the left and right halves of the PTE page table we are
- * examining at a lower order.
- *
- * For each of these, we determine how many PTE entries are occupied in the
- * range of PTE entries we propose to collapse, then we compare this to a
- * threshold number of PTE entries which would need to be occupied for a
- * collapse to be permitted at that order (accounting for max_ptes_none).
- *
- * If a collapse is permitted, we attempt to collapse the PTE range into a
- * mTHP.
- */
-static enum scan_result mthp_collapse(struct mm_struct *mm,
-		unsigned long address, int referenced, int unmapped,
-		struct collapse_control *cc, unsigned long enabled_orders)
-{
-	unsigned int nr_occupied_ptes, nr_ptes, max_ptes_none;
-	enum scan_result last_result = SCAN_FAIL;
-	int collapsed = 0;
-	bool alloc_failed = false;
-	unsigned long collapse_address;
-	unsigned int offset = 0;
-	unsigned int order = HPAGE_PMD_ORDER;
-
-	while (offset < HPAGE_PMD_NR) {
-		nr_ptes = 1UL << order;
-
-		if (!test_bit(order, &enabled_orders))
-			goto next_order;
-
-		max_ptes_none = collapse_max_ptes_none(cc, NULL, order);
-		nr_occupied_ptes = bitmap_weight_from(cc->eligible_ptes, offset,
-						      offset + nr_ptes);
-
-		/*
-		 * Swap PTEs accepted during the scan are counted in @unmapped,
-		 * not in the eligible bitmap. Account them for the PMD-order
-		 * candidate.
-		 */
-		if (is_pmd_order(order))
-			nr_occupied_ptes += unmapped;
-
-		if (nr_occupied_ptes >= nr_ptes - max_ptes_none) {
-			enum scan_result ret;
-
-			collapse_address = address + offset * PAGE_SIZE;
-			ret = collapse_huge_page(mm, collapse_address, referenced,
-						 unmapped, cc, order);
-
-			switch (ret) {
-			/* Cases where we continue to next collapse candidate */
-			case SCAN_SUCCEED:
-				collapsed += nr_ptes;
-				fallthrough;
-			case SCAN_PTE_MAPPED_HUGEPAGE:
-				goto next_offset;
-			/* Cases where lower orders might still succeed */
-			case SCAN_ALLOC_HUGE_PAGE_FAIL:
-				alloc_failed = true;
-				fallthrough;
-			case SCAN_LACK_REFERENCED_PAGE:
-			case SCAN_EXCEED_NONE_PTE:
-			case SCAN_EXCEED_SWAP_PTE:
-			case SCAN_EXCEED_SHARED_PTE:
-			case SCAN_PAGE_LOCK:
-			case SCAN_PAGE_COUNT:
-			case SCAN_PAGE_NULL:
-			case SCAN_DEL_PAGE_LRU:
-			case SCAN_PTE_NON_PRESENT:
-			case SCAN_PTE_UFFD:
-			case SCAN_PAGE_LAZYFREE:
-				last_result = ret;
-				goto next_order;
-			/* Cases where no further collapse is possible */
-			case SCAN_PMD_MAPPED:
-				fallthrough;
-			default:
-				last_result = ret;
-				goto done;
-			}
-		}
-
-next_order:
-		/*
-		 * Continue with the next smaller order if there is still
-		 * any smaller order enabled. When at the smallest order
-		 * we must always move to the next offset.
-		 */
-		if (order > COLLAPSE_MIN_MTHP_ORDER &&
-		    (enabled_orders & GENMASK(order - 1, 0))) {
-			order--;
-			continue;
-		}
-next_offset:
-		/*
-		 * Advance past the region we just processed and determine the
-		 * highest order we can attempt next. Since huge pages must be
-		 * naturally aligned, the max order we can attempt next is
-		 * limited by the alignment of the new offset.
-		 * E.g. if we collapsed a order-2 mTHP at offset 0, offset
-		 * becomes 4 and __ffs(4) == 2, so the next attempt starts at
-		 * order 2.
-		 */
-		offset += nr_ptes;
-		order = max_order_from_offset(offset);
-	}
-done:
-	if (collapsed)
-		return SCAN_SUCCEED;
-	if (alloc_failed)
-		return SCAN_ALLOC_HUGE_PAGE_FAIL;
-	return last_result;
-}
-
-static enum scan_result __maybe_unused
-collapse_scan_pmd(struct mm_struct *mm,
-		struct vm_area_struct *vma, unsigned long start_addr,
-		bool *lock_dropped, struct collapse_control *cc)
-{
-	const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);
-	const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
-	unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
-	enum tva_type tva_flags = cc->policy.tva_type;
-	pmd_t *pmd;
-	pte_t *pte, *_pte, pteval;
-	int i;
-	int none_or_zero = 0, shared = 0, referenced = 0;
-	enum scan_result result = SCAN_FAIL;
-	struct page *page = NULL;
-	struct folio *folio = NULL;
-	unsigned long addr;
-	unsigned long enabled_orders;
-	spinlock_t *ptl;
-	int node = NUMA_NO_NODE, unmapped = 0;
-
-	VM_BUG_ON(start_addr & ~HPAGE_PMD_MASK);
-
-	result = find_pmd_or_thp_or_none(mm, start_addr, &pmd);
-	if (result != SCAN_SUCCEED) {
-		cc->progress++;
-		goto out;
-	}
-
-	collapse_control_init_scan(cc);
-
-	enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags);
-
-	/*
-	 * If PMD is the only enabled order, enforce max_ptes_none, otherwise
-	 * scan all pages to populate the bitmap for mTHP collapse. The bitmap
-	 * is then checked again in mthp_collapse() for each attempted order.
-	 */
-	if (enabled_orders != BIT(HPAGE_PMD_ORDER))
-		max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
-
-	pte = pte_offset_map_lock(mm, pmd, start_addr, &ptl);
-	if (!pte) {
-		cc->progress++;
-		result = SCAN_NO_PTE_TABLE;
-		goto out;
-	}
-
-	for (i = 0; i < HPAGE_PMD_NR; i++) {
-		_pte = pte + i;
-		addr = start_addr + i * PAGE_SIZE;
-		pteval = ptep_get(_pte);
-
-		cc->progress++;
-
-		if (pte_none_or_zero(pteval)) {
-			if (++none_or_zero > max_ptes_none) {
-				result = SCAN_EXCEED_NONE_PTE;
-				count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_NONE_PTE,
-						     MTHP_STAT_COLLAPSE_EXCEED_NONE);
-				goto out_unmap;
-			}
-			continue;
-		}
-		if (!pte_present(pteval)) {
-			if (++unmapped > max_ptes_swap) {
-				result = SCAN_EXCEED_SWAP_PTE;
-				count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_SWAP_PTE,
-						     MTHP_STAT_COLLAPSE_EXCEED_SWAP);
-				goto out_unmap;
-			}
-			/*
-			 * Always be strict with uffd-wp
-			 * enabled swap entries.  Please see
-			 * comment below for pte_uffd().
-			 */
-			if (pte_swp_uffd_any(pteval)) {
-				result = SCAN_PTE_UFFD;
-				goto out_unmap;
-			}
-			continue;
-		}
-		if (pte_uffd(pteval)) {
-			/*
-			 * Don't collapse the page if any of the small
-			 * PTEs are armed with uffd write protection.
-			 * Here we can also mark the new huge pmd as
-			 * write protected if any of the small ones is
-			 * marked but that could bring unknown
-			 * userfault messages that falls outside of
-			 * the registered range.  So, just be simple.
-			 */
-			result = SCAN_PTE_UFFD;
-			goto out_unmap;
-		}
-
-		page = vm_normal_page(vma, addr, pteval);
-		if (unlikely(!page) || unlikely(is_zone_device_page(page))) {
-			result = SCAN_PAGE_NULL;
-			goto out_unmap;
-		}
-		folio = page_folio(page);
-
-		/*
-		 * If the vma has the VM_DROPPABLE flag, the collapse will
-		 * preserve the lazyfree property without needing to skip.
-		 */
-		if (cc->policy.skip_lazyfree && !(vma->vm_flags & VM_DROPPABLE) &&
-		    folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
-			result = SCAN_PAGE_LAZYFREE;
-			goto out_unmap;
-		}
-
-		if (!folio_test_anon(folio)) {
-			result = SCAN_PAGE_ANON;
-			goto out_unmap;
-		}
-
-		/*
-		 * We treat a single page as shared if any part of the THP
-		 * is shared.
-		 */
-		if (folio_maybe_mapped_shared(folio)) {
-			if (++shared > max_ptes_shared) {
-				result = SCAN_EXCEED_SHARED_PTE;
-				count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_SHARED_PTE,
-						     MTHP_STAT_COLLAPSE_EXCEED_SHARED);
-				goto out_unmap;
-			}
-		}
-
-		__set_bit(i, cc->eligible_ptes);
-		/*
-		 * Record which node the original page is from and save this
-		 * information to cc->node_load[].
-		 * Khugepaged will allocate hugepage from the node has the max
-		 * hit record.
-		 */
-		node = folio_nid(folio);
-		if (collapse_scan_abort(node, cc)) {
-			result = SCAN_SCAN_ABORT;
-			goto out_unmap;
-		}
-		cc->node_load[node]++;
-		if (!folio_test_lru(folio)) {
-			result = SCAN_PAGE_LRU;
-			goto out_unmap;
-		}
-		if (folio_test_locked(folio)) {
-			result = SCAN_PAGE_LOCK;
-			goto out_unmap;
-		}
-
-		/*
-		 * Check if the page has any GUP (or other external) pins.
-		 *
-		 * Here the check is racy, but such case is ephemeral and
-		 * we could always retry collapse later. Anyway the same
-		 * check will be done again later the risk seems low.
-		 */
-		if (folio_expected_ref_count(folio) != folio_ref_count(folio)) {
-			result = SCAN_PAGE_COUNT;
-			goto out_unmap;
-		}
-
-		if (cc->policy.require_referenced &&
-		    folio_pte_referenced(folio, vma, addr, pteval))
-			referenced++;
-	}
-	if (cc->policy.require_referenced &&
-		   (!referenced ||
-		    (unmapped && referenced < HPAGE_PMD_NR / 2))) {
-		result = SCAN_LACK_REFERENCED_PAGE;
-	} else {
-		result = SCAN_SUCCEED;
-	}
-out_unmap:
-	pte_unmap_unlock(pte, ptl);
-	if (result == SCAN_SUCCEED) {
-		/* collapse_huge_page() expects the lock to be dropped before calling */
-		mmap_read_unlock(mm);
-		result = mthp_collapse(mm, start_addr, referenced,
-				       unmapped, cc, enabled_orders);
-		/* mmap_lock was released above, set lock_dropped */
-		*lock_dropped = true;
-	}
-out:
-	trace_mm_khugepaged_scan_pmd(mm, folio, referenced,
-				     none_or_zero, result, unmapped);
-	return result;
-}
-
 static void collect_mm_slot(struct mm_slot *slot)
 {
 	struct mm_struct *mm = slot->mm;
-- 
2.54.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help