[PATCH v3 11/12] mm/collapse: declare the collapse interface in collapse.h
From: Kiryl Shutsemau
Date: Wed Sep 16 2026 - 05:43:46 EST
From: "Kiryl Shutsemau (Meta)" <kas@xxxxxxxxxx>
A collapse takes four calls:
- collapse_control_init() - set up the control a caller carries;
- collapse_scan_pmd() - scan one PTE table, under mmap_lock;
- collapse_run_pmd() - collapse what the scan found, no mmap_lock;
- collapse_control_release() - done with the control.
All four are static in khugepaged.c, as are collapse_possible_orders(),
which says what a VMA allows, and the revalidate a caller needs once a
collapse has given the mmap_lock up. No other file can ask for a collapse
without them.
Declare them in collapse.h, with a comment stating the order they are
called in and who holds the lock over each step. Each function says
what it needs and what it does where it is defined.
hugepage_vma_revalidate() becomes collapse_vma_revalidate(): it is part of
what a collapse offers now, not a helper of the daemon.
Preparation for implementing MADV_COLLAPSE in madvise.c.
No functional change.
Assisted-by: LLM
Signed-off-by: Kiryl Shutsemau (Meta) <kas@xxxxxxxxxx>
---
mm/collapse.h | 47 ++++++++++++++++++++++++++++++++++++++++++
mm/khugepaged.c | 55 +++++++++++++++++++++++++++++++++++++------------
2 files changed, 89 insertions(+), 13 deletions(-)
diff --git a/mm/collapse.h b/mm/collapse.h
index 9764011bad86..e52969965262 100644
--- a/mm/collapse.h
+++ b/mm/collapse.h
@@ -104,4 +104,51 @@ struct collapse_control {
pgoff_t scan_pgoff;
};
+/* Which orders a VMA may collapse to, zero when it may not collapse at all */
+unsigned long collapse_possible_orders(struct vm_area_struct *vma,
+ vm_flags_t vm_flags, enum tva_type tva_flags);
+
+/*
+ * A caller states what it allows in cc->policy and then hands over one PTE
+ * table's worth of a VMA at a time:
+ *
+ * collapse_control_init(cc) once, before the first table
+ * collapse_scan_pmd(vma, addr, ...) per table
+ * collapse_run_pmd(mm, addr, result, cc) when a scan found work
+ * collapse_control_release(cc) once, when done with the control
+ *
+ * The caller holds mmap_lock for reading over the scan and passes an address
+ * within @vma, aligned to the PTE table to scan.
+ *
+ * The scan returns with that lock still held. It only reads, and almost every
+ * table it is offered has nothing to collapse, so a caller walks a whole VMA
+ * under the one lock it took to get there. SCAN_SUCCEED means there is
+ * something to collapse. SCAN_PTE_MAPPED_HUGEPAGE means the page cache
+ * already holds the PMD folio and only the PTE table is left to retract.
+ * Both are work for the run, which is handed what the scan returned; anything
+ * else is why there is nothing to do.
+ *
+ * The run is called without the lock and returns without it, taking what it
+ * needs in between: what it does -- allocate, isolate, copy, flush -- is slow
+ * enough that a writer would wait behind it. The caller gives the lock up
+ * first, and with it @vma and anything derived under it, so a caller carrying
+ * on has to look up again with collapse_vma_revalidate(). The run revalidates
+ * for itself rather than trusting what the scan saw.
+ *
+ * A scan that found something has to be run: the file side takes a reference on
+ * the file while it still has the VMA to take it from, and the run is what
+ * gives it back.
+ */
+void collapse_control_init(struct collapse_control *cc);
+void collapse_control_release(struct collapse_control *cc);
+enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,
+ unsigned long addr, struct collapse_control *cc,
+ unsigned long orders);
+enum scan_result collapse_run_pmd(struct mm_struct *mm, unsigned long addr,
+ enum scan_result result, struct collapse_control *cc);
+enum scan_result collapse_vma_revalidate(struct mm_struct *mm,
+ unsigned long address, bool expect_anon,
+ struct vm_area_struct **vmap, struct collapse_control *cc,
+ unsigned int order);
+
#endif /* __MM_COLLAPSE_H */
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 12cb67d8df32..d4e0674a05d7 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -498,7 +498,7 @@ void __khugepaged_enter(struct mm_struct *mm)
* Check what orders are possible based on the vma and collapse type.
* This is used to determine if mTHP collapse is a viable option.
*/
-static unsigned long collapse_possible_orders(struct vm_area_struct *vma,
+unsigned long collapse_possible_orders(struct vm_area_struct *vma,
vm_flags_t vm_flags, enum tva_type tva_flags)
{
unsigned long orders;
@@ -1011,12 +1011,12 @@ static int collapse_find_target_node(struct collapse_control *cc)
#endif
/*
- * If mmap_lock temporarily dropped, revalidate vma
- * after taking the mmap_lock again.
- * Returns enum scan_result value.
+ * Find the VMA at @address again once mmap_lock has been given up and taken
+ * back, and check it still allows a collapse of @order there. The VMA has to
+ * span the whole PMD whatever @order is; with @expect_anon it also has to be
+ * anonymous and have an anon_vma. *@vmap is the VMA found, if any.
*/
-
-static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned long address,
+enum scan_result collapse_vma_revalidate(struct mm_struct *mm, unsigned long address,
bool expect_anon, struct vm_area_struct **vmap,
struct collapse_control *cc, unsigned int order)
{
@@ -1264,7 +1264,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm,
}
mmap_read_lock(mm);
- result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
+ result = collapse_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
&vma, cc, order);
if (result != SCAN_SUCCEED) {
mmap_read_unlock(mm);
@@ -1299,7 +1299,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm,
* mmap_lock.
*/
mmap_write_lock(mm);
- result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
+ result = collapse_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
&vma, cc, order);
if (result != SCAN_SUCCEED)
goto out_up_write;
@@ -2741,7 +2741,8 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
return result;
}
-static void collapse_control_init(struct collapse_control *cc)
+/* Set up a control before its first scan; cc->policy is the caller's to fill */
+void collapse_control_init(struct collapse_control *cc)
{
cc->progress = 0;
cc->scan_file = NULL;
@@ -2756,12 +2757,30 @@ static void collapse_put_scan_file(struct collapse_control *cc)
}
}
-static void collapse_control_release(struct collapse_control *cc)
+/*
+ * Done with a control. A scan that found something has to have been run by
+ * then: the file side takes a reference on the file while it still has the
+ * VMA to take it from, and the run is what gives it back.
+ */
+void collapse_control_release(struct collapse_control *cc)
{
collapse_put_scan_file(cc);
}
-static enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,
+/*
+ * Scan the PTE table of @vma at @addr for a collapse candidate. @addr is
+ * aligned to the table; @orders is what the caller allows there.
+ *
+ * Called with mmap_lock held for reading and returns with it still held. It
+ * only reads, and almost every table it is offered has nothing to collapse,
+ * so a caller walks a whole VMA under the one lock it took to get there.
+ *
+ * SCAN_SUCCEED means there is something to collapse. SCAN_PTE_MAPPED_HUGEPAGE
+ * means the page cache already holds the PMD folio and only the PTE table is
+ * left to retract. Both are work for collapse_run_pmd(), which is handed
+ * what the scan returned; anything else is why there is nothing to do.
+ */
+enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,
unsigned long addr, struct collapse_control *cc,
unsigned long orders)
{
@@ -2793,7 +2812,17 @@ static enum scan_result collapse_scan_pmd(struct vm_area_struct *vma,
return result;
}
-static enum scan_result collapse_run_pmd(struct mm_struct *mm,
+/*
+ * Collapse the table a scan found work in. @result is what the scan
+ * returned.
+ *
+ * Called without mmap_lock and returns without it, taking what it needs in
+ * between: what it does -- allocate, isolate, copy, flush -- is slow enough
+ * that a writer would wait behind it. The caller gives the lock up first,
+ * and with it the VMA and anything derived under it. The run revalidates for
+ * itself rather than trusting what the scan saw.
+ */
+enum scan_result collapse_run_pmd(struct mm_struct *mm,
unsigned long addr, enum scan_result result,
struct collapse_control *cc)
{
@@ -3246,7 +3275,7 @@ int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
if (!vma) {
cond_resched();
mmap_read_lock(mm);
- result = hugepage_vma_revalidate(mm, addr, false, &found,
+ result = collapse_vma_revalidate(mm, addr, false, &found,
cc, HPAGE_PMD_ORDER);
if (result != SCAN_SUCCEED) {
last_fail = result;
--
2.54.0