From: Andrew Morton <akpm@linux-foundation.org>
To: mm-commits@vger.kernel.org,ziy@nvidia.com,vbabka@kernel.org,ryan.roberts@arm.com,ljs@kernel.org,liam@infradead.org,lance.yang@linux.dev,jannh@google.com,dev.jain@arm.com,david@kernel.org,baolin.wang@linux.alibaba.com,baohua@kernel.org,kas@kernel.org,akpm@linux-foundation.org
Subject: + mm-collapse-state-what-a-collapse-may-do-in-the-policy.patch added to mm-new branch
Date: Thu, 10 Sep 2026 14:56:00 -0700 [thread overview]
Message-ID: <20260910215600.9FC6C1F000FF@smtp.kernel.org> (raw)
The patch titled
Subject: mm/collapse: state what a collapse may do in the policy
has been added to the -mm mm-new branch. Its filename is
mm-collapse-state-what-a-collapse-may-do-in-the-policy.patch
This patch will shortly appear at
https://git.kernel.org/pub/scm/linux/kernel/git/akpm/25-new.git/tree/patches/mm-collapse-state-what-a-collapse-may-do-in-the-policy.patch
This patch will later appear in the mm-new branch at
git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Note, mm-new is a provisional staging ground for work-in-progress
patches, and acceptance into mm-new is a notification for others take
notice and to finish up reviews. Please do not hesitate to respond to
review feedback and post updated versions to replace or incrementally
fixup patches in mm-new.
The mm-new branch of mm.git is not included in linux-next
If a few days of testing in mm-new is successful, the patch will me moved
into mm.git's mm-unstable branch, which is included in linux-next
Before you just go and hit "reply", please:
a) Consider who else should be cc'ed
b) Prefer to cc a suitable mailing list as well
c) Ideally: find the original patch on the mailing list and do a
reply-to-all to that, adding suitable additional cc's
*** Remember to use Documentation/process/submit-checklist.rst when testing your code ***
The -mm tree is included into linux-next via various
branches at git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
and is updated there most days
------------------------------------------------------
From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
Subject: mm/collapse: state what a collapse may do in the policy
Date: Thu, 10 Sep 2026 13:02:25 +0100
Tests scattered through the collapse path decide what a collapse is
allowed to do by asking whether khugepaged started it. Between them they
settle:
- which VMAs are eligible, and how hard to try for a folio;
- how many empty, swapped-out or shared PTEs a window may contain, and
whether a sub-PMD window is held to a stricter rule than a PMD;
- whether a range has to look used, and whether a MADV_FREE'd page is
left alone;
- whether the PMD is mapped as part of the request, and whether dirty
pages are worth writing back and retrying.
None of those is a fact about khugepaged. Each is something the caller
decided before asking, and the collapse code should not have to look up
who called to find out.
Add struct collapse_policy for the caller to fill: khugepaged from its own
settings, MADV_COLLAPSE from the fact that a user asked explicitly. Every
test becomes a read of a field, and cc->is_khugepaged goes, having no
reader left.
khugepaged fills the policy once per scan pass, MADV_COLLAPSE once per
call. That is the one change in behaviour. The max_ptes_* limits and the
defrag setting behind the allocation mask are sampled once per pass rather
than on every table. A table scanned early in a pass and one scanned late
are then judged alike.
collapse_file() also drops a NULL check on the collapse_control. It has
one call site, reached only from collapse_single_pmd(), which dereferences
cc unconditionally, so the check was already dead.
Assisted-by: LLM
Link: https://lore.kernel.org/20260910120238.2529819-6-kirill@shutemov.name
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Barry Song <baohua@kernel.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Jann Horn <jannh@google.com>
Cc: Lance Yang <lance.yang@linux.dev>
Cc: Liam R. Howlett <liam@infradead.org>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Cc: Zi Yan <ziy@nvidia.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---
mm/collapse.h | 31 ++++++++++++
mm/khugepaged.c | 114 +++++++++++++++++++++++++---------------------
2 files changed, 93 insertions(+), 52 deletions(-)
--- a/mm/collapse.h~mm-collapse-state-what-a-collapse-may-do-in-the-policy
+++ a/mm/collapse.h
@@ -45,8 +45,37 @@ enum scan_result {
SCAN_PAGE_DIRTY_OR_WRITEBACK,
};
+/* What a collapse is allowed to do, decided by the caller that asks for it */
+struct collapse_policy {
+ /* Limits, stated per PMD; HPAGE_PMD_NR means "no limit" */
+ unsigned int max_ptes_none;
+ unsigned int max_ptes_swap;
+ unsigned int max_ptes_shared;
+
+ /* Take no swapped-out or shared PTE into a sub-PMD collapse */
+ bool strict_sub_pmd;
+
+ /* Leave clean lazyfree folios to reclaim rather than collapse them */
+ bool skip_lazyfree;
+
+ /* Refuse a range with no sign of use */
+ bool require_referenced;
+
+ /* Map the PMD over a file collapse instead of leaving it to a fault */
+ bool install_pmd;
+
+ /* Write dirty pages back and retry once instead of refusing them */
+ bool writeback_dirty;
+
+ /* How hard to try for a destination folio */
+ gfp_t gfp;
+
+ /* Which VMAs are eligible, as thp_vma_allowable_orders() spells it */
+ enum tva_type tva_type;
+};
+
struct collapse_control {
- bool is_khugepaged;
+ struct collapse_policy policy;
/* Num pages scanned per node */
u32 node_load[MAX_NUMNODES];
--- a/mm/khugepaged.c~mm-collapse-state-what-a-collapse-may-do-in-the-policy
+++ a/mm/khugepaged.c
@@ -314,15 +314,12 @@ static bool pte_none_or_zero(pte_t pte)
static unsigned int collapse_max_ptes_none(struct collapse_control *cc,
struct vm_area_struct *vma, unsigned int order)
{
- const unsigned int max_ptes_none = khugepaged_max_ptes_none;
+ const unsigned int max_ptes_none = cc->policy.max_ptes_none;
if (vma && userfaultfd_armed(vma))
return 0;
- /* for MADV_COLLAPSE, allow any empty/shared zeropage PTEs */
- if (!cc->is_khugepaged)
- return HPAGE_PMD_NR;
- /* for PMD collapse, respect the user defined maximum */
- if (is_pmd_order(order))
+ /* The limit as given, at the PMD order and wherever it is not capped */
+ if (is_pmd_order(order) || !cc->policy.strict_sub_pmd)
return max_ptes_none;
/*
* for mTHP collapse with the sysctl value set to COLLAPSE_MAX_PTES_LIMIT,
@@ -354,19 +351,12 @@ static unsigned int collapse_max_ptes_sh
unsigned int order)
{
/*
- * For MADV_COLLAPSE, do not restrict the number of PTEs that map shared
- * anonymous pages.
- */
- if (!cc->is_khugepaged)
- return HPAGE_PMD_NR;
- /*
- * for mTHP collapse do not allow collapsing anonymous memory pages that
- * are shared between processes.
+ * A sub-PMD window held to the strict rule takes no shared page at all:
+ * an mTHP is not worth the CoW-breaking.
*/
- if (!is_pmd_order(order))
+ if (!is_pmd_order(order) && cc->policy.strict_sub_pmd)
return 0;
- /* for PMD collapse, respect the user defined maximum */
- return khugepaged_max_ptes_shared;
+ return cc->policy.max_ptes_shared;
}
/**
@@ -382,16 +372,12 @@ static unsigned int collapse_max_ptes_sw
unsigned int order)
{
/*
- * For MADV_COLLAPSE, do not restrict the number PTEs entries or
- * pagecache entries that are non-present.
+ * A sub-PMD window held to the strict rule takes nothing non-present:
+ * reading pages back to build an mTHP is not worth the latency.
*/
- if (!cc->is_khugepaged)
- return HPAGE_PMD_NR;
- /* for mTHP collapse do not allow any non-present PTEs or pagecache entries */
- if (!is_pmd_order(order))
+ if (!is_pmd_order(order) && cc->policy.strict_sub_pmd)
return 0;
- /* for PMD collapse, respect the user defined maximum */
- return khugepaged_max_ptes_swap;
+ return cc->policy.max_ptes_swap;
}
int hugepage_madvise(struct vm_area_struct *vma,
@@ -678,7 +664,7 @@ static enum scan_result __collapse_huge_
* If the vma has the VM_DROPPABLE flag, the collapse will
* preserve the lazyfree property without needing to skip.
*/
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
+ if (cc->policy.skip_lazyfree && !(vma->vm_flags & VM_DROPPABLE) &&
folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
result = SCAN_PAGE_LAZYFREE;
goto out;
@@ -767,12 +753,12 @@ static enum scan_result __collapse_huge_
if (folio_test_large(folio))
list_add_tail(&folio->lru, compound_pagelist);
next:
- if (cc->is_khugepaged &&
+ if (cc->policy.require_referenced &&
folio_pte_referenced(folio, vma, addr, pteval))
referenced++;
}
- if (unlikely(cc->is_khugepaged && !referenced)) {
+ if (unlikely(cc->policy.require_referenced && !referenced)) {
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
@@ -938,9 +924,7 @@ static void khugepaged_alloc_sleep(void)
remove_wait_queue(&khugepaged_wait, &wait);
}
-static struct collapse_control khugepaged_collapse_control = {
- .is_khugepaged = true,
-};
+static struct collapse_control khugepaged_collapse_control;
static bool collapse_scan_abort(int nid, struct collapse_control *cc)
{
@@ -976,6 +960,36 @@ static inline gfp_t alloc_hugepage_khuge
return khugepaged_defrag() ? GFP_TRANSHUGE : GFP_TRANSHUGE_LIGHT;
}
+/* khugepaged collapses on its own initiative, so it obeys its own settings */
+static void collapse_policy_khugepaged(struct collapse_policy *p)
+{
+ p->max_ptes_none = READ_ONCE(khugepaged_max_ptes_none);
+ p->max_ptes_swap = READ_ONCE(khugepaged_max_ptes_swap);
+ p->max_ptes_shared = READ_ONCE(khugepaged_max_ptes_shared);
+ p->strict_sub_pmd = true;
+ p->skip_lazyfree = true;
+ p->require_referenced = true;
+ p->install_pmd = false;
+ p->writeback_dirty = false;
+ p->gfp = alloc_hugepage_khugepaged_gfpmask();
+ p->tva_type = TVA_KHUGEPAGED;
+}
+
+/* MADV_COLLAPSE was asked for explicitly, so it is not held to those */
+static void collapse_policy_forced(struct collapse_policy *p)
+{
+ p->max_ptes_none = HPAGE_PMD_NR;
+ p->max_ptes_swap = HPAGE_PMD_NR;
+ p->max_ptes_shared = HPAGE_PMD_NR;
+ p->strict_sub_pmd = false;
+ p->skip_lazyfree = false;
+ p->require_referenced = false;
+ p->install_pmd = true;
+ p->writeback_dirty = true;
+ p->gfp = GFP_TRANSHUGE;
+ p->tva_type = TVA_FORCED_COLLAPSE;
+}
+
#ifdef CONFIG_NUMA
static int collapse_find_target_node(struct collapse_control *cc)
{
@@ -1013,8 +1027,7 @@ static enum scan_result hugepage_vma_rev
struct collapse_control *cc, unsigned int order)
{
struct vm_area_struct *vma;
- enum tva_type type = cc->is_khugepaged ? TVA_KHUGEPAGED :
- TVA_FORCED_COLLAPSE;
+ enum tva_type type = cc->policy.tva_type;
if (unlikely(collapse_test_exit_or_disable(mm)))
return SCAN_ANY_PROCESS;
@@ -1197,8 +1210,7 @@ out:
static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_struct *mm,
struct collapse_control *cc, unsigned int order)
{
- gfp_t gfp = (cc->is_khugepaged ? alloc_hugepage_khugepaged_gfpmask() :
- GFP_TRANSHUGE);
+ gfp_t gfp = cc->policy.gfp;
int node = collapse_find_target_node(cc);
struct folio *folio;
@@ -1551,7 +1563,7 @@ static enum scan_result collapse_scan_pm
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->is_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;
+ enum tva_type tva_flags = cc->policy.tva_type;
pmd_t *pmd;
pte_t *pte, *_pte, pteval;
int i;
@@ -1651,7 +1663,7 @@ static enum scan_result collapse_scan_pm
* If the vma has the VM_DROPPABLE flag, the collapse will
* preserve the lazyfree property without needing to skip.
*/
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
+ if (cc->policy.skip_lazyfree && !(vma->vm_flags & VM_DROPPABLE) &&
folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
result = SCAN_PAGE_LAZYFREE;
failed_pfn = folio_pfn(folio);
@@ -1717,13 +1729,13 @@ static enum scan_result collapse_scan_pm
goto out_unmap;
}
- if (cc->is_khugepaged &&
+ if (cc->policy.require_referenced &&
folio_pte_referenced(folio, vma, addr, pteval))
referenced++;
}
- if (cc->is_khugepaged &&
- (!referenced ||
- (unmapped && referenced < HPAGE_PMD_NR / 2))) {
+ if (cc->policy.require_referenced &&
+ (!referenced ||
+ (unmapped && referenced < HPAGE_PMD_NR / 2))) {
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
@@ -2582,11 +2594,11 @@ immap_locked:
xas_unlock_irq(&xas);
/*
- * Remove pte page tables, so we can re-fault the page as huge.
- * If MADV_COLLAPSE, adjust result to call try_collapse_pte_mapped_thp().
+ * Remove pte page tables, so we can re-fault the page as huge. A
+ * caller that wants the PMD mapped now is told to go and do that.
*/
retract_page_tables(mapping, start);
- if (cc && !cc->is_khugepaged)
+ if (cc->policy.install_pmd)
result = SCAN_PTE_MAPPED_HUGEPAGE;
folio_unlock(new_folio);
@@ -2773,11 +2785,8 @@ static enum scan_result collapse_single_
retry:
result = collapse_scan_file(mm, addr, file, pgoff, cc);
- /*
- * For MADV_COLLAPSE, when encountering dirty pages, try to writeback,
- * then retry the collapse one time.
- */
- if (!cc->is_khugepaged && result == SCAN_PAGE_DIRTY_OR_WRITEBACK &&
+ /* Dirty pages are worth a writeback and one more try, if asked for */
+ if (cc->policy.writeback_dirty && result == SCAN_PAGE_DIRTY_OR_WRITEBACK &&
!triggered_wb && mapping_can_writeback(file->f_mapping)) {
const loff_t lstart = (loff_t)pgoff << PAGE_SHIFT;
const loff_t lend = lstart + HPAGE_PMD_SIZE - 1;
@@ -2794,7 +2803,7 @@ retry:
result = SCAN_ANY_PROCESS;
else
result = try_collapse_pte_mapped_thp(mm, addr,
- !cc->is_khugepaged);
+ cc->policy.install_pmd);
if (result == SCAN_PMD_MAPPED)
result = SCAN_SUCCEED;
mmap_read_unlock(mm);
@@ -2943,6 +2952,9 @@ static void khugepaged_do_scan(struct co
lru_add_drain_all();
+ /* One policy for the whole pass, so every table is judged the same */
+ collapse_policy_khugepaged(&cc->policy);
+
cc->progress = 0;
while (true) {
cond_resched();
@@ -3170,7 +3182,7 @@ int madvise_collapse(struct vm_area_stru
cc = kmalloc_obj(*cc);
if (!cc)
return -ENOMEM;
- cc->is_khugepaged = false;
+ collapse_policy_forced(&cc->policy);
cc->progress = 0;
lru_add_drain_all();
_
Patches currently in -mm which might be from kas@kernel.org are
mm-huge_memory-do-not-touch-frozen-folios-in-deferred_split_isolate.patch
mm-huge_memory-dequeue-the-deferred-split-after-the-split-freeze.patch
mm-huge_memory-add-folio_reset_partially_mapped.patch
mm-khugepaged-drop-redundant-mm_struct-pin-in-madvise_collapse.patch
mm-khugepaged-count-collapses-where-khugepaged-makes-them.patch
mm-khugepaged-rename-mthp_present_ptes-bitmap-to-eligible_ptes.patch
mm-collapse-add-collapseh-for-the-collapse-interface.patch
mm-collapse-state-what-a-collapse-may-do-in-the-policy.patch
mm-collapse-drop-the-collapse_possible-wrapper.patch
mm-collapse-name-the-per-table-scan-reset-for-what-it-resets.patch
mm-collapse-separate-scanning-a-pte-table-from-collapsing-it.patch
mm-collapse-open-code-collapse_single_pmd-in-its-two-callers.patch
mm-collapse-work-out-the-orders-a-vma-allows-once-per-vma.patch
mm-collapse-declare-the-collapse-interface-in-collapseh.patch
mm-collapse-implement-madv_collapse-in-madvisec.patch
reply other threads:[~2026-09-10 21:56 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260910215600.9FC6C1F000FF@smtp.kernel.org \
--to=akpm@linux-foundation.org \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=david@kernel.org \
--cc=dev.jain@arm.com \
--cc=jannh@google.com \
--cc=kas@kernel.org \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=ljs@kernel.org \
--cc=mm-commits@vger.kernel.org \
--cc=ryan.roberts@arm.com \
--cc=vbabka@kernel.org \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox