* [RFC PATCH v2 01/11] mm: khugepaged: add max_ptes_none check in collapse_file()
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 02/11] mm: khugepaged: generalize collapse_file() for shmem mTHP support Baolin Wang
` (10 subsequent siblings)
11 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
Similar to the anonymous folios collapse, we should also check the
'max_ptes_none' when trying to collapse shmem/file folios, which is
also intended as preparation for shmem mTHP collapse in the
following patches.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
mm/khugepaged.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index b12187709f6d..631459172e19 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2234,6 +2234,7 @@ static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff)
static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
struct file *file, pgoff_t start, struct collapse_control *cc)
{
+ const unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL, HPAGE_PMD_ORDER);
struct address_space *mapping = file->f_mapping;
struct page *dst;
struct folio *folio, *tmp, *new_folio;
@@ -2299,7 +2300,13 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
goto xa_locked;
}
}
- nr_none++;
+
+ if (++nr_none > max_ptes_none) {
+ result = SCAN_EXCEED_NONE_PTE;
+ count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
+ goto xa_locked;
+ }
+
index++;
continue;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v2 02/11] mm: khugepaged: generalize collapse_file() for shmem mTHP support
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 01/11] mm: khugepaged: add max_ptes_none check in collapse_file() Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 03/11] mm: khugepaged: add an order check for PMD-sized THP statistics Baolin Wang
` (9 subsequent siblings)
11 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
Generalize the order of the collapse_file() function to support future
shmem mTHP collapse.
No functional changes in this patch.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
mm/khugepaged.c | 27 +++++++++++++++------------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 631459172e19..4adc8c6de062 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2214,6 +2214,7 @@ static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff)
* @file: file that collapse on
* @start: collapse start address
* @cc: collapse context and scratchpad
+ * @order: folio order being collapsed to
*
* Basic scheme is simple, details are more complex:
* - allocate and lock a new huge page;
@@ -2232,15 +2233,17 @@ static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff)
* + unlock and free huge page;
*/
static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
- struct file *file, pgoff_t start, struct collapse_control *cc)
+ struct file *file, pgoff_t start, struct collapse_control *cc,
+ int order)
{
- const unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL, HPAGE_PMD_ORDER);
+ const unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL, order);
struct address_space *mapping = file->f_mapping;
+ const unsigned long nr_pages = 1UL << order;
struct page *dst;
struct folio *folio, *tmp, *new_folio;
- pgoff_t index = 0, end = start + HPAGE_PMD_NR;
+ pgoff_t index = 0, end = start + nr_pages;
LIST_HEAD(pagelist);
- XA_STATE_ORDER(xas, &mapping->i_pages, start, HPAGE_PMD_ORDER);
+ XA_STATE_ORDER(xas, &mapping->i_pages, start, order);
enum scan_result result = SCAN_SUCCEED;
int nr_none = 0;
bool is_shmem = shmem_file(file);
@@ -2252,9 +2255,9 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
* mapping, the shmem check can be removed.
*/
VM_WARN_ON_ONCE(!is_shmem && !mapping_pmd_folio_support(mapping));
- VM_WARN_ON_ONCE(start & (HPAGE_PMD_NR - 1));
+ VM_WARN_ON_ONCE(start & (nr_pages - 1));
- result = alloc_charge_folio(&new_folio, mm, cc, HPAGE_PMD_ORDER);
+ result = alloc_charge_folio(&new_folio, mm, cc, order);
if (result != SCAN_SUCCEED)
goto out;
@@ -2591,12 +2594,12 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
}
if (is_shmem) {
- lruvec_stat_mod_folio(new_folio, NR_SHMEM, HPAGE_PMD_NR);
+ lruvec_stat_mod_folio(new_folio, NR_SHMEM, nr_pages);
lruvec_stat_mod_folio(new_folio, NR_SHMEM_THPS, HPAGE_PMD_NR);
} else {
lruvec_stat_mod_folio(new_folio, NR_FILE_THPS, HPAGE_PMD_NR);
}
- lruvec_stat_mod_folio(new_folio, NR_FILE_PAGES, HPAGE_PMD_NR);
+ lruvec_stat_mod_folio(new_folio, NR_FILE_PAGES, nr_pages);
/*
* Mark new_folio as uptodate before inserting it into the
@@ -2604,14 +2607,14 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
* unwritten page.
*/
folio_mark_uptodate(new_folio);
- folio_ref_add(new_folio, HPAGE_PMD_NR - 1);
+ folio_ref_add(new_folio, nr_pages - 1);
if (is_shmem)
folio_mark_dirty(new_folio);
folio_add_lru(new_folio);
/* Join all the small entries into a single multi-index entry. */
- xas_set_order(&xas, start, HPAGE_PMD_ORDER);
+ xas_set_order(&xas, start, order);
xas_store(&xas, new_folio);
WARN_ON_ONCE(xas_error(&xas));
xas_unlock_irq(&xas);
@@ -2666,7 +2669,7 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
folio_put(new_folio);
out:
VM_BUG_ON(!list_empty(&pagelist));
- trace_mm_khugepaged_collapse_file(mm, new_folio, index, addr, is_shmem, file, HPAGE_PMD_NR, result);
+ trace_mm_khugepaged_collapse_file(mm, new_folio, index, addr, is_shmem, file, nr_pages, result);
return result;
}
@@ -2769,7 +2772,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
result = SCAN_EXCEED_NONE_PTE;
count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
} else {
- result = collapse_file(mm, addr, file, start, cc);
+ result = collapse_file(mm, addr, file, start, cc, HPAGE_PMD_ORDER);
}
}
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v2 03/11] mm: khugepaged: add an order check for PMD-sized THP statistics
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 01/11] mm: khugepaged: add max_ptes_none check in collapse_file() Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 02/11] mm: khugepaged: generalize collapse_file() for shmem mTHP support Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 04/11] mm: khugepaged: add shmem mTHP collapse support Baolin Wang
` (8 subsequent siblings)
11 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
In order to support shmem mTHP collapse in the following patches, add
an PMD-sized THP order check to avoid PMD-sized THP statistics errors.
No functional changes.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
mm/khugepaged.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 4adc8c6de062..0c8dfbd48410 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2595,8 +2595,9 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
if (is_shmem) {
lruvec_stat_mod_folio(new_folio, NR_SHMEM, nr_pages);
- lruvec_stat_mod_folio(new_folio, NR_SHMEM_THPS, HPAGE_PMD_NR);
- } else {
+ if (is_pmd_order(order))
+ lruvec_stat_mod_folio(new_folio, NR_SHMEM_THPS, HPAGE_PMD_NR);
+ } else if (is_pmd_order(order)) {
lruvec_stat_mod_folio(new_folio, NR_FILE_THPS, HPAGE_PMD_NR);
}
lruvec_stat_mod_folio(new_folio, NR_FILE_PAGES, nr_pages);
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v2 04/11] mm: khugepaged: add shmem mTHP collapse support
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
` (2 preceding siblings ...)
2026-06-10 10:29 ` [RFC PATCH v2 03/11] mm: khugepaged: add an order check for PMD-sized THP statistics Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 12:13 ` Lance Yang
2026-06-10 12:44 ` Lance Yang
2026-06-10 10:29 ` [RFC PATCH v2 05/11] mm: shmem: run khugepaged for all shmem mTHP orders Baolin Wang
` (7 subsequent siblings)
11 siblings, 2 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
Khugepaged already supports the anonymous mTHP collapse. Similarly, let
khugepaged also support the shmem mTHP collapse. The strategy for shmem
mTHP collapse follows the anonymous mTHP collapse:
Track present pages via a bitmap while scanning PMD ranges for collapse
candidates. After the scan completes, use the bitmap to determine the
most efficient mTHP order to collapse to. Scale 'max_ptes_none' by the
attempted collapse order to determine the minimum fill threshold for
eligibility. Similarly, shmem mTHP collapse rejects regions containing
swapped-out pages to avoid creep.
Currently, the collapse_pte_mapped_thp() does not build the mapping for mTHP.
Cause we still expect to establish the mTHP mapping via refault under the
control of fault_around. So collapse_pte_mapped_thp() remains responsible
only for building the mapping for PMD-sized THP, which is reasonable and
makes life easier.
Note that we do not need to remove pte page tables for shmem mTHP collapse.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
mm/khugepaged.c | 115 ++++++++++++++++++++++++++++++++++++++----------
1 file changed, 91 insertions(+), 24 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 0c8dfbd48410..818d51915748 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -135,6 +135,10 @@ static struct khugepaged_scan khugepaged_scan = {
.mm_head = LIST_HEAD_INIT(khugepaged_scan.mm_head),
};
+static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
+ struct file *file, pgoff_t start,
+ struct collapse_control *cc, int order);
+
#ifdef CONFIG_SYSFS
static ssize_t scan_sleep_millisecs_show(struct kobject *kobj,
struct kobj_attribute *attr,
@@ -1487,6 +1491,7 @@ static unsigned int max_order_from_offset(unsigned int offset)
* mTHP.
*/
static enum scan_result mthp_collapse(struct mm_struct *mm,
+ struct file *file, pgoff_t start,
unsigned long address, int referenced, int unmapped,
struct collapse_control *cc, unsigned long enabled_orders)
{
@@ -1512,8 +1517,12 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
enum scan_result ret;
collapse_address = address + offset * PAGE_SIZE;
- ret = collapse_huge_page(mm, collapse_address, referenced,
- unmapped, cc, order);
+ if (file)
+ ret = collapse_file(mm, collapse_address, file,
+ start + offset, cc, order);
+ else
+ ret = collapse_huge_page(mm, collapse_address,
+ referenced, unmapped, cc, order);
switch (ret) {
/* Cases where we continue to next collapse candidate */
@@ -1521,6 +1530,7 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
collapsed += nr_ptes;
fallthrough;
case SCAN_PTE_MAPPED_HUGEPAGE:
+ case SCAN_PAGE_COMPOUND:
goto next_offset;
/* Cases where lower orders might still succeed */
case SCAN_ALLOC_HUGE_PAGE_FAIL:
@@ -1774,7 +1784,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
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,
+ result = mthp_collapse(mm, NULL, 0, start_addr, referenced,
unmapped, cc, enabled_orders);
/* mmap_lock was released above, set lock_dropped */
*lock_dropped = true;
@@ -2306,7 +2316,9 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
if (++nr_none > max_ptes_none) {
result = SCAN_EXCEED_NONE_PTE;
- count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
+ if (is_pmd_order(order))
+ count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
+ count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_NONE);
goto xa_locked;
}
@@ -2316,6 +2328,19 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
if (xa_is_value(folio) || !folio_test_uptodate(folio)) {
xas_unlock_irq(&xas);
+
+ /*
+ * 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);
+ result = SCAN_EXCEED_SWAP_PTE;
+ goto xa_unlocked;
+ }
+
/* swap in or instantiate fallocated page */
if (shmem_get_folio(mapping->host, index, 0,
&folio, SGP_NOALLOC)) {
@@ -2399,6 +2424,18 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
goto out_unlock;
}
+ /*
+ * If the folio order is greater than the collapse order, there is
+ * no need to continue attempting to collapse.
+ * And should return SCAN_PAGE_COMPOUND instead of SCAN_PTE_MAPPED_HUGEPAGE,
+ * then we can build the mapping under the control of fault_around
+ * when refaulting.
+ */
+ if (folio_order(folio) >= order) {
+ result = SCAN_PAGE_COMPOUND;
+ goto out_unlock;
+ }
+
if (folio_mapping(folio) != mapping) {
result = SCAN_TRUNCATED;
goto out_unlock;
@@ -2621,12 +2658,11 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
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 for PMD-sized THP collapse, so we can
+ * re-fault the page as huge.
*/
- retract_page_tables(mapping, start);
- if (cc && !cc->is_khugepaged)
- result = SCAN_PTE_MAPPED_HUGEPAGE;
+ if (is_pmd_order(order))
+ retract_page_tables(mapping, start);
folio_unlock(new_folio);
/*
@@ -2675,22 +2711,35 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
}
static enum scan_result collapse_scan_file(struct mm_struct *mm,
- unsigned long addr, struct file *file, pgoff_t start,
- struct collapse_control *cc)
+ struct vm_area_struct *vma, unsigned long addr,
+ struct file *file, pgoff_t start, struct collapse_control *cc)
{
- const unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL, HPAGE_PMD_ORDER);
+ enum tva_type tva_flags = cc->is_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;
+ unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL, HPAGE_PMD_ORDER);
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
- struct folio *folio = NULL;
struct address_space *mapping = file->f_mapping;
XA_STATE(xas, &mapping->i_pages, start);
- int present, swap;
- int node = NUMA_NO_NODE;
enum scan_result result = SCAN_SUCCEED;
+ unsigned long enabled_orders, nr_pages;
+ struct folio *folio = NULL;
+ int node = NUMA_NO_NODE;
+ int present, swap;
+ pgoff_t pgoff;
present = 0;
swap = 0;
+ bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
memset(cc->node_load, 0, sizeof(cc->node_load));
nodes_clear(cc->alloc_nmask);
+
+ 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.
+ */
+ if (enabled_orders != BIT(HPAGE_PMD_ORDER))
+ max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
+
rcu_read_lock();
xas_for_each(&xas, folio, start + HPAGE_PMD_NR - 1) {
if (xas_retry(&xas, folio))
@@ -2754,7 +2803,17 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
* is just too costly...
*/
- present += folio_nr_pages(folio);
+ nr_pages = folio_nr_pages(folio);
+ present += nr_pages;
+
+ /*
+ * If there are folios present, keep track of it in the bitmap
+ * for file/shmem mTHP collapse.
+ */
+ pgoff = max_t(pgoff_t, start, folio->index) - start;
+ nr_pages = min_t(int, HPAGE_PMD_NR - pgoff, nr_pages);
+ bitmap_set(cc->mthp_present_ptes, pgoff, nr_pages);
+
folio_put(folio);
if (need_resched()) {
@@ -2768,15 +2827,23 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
else
cc->progress += HPAGE_PMD_NR;
- if (result == SCAN_SUCCEED) {
- if (present < HPAGE_PMD_NR - max_ptes_none) {
- result = SCAN_EXCEED_NONE_PTE;
- count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
- } else {
- result = collapse_file(mm, addr, file, start, cc, HPAGE_PMD_ORDER);
- }
+ if (result != SCAN_SUCCEED)
+ goto out;
+
+ if (present < HPAGE_PMD_NR - max_ptes_none) {
+ result = SCAN_EXCEED_NONE_PTE;
+ count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
+ count_mthp_stat(HPAGE_PMD_ORDER,
+ MTHP_STAT_COLLAPSE_EXCEED_NONE);
+ goto out;
}
+ result = mthp_collapse(mm, file, start, addr, 0, 0, cc, enabled_orders);
+ if (result == SCAN_SUCCEED && !cc->is_khugepaged) {
+ /* If MADV_COLLAPSE, adjust result to call collapse_pte_mapped_thp(). */
+ result = SCAN_PTE_MAPPED_HUGEPAGE;
+ }
+out:
trace_mm_khugepaged_scan_file(mm, folio, file, present, swap, result);
return result;
}
@@ -2808,7 +2875,7 @@ static enum scan_result collapse_single_pmd(unsigned long addr,
mmap_read_unlock(mm);
*lock_dropped = true;
retry:
- result = collapse_scan_file(mm, addr, file, pgoff, cc);
+ result = collapse_scan_file(mm, vma, addr, file, pgoff, cc);
/*
* For MADV_COLLAPSE, when encountering dirty pages, try to writeback,
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [RFC PATCH v2 04/11] mm: khugepaged: add shmem mTHP collapse support
2026-06-10 10:29 ` [RFC PATCH v2 04/11] mm: khugepaged: add shmem mTHP collapse support Baolin Wang
@ 2026-06-10 12:13 ` Lance Yang
2026-06-10 12:44 ` Lance Yang
1 sibling, 0 replies; 16+ messages in thread
From: Lance Yang @ 2026-06-10 12:13 UTC (permalink / raw)
To: baolin.wang
Cc: akpm, david, ljs, hughd, willy, ziy, liam, npache, ryan.roberts,
dev.jain, baohua, lance.yang, linux-mm, linux-kselftest,
linux-kernel
On Wed, Jun 10, 2026 at 06:29:12PM +0800, Baolin Wang wrote:
[...]
>@@ -2808,7 +2875,7 @@ static enum scan_result collapse_single_pmd(unsigned long addr,
> mmap_read_unlock(mm);
> *lock_dropped = true;
> retry:
>- result = collapse_scan_file(mm, addr, file, pgoff, cc);
>+ result = collapse_scan_file(mm, vma, addr, file, pgoff, cc);
Looks unsafe to pass "vma" down here, since we have already dropped
mmap_read_unlock(mm) ...
A racing munmap() could leave it stale before collapse_scan_file() use
it, no?
Cheers, Lance
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v2 04/11] mm: khugepaged: add shmem mTHP collapse support
2026-06-10 10:29 ` [RFC PATCH v2 04/11] mm: khugepaged: add shmem mTHP collapse support Baolin Wang
2026-06-10 12:13 ` Lance Yang
@ 2026-06-10 12:44 ` Lance Yang
1 sibling, 0 replies; 16+ messages in thread
From: Lance Yang @ 2026-06-10 12:44 UTC (permalink / raw)
To: baolin.wang
Cc: akpm, david, ljs, hughd, willy, ziy, liam, npache, ryan.roberts,
dev.jain, baohua, lance.yang, linux-mm, linux-kselftest,
linux-kernel
On Wed, Jun 10, 2026 at 06:29:12PM +0800, Baolin Wang wrote:
[...]
>@@ -1512,8 +1517,12 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
> enum scan_result ret;
>
> collapse_address = address + offset * PAGE_SIZE;
>- ret = collapse_huge_page(mm, collapse_address, referenced,
>- unmapped, cc, order);
>+ if (file)
>+ ret = collapse_file(mm, collapse_address, file,
>+ start + offset, cc, order);
>+ else
>+ ret = collapse_huge_page(mm, collapse_address,
>+ referenced, unmapped, cc, order);
>
> switch (ret) {
> /* Cases where we continue to next collapse candidate */
>@@ -1521,6 +1530,7 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
> collapsed += nr_ptes;
> fallthrough;
> case SCAN_PTE_MAPPED_HUGEPAGE:
Looks like SCAN_PTE_MAPPED_HUGEPAGE from collapse_file() get lost for
the PMD-order case.
Previously, collapse_file() returned it straight back to
collapse_single_pmd(), so we would run try_collapse_pte_mapped_thp().
Now it hits mthp_collapse() fitst, and that case just goes to
next_offset ...
>+ case SCAN_PAGE_COMPOUND:
> goto next_offset;
> /* Cases where lower orders might still succeed */
> case SCAN_ALLOC_HUGE_PAGE_FAIL:
[...]
Cheers, Lance
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v2 05/11] mm: shmem: run khugepaged for all shmem mTHP orders
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
` (3 preceding siblings ...)
2026-06-10 10:29 ` [RFC PATCH v2 04/11] mm: khugepaged: add shmem mTHP collapse support Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 06/11] mm: khugepaged: allow khugepaged to check all shmem mTHP-sized orders Baolin Wang
` (6 subsequent siblings)
11 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
When only non-PMD-sized mTHP is enabled (such as only 64K mTHP enabled),
we should also allow kicking khugepaged to attempt scanning and collapsing
64K shmem mTHP. Modify shmem_hpage_pmd_enabled() to support shmem mTHP
collapse, and while we are at it, rename it to make the function name
more clear.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
include/linux/shmem_fs.h | 4 ++--
mm/khugepaged.c | 2 +-
mm/shmem.c | 10 +++++-----
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
index acb8dd961b45..1ec358b40c9b 100644
--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -131,7 +131,7 @@ int shmem_unuse(unsigned int type);
unsigned long shmem_allowable_huge_orders(struct inode *inode,
struct vm_area_struct *vma, pgoff_t index,
loff_t write_end, bool shmem_huge_force);
-bool shmem_hpage_pmd_enabled(void);
+bool shmem_hpage_enabled(void);
#else
static inline unsigned long shmem_allowable_huge_orders(struct inode *inode,
struct vm_area_struct *vma, pgoff_t index,
@@ -140,7 +140,7 @@ static inline unsigned long shmem_allowable_huge_orders(struct inode *inode,
return 0;
}
-static inline bool shmem_hpage_pmd_enabled(void)
+static inline bool shmem_hpage_enabled(void)
{
return false;
}
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 818d51915748..75b18ec4a6c3 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -532,7 +532,7 @@ static bool hugepage_enabled(void)
return true;
if (anon_hpage_enabled())
return true;
- if (shmem_hpage_pmd_enabled())
+ if (shmem_hpage_enabled())
return true;
return false;
}
diff --git a/mm/shmem.c b/mm/shmem.c
index 56c23a7b15c7..a8d30a123b1f 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1792,17 +1792,17 @@ static struct folio *shmem_swapin_cluster(swp_entry_t swap, gfp_t gfp,
}
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
-bool shmem_hpage_pmd_enabled(void)
+bool shmem_hpage_enabled(void)
{
if (shmem_huge == SHMEM_HUGE_DENY)
return false;
- if (test_bit(HPAGE_PMD_ORDER, &huge_shmem_orders_always))
+ if (READ_ONCE(huge_shmem_orders_always))
return true;
- if (test_bit(HPAGE_PMD_ORDER, &huge_shmem_orders_madvise))
+ if (READ_ONCE(huge_shmem_orders_madvise))
return true;
- if (test_bit(HPAGE_PMD_ORDER, &huge_shmem_orders_within_size))
+ if (READ_ONCE(huge_shmem_orders_within_size))
return true;
- if (test_bit(HPAGE_PMD_ORDER, &huge_shmem_orders_inherit) &&
+ if (READ_ONCE(huge_shmem_orders_inherit) &&
shmem_huge != SHMEM_HUGE_NEVER)
return true;
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v2 06/11] mm: khugepaged: allow khugepaged to check all shmem mTHP-sized orders
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
` (4 preceding siblings ...)
2026-06-10 10:29 ` [RFC PATCH v2 05/11] mm: shmem: run khugepaged for all shmem mTHP orders Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 11:33 ` Lance Yang
2026-06-10 10:29 ` [RFC PATCH v2 07/11] mm: khugepaged: skip large folios that don't need to be collapsed Baolin Wang
` (5 subsequent siblings)
11 siblings, 1 reply; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
We are now ready to enable shmem mTHP collapse, allowing
thp_vma_allowable_orders() to check all permissible shmem large orders.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
mm/khugepaged.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 75b18ec4a6c3..a87918b7e18c 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -578,9 +578,14 @@ static unsigned long collapse_possible_orders(struct vm_area_struct *vma,
{
unsigned long orders;
- /* If khugepaged is scanning an anonymous vma, allow mTHP collapse */
+ /*
+ * If khugepaged is scanning an anonymous or shmem vma,
+ * allow mTHP collapse.
+ */
if ((tva_flags == TVA_KHUGEPAGED) && vma_is_anonymous(vma))
orders = THP_ORDERS_ALL_ANON;
+ else if ((tva_flags == TVA_KHUGEPAGED) && vma_is_shmem(vma))
+ orders = THP_ORDERS_ALL_FILE_DEFAULT;
else
orders = BIT(HPAGE_PMD_ORDER);
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [RFC PATCH v2 06/11] mm: khugepaged: allow khugepaged to check all shmem mTHP-sized orders
2026-06-10 10:29 ` [RFC PATCH v2 06/11] mm: khugepaged: allow khugepaged to check all shmem mTHP-sized orders Baolin Wang
@ 2026-06-10 11:33 ` Lance Yang
0 siblings, 0 replies; 16+ messages in thread
From: Lance Yang @ 2026-06-10 11:33 UTC (permalink / raw)
To: baolin.wang
Cc: akpm, david, ljs, hughd, willy, ziy, liam, npache, ryan.roberts,
dev.jain, baohua, lance.yang, linux-mm, linux-kselftest,
linux-kernel
On Wed, Jun 10, 2026 at 06:29:14PM +0800, Baolin Wang wrote:
>We are now ready to enable shmem mTHP collapse, allowing
>thp_vma_allowable_orders() to check all permissible shmem large orders.
>
>Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
>---
> mm/khugepaged.c | 7 ++++++-
> 1 file changed, 6 insertions(+), 1 deletion(-)
>
>diff --git a/mm/khugepaged.c b/mm/khugepaged.c
>index 75b18ec4a6c3..a87918b7e18c 100644
>--- a/mm/khugepaged.c
>+++ b/mm/khugepaged.c
>@@ -578,9 +578,14 @@ static unsigned long collapse_possible_orders(struct vm_area_struct *vma,
> {
> unsigned long orders;
>
>- /* If khugepaged is scanning an anonymous vma, allow mTHP collapse */
>+ /*
>+ * If khugepaged is scanning an anonymous or shmem vma,
>+ * allow mTHP collapse.
>+ */
> if ((tva_flags == TVA_KHUGEPAGED) && vma_is_anonymous(vma))
> orders = THP_ORDERS_ALL_ANON;
>+ else if ((tva_flags == TVA_KHUGEPAGED) && vma_is_shmem(vma))
>+ orders = THP_ORDERS_ALL_FILE_DEFAULT;
Hmm... for shmem, is the lower bound in mthp_collapse() expected?
#define KHUGEPAGED_MIN_MTHP_ORDER 2
#define THP_ORDERS_ALL_FILE_DEFAULT \
((BIT(MAX_PAGECACHE_ORDER + 1) - 1) & ~BIT(0))
KHUGEPAGED_MIN_MTHP_ORDER is 2, which was introduced for anon collapse.
For shmem, though, we're feeding mthp_collapse() orders from
THP_ORDERS_ALL_FILE_DEFAULT, and that only filter out order-0 ...
if (order > KHUGEPAGED_MIN_MTHP_ORDER &&
(enabled_orders & GENMASK(order - 1, 0))) {
order--;
continue;
}
So order-1 never gets a chance. The walker stops at order-2 and never
tries it, right?
> else
> orders = BIT(HPAGE_PMD_ORDER);
>
>--
>2.47.3
>
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v2 07/11] mm: khugepaged: skip large folios that don't need to be collapsed
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
` (5 preceding siblings ...)
2026-06-10 10:29 ` [RFC PATCH v2 06/11] mm: khugepaged: allow khugepaged to check all shmem mTHP-sized orders Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 08/11] selftests: mm: extend the check_huge() to support mTHP check Baolin Wang
` (4 subsequent siblings)
11 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
If a VMA has already created a mapping of large folios after a successful
mTHP collapse, we can skip those folios that exceed the 'highest_enabled_order'
when scanning the VMA range again, as they can no longer be collapsed further.
This helps prevent wasting CPU cycles.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
mm/khugepaged.c | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index a87918b7e18c..a9664ac26f11 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2724,12 +2724,12 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
struct address_space *mapping = file->f_mapping;
XA_STATE(xas, &mapping->i_pages, start);
+ unsigned int highest_enabled_order = 0;
enum scan_result result = SCAN_SUCCEED;
unsigned long enabled_orders, nr_pages;
struct folio *folio = NULL;
int node = NUMA_NO_NODE;
int present, swap;
- pgoff_t pgoff;
present = 0;
swap = 0;
@@ -2738,6 +2738,9 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
nodes_clear(cc->alloc_nmask);
enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags);
+ if (enabled_orders > 0)
+ highest_enabled_order = highest_order(enabled_orders);
+
/*
* If PMD is the only enabled order, enforce max_ptes_none, otherwise
* scan all pages to populate the bitmap for mTHP collapse.
@@ -2814,10 +2817,17 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
/*
* If there are folios present, keep track of it in the bitmap
* for file/shmem mTHP collapse.
+ *
+ * Skip those folios whose order has already exceeded the
+ * 'highest_enabled_order', meaning they cannot be collapsed
+ * into larger order folios.
*/
- pgoff = max_t(pgoff_t, start, folio->index) - start;
- nr_pages = min_t(int, HPAGE_PMD_NR - pgoff, nr_pages);
- bitmap_set(cc->mthp_present_ptes, pgoff, nr_pages);
+ if (folio_order(folio) < highest_enabled_order) {
+ pgoff_t pgoff = max_t(pgoff_t, start, folio->index) - start;
+
+ nr_pages = min_t(int, HPAGE_PMD_NR - pgoff, nr_pages);
+ bitmap_set(cc->mthp_present_ptes, pgoff, nr_pages);
+ }
folio_put(folio);
@@ -2843,6 +2853,11 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
goto out;
}
+ if (bitmap_empty(cc->mthp_present_ptes, MAX_PTRS_PER_PTE)) {
+ result = SCAN_FAIL;
+ goto out;
+ }
+
result = mthp_collapse(mm, file, start, addr, 0, 0, cc, enabled_orders);
if (result == SCAN_SUCCEED && !cc->is_khugepaged) {
/* If MADV_COLLAPSE, adjust result to call collapse_pte_mapped_thp(). */
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v2 08/11] selftests: mm: extend the check_huge() to support mTHP check
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
` (6 preceding siblings ...)
2026-06-10 10:29 ` [RFC PATCH v2 07/11] mm: khugepaged: skip large folios that don't need to be collapsed Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 09/11] selftests: mm: move gather_after_split_folio_orders() into vm_util.c file Baolin Wang
` (3 subsequent siblings)
11 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
To support checking for various sized mTHPs during mTHP collapse, extend the
check_huge() function prototype to accept two new parameters specifying the
address range and mTHP size, in preparation for the following patches.
No functional changes.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
.../selftests/mm/folio_split_race_test.c | 2 +-
tools/testing/selftests/mm/khugepaged.c | 66 ++++++++++---------
.../testing/selftests/mm/prctl_thp_disable.c | 2 +-
tools/testing/selftests/mm/soft-dirty.c | 2 +-
.../selftests/mm/split_huge_page_test.c | 14 ++--
tools/testing/selftests/mm/uffd-common.c | 4 +-
tools/testing/selftests/mm/vm_util.c | 6 +-
tools/testing/selftests/mm/vm_util.h | 6 +-
8 files changed, 55 insertions(+), 47 deletions(-)
diff --git a/tools/testing/selftests/mm/folio_split_race_test.c b/tools/testing/selftests/mm/folio_split_race_test.c
index 6329e37fff4c..45b84f7b364e 100644
--- a/tools/testing/selftests/mm/folio_split_race_test.c
+++ b/tools/testing/selftests/mm/folio_split_race_test.c
@@ -182,7 +182,7 @@ static uint64_t run_iteration(void)
for (i = 0; i < TOTAL_PAGES; i++)
fill_page(mmap_base, i);
- if (!check_huge_shmem(mmap_base, NR_PMD_PAGE, pmd_pagesize))
+ if (!check_huge_shmem(mmap_base, FILE_SIZE, NR_PMD_PAGE, pmd_pagesize))
ksft_exit_fail_msg("No shmem THP is allocated\n");
if (pthread_barrier_init(&ctl.barrier, NULL, NUM_READER_THREADS + 1) != 0)
diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index 10e8dedcb087..f69be6be0ecd 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -51,7 +51,7 @@ struct mem_ops {
void *(*setup_area)(int nr_hpages);
void (*cleanup_area)(void *p, unsigned long size);
void (*fault)(void *p, unsigned long start, unsigned long end);
- bool (*check_huge)(void *addr, int nr_hpages);
+ bool (*check_huge)(void *addr, unsigned long size, int nr_hpages, unsigned long hpage_size);
const char *name;
};
@@ -276,7 +276,7 @@ static void *alloc_hpage(struct mem_ops *ops)
ksft_print_msg("Allocate huge page...");
if (madvise_collapse_retry(p, hpage_pmd_size))
ksft_exit_fail_perror("madvise(MADV_COLLAPSE)");
- if (!ops->check_huge(p, 1))
+ if (!ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
ksft_exit_fail_perror("madvise(MADV_COLLAPSE)");
if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)");
@@ -310,9 +310,10 @@ static void anon_fault(void *p, unsigned long start, unsigned long end)
fill_memory(p, start, end);
}
-static bool anon_check_huge(void *addr, int nr_hpages)
+static bool anon_check_huge(void *addr, unsigned long size,
+ int nr_hpages, unsigned long hpage_size)
{
- return check_huge_anon(addr, nr_hpages, hpage_pmd_size);
+ return check_huge_anon(addr, size, nr_hpages, hpage_size);
}
static void *file_setup_area_common(int nr_hpages, enum file_setup_ops setup)
@@ -412,13 +413,14 @@ static void file_fault_write(void *p, unsigned long start, unsigned long end)
ksft_exit_fail_perror("madvise(MADV_POPULATE_WRITE)");
}
-static bool file_check_huge(void *addr, int nr_hpages)
+static bool file_check_huge(void *addr, unsigned long size,
+ int nr_hpages, unsigned long hpage_size)
{
switch (finfo.type) {
case VMA_FILE:
- return check_huge_file(addr, nr_hpages, hpage_pmd_size);
+ return check_huge_file(addr, size, nr_hpages, hpage_size);
case VMA_SHMEM:
- return check_huge_shmem(addr, nr_hpages, hpage_pmd_size);
+ return check_huge_shmem(addr, size, nr_hpages, hpage_size);
default:
exit(EXIT_FAILURE);
return false;
@@ -448,9 +450,10 @@ static void shmem_cleanup_area(void *p, unsigned long size)
close(finfo.fd);
}
-static bool shmem_check_huge(void *addr, int nr_hpages)
+static bool shmem_check_huge(void *addr, unsigned long size,
+ int nr_hpages, unsigned long hpage_size)
{
- return check_huge_shmem(addr, nr_hpages, hpage_pmd_size);
+ return check_huge_shmem(addr, size, nr_hpages, hpage_size);
}
static struct mem_ops __anon_ops = {
@@ -533,7 +536,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages,
ret = madvise_collapse_retry(p, nr_hpages * hpage_pmd_size);
if (((bool)ret) == expect)
fail("Fail: Bad return value");
- else if (!ops->check_huge(p, expect ? nr_hpages : 0))
+ else if (!ops->check_huge(p, nr_hpages * hpage_pmd_size, expect ? nr_hpages : 0, hpage_pmd_size))
fail("Fail: check_huge()");
else
success("OK");
@@ -545,7 +548,7 @@ static void madvise_collapse(const char *msg, char *p, int nr_hpages,
struct mem_ops *ops, bool expect)
{
/* Sanity check */
- if (!ops->check_huge(p, 0))
+ if (!ops->check_huge(p, nr_hpages * hpage_pmd_size, 0, hpage_pmd_size))
ksft_exit_fail_msg("Unexpected huge page\n");
__madvise_collapse(msg, p, nr_hpages, ops, expect);
}
@@ -554,11 +557,12 @@ static void madvise_collapse(const char *msg, char *p, int nr_hpages,
static bool wait_for_scan(const char *msg, char *p, int nr_hpages,
struct mem_ops *ops)
{
+ unsigned long size = nr_hpages * hpage_pmd_size;
int full_scans;
int timeout = 6; /* 3 seconds */
/* Sanity check */
- if (!ops->check_huge(p, 0))
+ if (!ops->check_huge(p, size, 0, hpage_pmd_size))
ksft_exit_fail_msg("Unexpected huge page\n");
madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE);
@@ -568,7 +572,7 @@ static bool wait_for_scan(const char *msg, char *p, int nr_hpages,
ksft_print_msg("%s...", msg);
while (timeout--) {
- if (ops->check_huge(p, nr_hpages))
+ if (ops->check_huge(p, size, nr_hpages, hpage_pmd_size))
break;
if (thp_read_num("khugepaged/full_scans") >= full_scans)
break;
@@ -582,6 +586,8 @@ static bool wait_for_scan(const char *msg, char *p, int nr_hpages,
static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,
struct mem_ops *ops, bool expect)
{
+ unsigned long size = nr_hpages * hpage_pmd_size;
+
/*
* read&write file collapse fails since khugepaged does not flush
* the target dirty folios
@@ -605,7 +611,7 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,
if (ops != &__anon_ops)
ops->fault(p, 0, nr_hpages * hpage_pmd_size);
- if (ops->check_huge(p, expect ? nr_hpages : 0))
+ if (ops->check_huge(p, size, expect ? nr_hpages : 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -634,7 +640,7 @@ static void alloc_at_fault(void)
p = alloc_mapping(1);
*p = 1;
ksft_print_msg("Allocate huge page on fault...");
- if (check_huge_anon(p, 1, hpage_pmd_size))
+ if (check_huge_anon(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -643,7 +649,7 @@ static void alloc_at_fault(void)
madvise(p, page_size, MADV_DONTNEED);
ksft_print_msg("Split huge PMD on MADV_DONTNEED...");
- if (check_huge_anon(p, 0, hpage_pmd_size))
+ if (check_huge_anon(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -815,7 +821,7 @@ static void collapse_single_pte_entry_compound(struct collapse_context *c, struc
madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE);
ksft_print_msg("Split huge page leaving single PTE mapping compound page...");
madvise(p + page_size, hpage_pmd_size - page_size, MADV_DONTNEED);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -836,7 +842,7 @@ static void collapse_full_of_compound(struct collapse_context *c, struct mem_ops
ksft_print_msg("Split huge page leaving single PTE page table full of compound pages...");
madvise(p, page_size, MADV_NOHUGEPAGE);
madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -858,7 +864,7 @@ static void collapse_compound_extreme(struct collapse_context *c, struct mem_ops
for (i = 0; i < hpage_pmd_nr; i++) {
madvise(BASE_ADDR, hpage_pmd_size, MADV_HUGEPAGE);
ops->fault(BASE_ADDR, 0, hpage_pmd_size);
- if (!ops->check_huge(BASE_ADDR, 1))
+ if (!ops->check_huge(BASE_ADDR, hpage_pmd_size, 1, hpage_pmd_size))
ksft_exit_fail_msg("Failed to allocate huge page\n");
madvise(BASE_ADDR, hpage_pmd_size, MADV_NOHUGEPAGE);
@@ -881,7 +887,7 @@ static void collapse_compound_extreme(struct collapse_context *c, struct mem_ops
ops->cleanup_area(BASE_ADDR, hpage_pmd_size);
ops->fault(p, 0, hpage_pmd_size);
- if (!ops->check_huge(p, 1))
+ if (!ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -903,7 +909,7 @@ static void collapse_fork(struct collapse_context *c, struct mem_ops *ops)
ksft_print_msg("Allocate small page...");
ops->fault(p, 0, page_size);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -911,7 +917,7 @@ static void collapse_fork(struct collapse_context *c, struct mem_ops *ops)
ksft_print_msg("Share small page over fork()...");
if (!fork()) {
/* Do not touch settings on child exit */
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -929,7 +935,7 @@ static void collapse_fork(struct collapse_context *c, struct mem_ops *ops)
exit_status = WEXITSTATUS(wstatus);
ksft_print_msg("Check if parent still has small page...");
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -947,7 +953,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o
ksft_print_msg("Share huge page over fork()...");
if (!fork()) {
/* Do not touch settings on child exit */
- if (ops->check_huge(p, 1))
+ if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -955,7 +961,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o
ksft_print_msg("Split huge page PMD in child process...");
madvise(p, page_size, MADV_NOHUGEPAGE);
madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -976,7 +982,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o
exit_status = WEXITSTATUS(wstatus);
ksft_print_msg("Check if parent still has huge page...");
- if (ops->check_huge(p, 1))
+ if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -995,7 +1001,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops
ksft_print_msg("Share huge page over fork()...");
if (!fork()) {
/* Do not touch settings on child exit */
- if (ops->check_huge(p, 1))
+ if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -1003,7 +1009,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops
ksft_print_msg("Trigger CoW on page %d of %d...",
hpage_pmd_nr - max_ptes_shared - 1, hpage_pmd_nr);
ops->fault(p, 0, (hpage_pmd_nr - max_ptes_shared - 1) * page_size);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -1016,7 +1022,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops
hpage_pmd_nr - max_ptes_shared, hpage_pmd_nr);
ops->fault(p, 0, (hpage_pmd_nr - max_ptes_shared) *
page_size);
- if (ops->check_huge(p, 0))
+ if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -1034,7 +1040,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops
exit_status = WEXITSTATUS(wstatus);
ksft_print_msg("Check if parent still has huge page...");
- if (ops->check_huge(p, 1))
+ if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size))
success("OK");
else
fail("Fail");
diff --git a/tools/testing/selftests/mm/prctl_thp_disable.c b/tools/testing/selftests/mm/prctl_thp_disable.c
index d8d9d1de57b8..82c6e96ea6eb 100644
--- a/tools/testing/selftests/mm/prctl_thp_disable.c
+++ b/tools/testing/selftests/mm/prctl_thp_disable.c
@@ -67,7 +67,7 @@ static int test_mmap_thp(enum thp_collapse_type madvise_buf, size_t pmdsize)
/* HACK: make sure we have a separate VMA that we can check reliably. */
mprotect(mem, pmdsize, PROT_READ);
- ret = check_huge_anon(mem, 1, pmdsize);
+ ret = check_huge_anon(mem, pmdsize, 1, pmdsize);
munmap(mmap_mem, mmap_size);
return ret;
}
diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c
index fb1864a68e1c..e198facf78bb 100644
--- a/tools/testing/selftests/mm/soft-dirty.c
+++ b/tools/testing/selftests/mm/soft-dirty.c
@@ -103,7 +103,7 @@ static void test_hugepage(int pagemap_fd, int pagesize)
for (i = 0; i < hpage_len; i++)
map[i] = (char)i;
- if (check_huge_anon(map, 1, hpage_len)) {
+ if (check_huge_anon(map, hpage_len, 1, hpage_len)) {
ksft_test_result_pass("Test %s huge page allocation\n", __func__);
clear_softdirty();
diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 32b991472f74..4cc70873a674 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -296,7 +296,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hp
unsigned long rss_anon_before, rss_anon_after;
size_t i;
- if (!check_huge_anon(one_page, nr_hpages, pmd_pagesize))
+ if (!check_huge_anon(one_page, nr_hpages * pmd_pagesize, nr_hpages, pmd_pagesize))
ksft_exit_fail_msg("No THP is allocated\n");
rss_anon_before = rss_anon();
@@ -311,7 +311,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hp
if (one_page[i] != (char)0)
ksft_exit_fail_msg("%ld byte corrupted\n", i);
- if (!check_huge_anon(one_page, 0, pmd_pagesize))
+ if (!check_huge_anon(one_page, nr_hpages * pmd_pagesize, 0, pmd_pagesize))
ksft_exit_fail_msg("Still AnonHugePages not split\n");
rss_anon_after = rss_anon();
@@ -347,7 +347,7 @@ static void split_pmd_thp_to_order(int order)
for (i = 0; i < len; i++)
one_page[i] = (char)i;
- if (!check_huge_anon(one_page, 4, pmd_pagesize))
+ if (!check_huge_anon(one_page, 4 * pmd_pagesize, 4, pmd_pagesize))
ksft_exit_fail_msg("No THP is allocated\n");
/* split all THPs */
@@ -366,7 +366,7 @@ static void split_pmd_thp_to_order(int order)
(pmd_order + 1)))
ksft_exit_fail_msg("Unexpected THP split\n");
- if (!check_huge_anon(one_page, 0, pmd_pagesize))
+ if (!check_huge_anon(one_page, 4 * pmd_pagesize, 0, pmd_pagesize))
ksft_exit_fail_msg("Still AnonHugePages not split\n");
ksft_test_result_pass("Split huge pages to order %d successful\n", order);
@@ -393,7 +393,7 @@ static void split_pte_mapped_thp(void)
for (i = 0; i < thp_area_size; i++)
thp_area[i] = (char)i;
- if (!check_huge_anon(thp_area, nr_thps, pmd_pagesize)) {
+ if (!check_huge_anon(thp_area, nr_thps * pmd_pagesize, nr_thps, pmd_pagesize)) {
ksft_test_result_skip("Not all THPs allocated\n");
goto out;
}
@@ -657,7 +657,7 @@ static int create_pagecache_thp_and_fd(const char *testfile, size_t fd_size,
force_read_pages(*addr, fd_size / pmd_pagesize, pmd_pagesize);
- if (!check_huge_file(*addr, fd_size / pmd_pagesize, pmd_pagesize)) {
+ if (!check_huge_file(*addr, fd_size, fd_size / pmd_pagesize, pmd_pagesize)) {
ksft_print_msg("No large pagecache folio generated, please provide a filesystem supporting large folio\n");
munmap(*addr, fd_size);
close(*fd);
@@ -735,7 +735,7 @@ static void split_thp_in_pagecache_to_order_at(size_t fd_size,
goto out;
}
- if (!check_huge_file(addr, 0, pmd_pagesize)) {
+ if (!check_huge_file(addr, fd_size, 0, pmd_pagesize)) {
ksft_print_msg("Still FilePmdMapped not split\n");
err = EXIT_FAILURE;
goto out;
diff --git a/tools/testing/selftests/mm/uffd-common.c b/tools/testing/selftests/mm/uffd-common.c
index edd02328f77b..777f276044e2 100644
--- a/tools/testing/selftests/mm/uffd-common.c
+++ b/tools/testing/selftests/mm/uffd-common.c
@@ -194,7 +194,9 @@ static void shmem_alias_mapping(uffd_global_test_opts_t *gopts, __u64 *start,
static void shmem_check_pmd_mapping(uffd_global_test_opts_t *gopts, void *p, int expect_nr_hpages)
{
- if (!check_huge_shmem(gopts->area_dst_alias, expect_nr_hpages,
+ unsigned long size = expect_nr_hpages * read_pmd_pagesize();
+
+ if (!check_huge_shmem(gopts->area_dst_alias, size, expect_nr_hpages,
read_pmd_pagesize()))
err("Did not find expected %d number of hugepages",
expect_nr_hpages);
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 311fc5b4513e..b43adfa92116 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -247,17 +247,17 @@ bool __check_huge(void *addr, char *pattern, int nr_hpages,
return thp == (nr_hpages * (hpage_size >> 10));
}
-bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size)
+bool check_huge_anon(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size)
{
return __check_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size);
}
-bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size)
+bool check_huge_file(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size)
{
return __check_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size);
}
-bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size)
+bool check_huge_shmem(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size)
{
return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size);
}
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index ea8fc8fdf0eb..40c6d8c4f1b8 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -90,9 +90,9 @@ void clear_softdirty(void);
bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len);
uint64_t read_pmd_pagesize(void);
unsigned long rss_anon(void);
-bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size);
-bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size);
-bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size);
+bool check_huge_anon(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size);
+bool check_huge_file(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size);
+bool check_huge_shmem(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size);
int64_t allocate_transhuge(void *ptr, int pagemap_fd);
int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags);
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v2 09/11] selftests: mm: move gather_after_split_folio_orders() into vm_util.c file
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
` (7 preceding siblings ...)
2026-06-10 10:29 ` [RFC PATCH v2 08/11] selftests: mm: extend the check_huge() to support mTHP check Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 10/11] selftests: mm: implement the mTHP-sized hugepage check helpers Baolin Wang
` (2 subsequent siblings)
11 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
Move gather_after_split_folio_orders() to vm_util.c as a helper function
in preparation for implementing checks for mTHP collapse. While we are
at it, rename this function to indicate that it is not only used for
large folio splits.
No functional changes.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
.../selftests/mm/split_huge_page_test.c | 125 +-----------------
tools/testing/selftests/mm/vm_util.c | 123 +++++++++++++++++
tools/testing/selftests/mm/vm_util.h | 2 +
3 files changed, 126 insertions(+), 124 deletions(-)
diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 4cc70873a674..86a603692826 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -104,129 +104,6 @@ static bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd,
return false;
}
-static int vaddr_pageflags_get(char *vaddr, int pagemap_fd, int kpageflags_fd,
- uint64_t *flags)
-{
- unsigned long pfn;
-
- pfn = pagemap_get_pfn(pagemap_fd, vaddr);
-
- /* non-present PFN */
- if (pfn == -1UL)
- return 1;
-
- if (pageflags_get(pfn, kpageflags_fd, flags))
- return -1;
-
- return 0;
-}
-
-/*
- * gather_after_split_folio_orders - scan through [vaddr_start, len) and record
- * folio orders
- *
- * @vaddr_start: start vaddr
- * @len: range length
- * @pagemap_fd: file descriptor to /proc/<pid>/pagemap
- * @kpageflags_fd: file descriptor to /proc/kpageflags
- * @orders: output folio order array
- * @nr_orders: folio order array size
- *
- * gather_after_split_folio_orders() scan through [vaddr_start, len) and check
- * all folios within the range and record their orders. All order-0 pages will
- * be recorded. Non-present vaddr is skipped.
- *
- * NOTE: the function is used to check folio orders after a split is performed,
- * so it assumes [vaddr_start, len) fully maps to after-split folios within that
- * range.
- *
- * Return: 0 - no error, -1 - unhandled cases
- */
-static int gather_after_split_folio_orders(char *vaddr_start, size_t len,
- int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders)
-{
- uint64_t page_flags = 0;
- int cur_order = -1;
- char *vaddr;
-
- if (pagemap_fd == -1 || kpageflags_fd == -1)
- return -1;
- if (!orders)
- return -1;
- if (nr_orders <= 0)
- return -1;
-
- for (vaddr = vaddr_start; vaddr < vaddr_start + len;) {
- char *next_folio_vaddr;
- int status;
-
- status = vaddr_pageflags_get(vaddr, pagemap_fd, kpageflags_fd,
- &page_flags);
- if (status < 0)
- return -1;
-
- /* skip non present vaddr */
- if (status == 1) {
- vaddr += psize();
- continue;
- }
-
- /* all order-0 pages with possible false postive (non folio) */
- if (!(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) {
- orders[0]++;
- vaddr += psize();
- continue;
- }
-
- /* skip non thp compound pages */
- if (!(page_flags & KPF_THP)) {
- vaddr += psize();
- continue;
- }
-
- /* vpn points to part of a THP at this point */
- if (page_flags & KPF_COMPOUND_HEAD)
- cur_order = 1;
- else {
- vaddr += psize();
- continue;
- }
-
- next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
-
- if (next_folio_vaddr >= vaddr_start + len)
- break;
-
- while ((status = vaddr_pageflags_get(next_folio_vaddr,
- pagemap_fd, kpageflags_fd,
- &page_flags)) >= 0) {
- /*
- * non present vaddr, next compound head page, or
- * order-0 page
- */
- if (status == 1 ||
- (page_flags & KPF_COMPOUND_HEAD) ||
- !(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) {
- if (cur_order < nr_orders) {
- orders[cur_order]++;
- cur_order = -1;
- vaddr = next_folio_vaddr;
- }
- break;
- }
-
- cur_order++;
- next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
- }
-
- if (status < 0)
- return status;
- }
- if (cur_order > 0 && cur_order < nr_orders)
- orders[cur_order]++;
- return 0;
-}
-
static int check_after_split_folio_orders(char *vaddr_start, size_t len,
int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders)
{
@@ -240,7 +117,7 @@ static int check_after_split_folio_orders(char *vaddr_start, size_t len,
ksft_exit_fail_msg("Cannot allocate memory for vaddr_orders");
memset(vaddr_orders, 0, sizeof(int) * nr_orders);
- status = gather_after_split_folio_orders(vaddr_start, len, pagemap_fd,
+ status = gather_folio_orders(vaddr_start, len, pagemap_fd,
kpageflags_fd, vaddr_orders, nr_orders);
if (status)
ksft_exit_fail_msg("gather folio info failed\n");
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index b43adfa92116..b08bf655ab23 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -194,6 +194,129 @@ unsigned long rss_anon(void)
return rss_anon;
}
+static int vaddr_pageflags_get(char *vaddr, int pagemap_fd, int kpageflags_fd,
+ uint64_t *flags)
+{
+ unsigned long pfn;
+
+ pfn = pagemap_get_pfn(pagemap_fd, vaddr);
+
+ /* non-present PFN */
+ if (pfn == -1UL)
+ return 1;
+
+ if (pageflags_get(pfn, kpageflags_fd, flags))
+ return -1;
+
+ return 0;
+}
+
+/*
+ * gather_folio_orders - scan through [vaddr_start, len) and record
+ * folio orders
+ *
+ * @vaddr_start: start vaddr
+ * @len: range length
+ * @pagemap_fd: file descriptor to /proc/<pid>/pagemap
+ * @kpageflags_fd: file descriptor to /proc/kpageflags
+ * @orders: output folio order array
+ * @nr_orders: folio order array size
+ *
+ * gather_after_split_folio_orders() scan through [vaddr_start, len) and check
+ * all folios within the range and record their orders. All order-0 pages will
+ * be recorded. Non-present vaddr is skipped.
+ *
+ * NOTE: the function is used to check folio orders after a split is performed,
+ * so it assumes [vaddr_start, len) fully maps to after-split folios within that
+ * range.
+ *
+ * Return: 0 - no error, -1 - unhandled cases
+ */
+int gather_folio_orders(char *vaddr_start, size_t len,
+ int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders)
+{
+ uint64_t page_flags = 0;
+ int cur_order = -1;
+ char *vaddr;
+
+ if (pagemap_fd == -1 || kpageflags_fd == -1)
+ return -1;
+ if (!orders)
+ return -1;
+ if (nr_orders <= 0)
+ return -1;
+
+ for (vaddr = vaddr_start; vaddr < vaddr_start + len;) {
+ char *next_folio_vaddr;
+ int status;
+
+ status = vaddr_pageflags_get(vaddr, pagemap_fd, kpageflags_fd,
+ &page_flags);
+ if (status < 0)
+ return -1;
+
+ /* skip non present vaddr */
+ if (status == 1) {
+ vaddr += psize();
+ continue;
+ }
+
+ /* all order-0 pages with possible false postive (non folio) */
+ if (!(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) {
+ orders[0]++;
+ vaddr += psize();
+ continue;
+ }
+
+ /* skip non thp compound pages */
+ if (!(page_flags & KPF_THP)) {
+ vaddr += psize();
+ continue;
+ }
+
+ /* vpn points to part of a THP at this point */
+ if (page_flags & KPF_COMPOUND_HEAD)
+ cur_order = 1;
+ else {
+ vaddr += psize();
+ continue;
+ }
+
+ next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
+
+ if (next_folio_vaddr >= vaddr_start + len)
+ break;
+
+ while ((status = vaddr_pageflags_get(next_folio_vaddr,
+ pagemap_fd, kpageflags_fd,
+ &page_flags)) >= 0) {
+ /*
+ * non present vaddr, next compound head page, or
+ * order-0 page
+ */
+ if (status == 1 ||
+ (page_flags & KPF_COMPOUND_HEAD) ||
+ !(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) {
+ if (cur_order < nr_orders) {
+ orders[cur_order]++;
+ cur_order = -1;
+ vaddr = next_folio_vaddr;
+ }
+ break;
+ }
+
+ cur_order++;
+ next_folio_vaddr = vaddr + (1UL << (cur_order + pshift()));
+ }
+
+ if (status < 0)
+ return status;
+ }
+ if (cur_order > 0 && cur_order < nr_orders)
+ orders[cur_order]++;
+ return 0;
+}
+
char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len)
{
int ret;
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index 40c6d8c4f1b8..19d8568a8553 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -95,6 +95,8 @@ bool check_huge_file(void *addr, unsigned long size, int nr_hpages, uint64_t hpa
bool check_huge_shmem(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size);
int64_t allocate_transhuge(void *ptr, int pagemap_fd);
int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags);
+int gather_folio_orders(char *vaddr_start, size_t len,
+ int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders);
int uffd_register(int uffd, void *addr, uint64_t len,
bool miss, bool wp, bool minor);
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v2 10/11] selftests: mm: implement the mTHP-sized hugepage check helpers
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
` (8 preceding siblings ...)
2026-06-10 10:29 ` [RFC PATCH v2 09/11] selftests: mm: move gather_after_split_folio_orders() into vm_util.c file Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 10:29 ` [RFC PATCH v2 11/11] selftests: mm: add mTHP collapse test cases Baolin Wang
2026-06-10 16:28 ` [RFC PATCH v2 00/11] add shmem mTHP collapse support Nico Pache
11 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
Implement mTHP-sized hugepage checking helpers using gather_folio_orders().
Also rename the existing PMD-sized huge page check function to
__check_pmd_huge() for clarity.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
tools/testing/selftests/mm/vm_util.c | 55 ++++++++++++++++++++++++++--
1 file changed, 51 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index b08bf655ab23..6d464d49f164 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -15,6 +15,10 @@
#define SMAP_FILE_PATH "/proc/self/smaps"
#define STATUS_FILE_PATH "/proc/self/status"
#define MAX_LINE_LENGTH 500
+#define PAGEMAP_PATH "/proc/self/pagemap"
+#define KPAGEFLAGS_PATH "/proc/kpageflags"
+#define GET_ORDER(nr_pages) (31 - __builtin_clz(nr_pages))
+#define NR_ORDERS 20
unsigned int __page_size;
unsigned int __page_shift;
@@ -352,7 +356,7 @@ char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len)
return entry;
}
-bool __check_huge(void *addr, char *pattern, int nr_hpages,
+static bool __check_pmd_huge(void *addr, char *pattern, int nr_hpages,
uint64_t hpage_size)
{
char buffer[MAX_LINE_LENGTH];
@@ -370,19 +374,62 @@ bool __check_huge(void *addr, char *pattern, int nr_hpages,
return thp == (nr_hpages * (hpage_size >> 10));
}
+static bool check_large_folios(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size)
+{
+ int pagesize = getpagesize();
+ int order = GET_ORDER(hpage_size / pagesize);
+ int pagemap_fd, kpageflags_fd;
+ int orders[NR_ORDERS], status;
+ bool ret = false;
+
+ memset(orders, 0, sizeof(int) * NR_ORDERS);
+
+ pagemap_fd = open(PAGEMAP_PATH, O_RDONLY);
+ if (pagemap_fd == -1)
+ ksft_exit_fail_msg("read pagemap fail\n");
+
+ kpageflags_fd = open(KPAGEFLAGS_PATH, O_RDONLY);
+ if (kpageflags_fd == -1) {
+ close(pagemap_fd);
+ ksft_exit_fail_msg("read kpageflags fail\n");
+ }
+
+ status = gather_folio_orders(addr, size, pagemap_fd,
+ kpageflags_fd, orders, NR_ORDERS);
+ if (status)
+ goto out;
+
+ if (orders[order] == nr_hpages)
+ ret = true;
+
+out:
+ close(pagemap_fd);
+ close(kpageflags_fd);
+ return ret;
+}
+
bool check_huge_anon(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size)
{
- return __check_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size);
+ if (hpage_size == read_pmd_pagesize())
+ return __check_pmd_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size);
+
+ return check_large_folios(addr, size, nr_hpages, hpage_size);
}
bool check_huge_file(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size)
{
- return __check_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size);
+ if (hpage_size == read_pmd_pagesize())
+ return __check_pmd_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size);
+
+ return check_large_folios(addr, size, nr_hpages, hpage_size);
}
bool check_huge_shmem(void *addr, unsigned long size, int nr_hpages, uint64_t hpage_size)
{
- return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size);
+ if (hpage_size == read_pmd_pagesize())
+ return __check_pmd_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size);
+
+ return check_large_folios(addr, size, nr_hpages, hpage_size);
}
int64_t allocate_transhuge(void *ptr, int pagemap_fd)
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v2 11/11] selftests: mm: add mTHP collapse test cases
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
` (9 preceding siblings ...)
2026-06-10 10:29 ` [RFC PATCH v2 10/11] selftests: mm: implement the mTHP-sized hugepage check helpers Baolin Wang
@ 2026-06-10 10:29 ` Baolin Wang
2026-06-10 16:28 ` [RFC PATCH v2 00/11] add shmem mTHP collapse support Nico Pache
11 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-06-10 10:29 UTC (permalink / raw)
To: akpm, david, ljs, hughd
Cc: willy, ziy, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, baolin.wang, linux-mm, linux-kselftest, linux-kernel
Added a new command 'mthp_khugepaged' for mTHP collapse, along with the '-c'
parameter to specify the collapse order. Additionally, added mTHP collapse
test cases for 'collapse_full', 'collapse_empty', and 'collapse_single_mthp'
for both anonymous pages and shmem. All khugepaged test cases passed.
Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
---
tools/testing/selftests/mm/khugepaged.c | 135 +++++++++++++++++++---
tools/testing/selftests/mm/run_vmtests.sh | 4 +
2 files changed, 120 insertions(+), 19 deletions(-)
diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index f69be6be0ecd..8975be5b7b2f 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -26,9 +26,11 @@
#define BASE_ADDR ((void *)(1UL << 30))
static unsigned long hpage_pmd_size;
+static int hpage_pmd_order;
static unsigned long page_size;
static int hpage_pmd_nr;
static int anon_order;
+static int collapse_order;
#define PID_SMAPS "/proc/self/smaps"
#define TEST_FILE "collapse_test_file"
@@ -69,6 +71,7 @@ struct collapse_context {
};
static struct collapse_context *khugepaged_context;
+static struct collapse_context *mthp_khugepaged_context;
static struct collapse_context *madvise_context;
struct file_info {
@@ -554,25 +557,25 @@ static void madvise_collapse(const char *msg, char *p, int nr_hpages,
}
#define TICK 500000
-static bool wait_for_scan(const char *msg, char *p, int nr_hpages,
- struct mem_ops *ops)
+static bool wait_for_scan(const char *msg, char *p, unsigned long size,
+ int nr_hpages, int collap_order, struct mem_ops *ops)
{
- unsigned long size = nr_hpages * hpage_pmd_size;
+ unsigned long hpage_size = page_size << collap_order;
int full_scans;
int timeout = 6; /* 3 seconds */
/* Sanity check */
- if (!ops->check_huge(p, size, 0, hpage_pmd_size))
+ if (!ops->check_huge(p, size, 0, hpage_size))
ksft_exit_fail_msg("Unexpected huge page\n");
- madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE);
+ madvise(p, size, MADV_HUGEPAGE);
/* Wait until the second full_scan completed */
full_scans = thp_read_num("khugepaged/full_scans") + 2;
ksft_print_msg("%s...", msg);
while (timeout--) {
- if (ops->check_huge(p, size, nr_hpages, hpage_pmd_size))
+ if (ops->check_huge(p, size, nr_hpages, hpage_size))
break;
if (thp_read_num("khugepaged/full_scans") >= full_scans)
break;
@@ -595,7 +598,7 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,
if (!is_tmpfs(ops) && ops == &__read_write_file_write_ops)
expect = false;
- if (wait_for_scan(msg, p, nr_hpages, ops)) {
+ if (wait_for_scan(msg, p, size, nr_hpages, hpage_pmd_order, ops)) {
if (expect)
fail("Timeout");
else
@@ -617,12 +620,65 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages,
fail("Fail");
}
+static void mthp_khugepaged_collapse(const char *msg, char *p, int nr_hpages,
+ struct mem_ops *ops, bool expect)
+{
+ unsigned long hpage_size = page_size << collapse_order;
+ struct thp_settings settings = *thp_current_settings();
+ /* mTHP collpase only allocates PMD sized memory */
+ unsigned long size = hpage_pmd_size;
+
+ /* Set mTHP setting for mTHP collapse */
+ if (ops == &__anon_ops) {
+ settings.thp_enabled = THP_NEVER;
+ settings.hugepages[collapse_order].enabled = THP_ALWAYS;
+ } else if (ops == &__shmem_ops) {
+ settings.shmem_enabled = SHMEM_NEVER;
+ settings.shmem_hugepages[collapse_order].enabled = SHMEM_ALWAYS;
+ }
+
+ thp_push_settings(&settings);
+
+ if (wait_for_scan(msg, p, size, nr_hpages, collapse_order, ops)) {
+ if (expect)
+ fail("Timeout");
+ else
+ success("OK");
+
+ /* Restore THP settings for mTHP collapse. */
+ thp_pop_settings();
+ return;
+ }
+
+ /*
+ * For file and shmem memory, khugepaged only retracts pte entries after
+ * putting the new hugepage in the page cache. The hugepage must be
+ * subsequently refaulted to install the pmd mapping for the mm.
+ */
+ if (ops != &__anon_ops)
+ ops->fault(p, 0, nr_hpages * hpage_size);
+
+ if (ops->check_huge(p, size, expect ? nr_hpages : 0, hpage_size))
+ success("OK");
+ else
+ fail("Fail");
+
+ /* Restore THP settings for mTHP collapse. */
+ thp_pop_settings();
+}
+
static struct collapse_context __khugepaged_context = {
.collapse = &khugepaged_collapse,
.enforce_pte_scan_limits = true,
.name = "khugepaged",
};
+static struct collapse_context __mthp_khugepaged_context = {
+ .collapse = &mthp_khugepaged_collapse,
+ .enforce_pte_scan_limits = true,
+ .name = "mthp_khugepaged",
+};
+
static struct collapse_context __madvise_context = {
.collapse = &madvise_collapse,
.enforce_pte_scan_limits = false,
@@ -661,10 +717,17 @@ static void alloc_at_fault(void)
static void collapse_full(struct collapse_context *c, struct mem_ops *ops)
{
void *p;
- int nr_hpages = 4;
+ int nr_pmds = 4, nr_hpages = 4;
unsigned long size = nr_hpages * hpage_pmd_size;
- p = ops->setup_area(nr_hpages);
+ /* Only try 1 PMD sized range for mTHP collapse. */
+ if (c == &__mthp_khugepaged_context) {
+ nr_pmds = 1;
+ nr_hpages = 1 << (hpage_pmd_order - collapse_order);
+ size = hpage_pmd_size;
+ }
+
+ p = ops->setup_area(nr_pmds);
ops->fault(p, 0, size);
c->collapse("Collapse multiple fully populated PTE table", p, nr_hpages,
ops, true);
@@ -676,10 +739,31 @@ static void collapse_full(struct collapse_context *c, struct mem_ops *ops)
static void collapse_empty(struct collapse_context *c, struct mem_ops *ops)
{
+ int nr_hpages = 1;
+ void *p;
+
+ if (c == &__mthp_khugepaged_context)
+ nr_hpages = 1 << (hpage_pmd_order - collapse_order);
+
+ p = ops->setup_area(1);
+ c->collapse("Do not collapse empty PTE table", p, nr_hpages, ops, false);
+ ops->cleanup_area(p, hpage_pmd_size);
+ ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
+static void collapse_single_mthp(struct collapse_context *c, struct mem_ops *ops)
+{
+ unsigned long hpage_size = page_size << collapse_order;
void *p;
p = ops->setup_area(1);
- c->collapse("Do not collapse empty PTE table", p, 1, ops, false);
+ /*
+ * Only fault collapse_order sized ranges, and only check 1
+ * collapse_order sized huge page.
+ */
+ ops->fault(p, 0, hpage_size);
+ c->collapse("Collapse PTE table with half PTE entries present",
+ p, 1, ops, true);
ops->cleanup_area(p, hpage_pmd_size);
ksft_test_result_report(exit_status, "%s\n", __func__);
}
@@ -1081,8 +1165,8 @@ static void madvise_retracted_page_tables(struct collapse_context *c,
ops->fault(p, 0, size);
/* Let khugepaged collapse and leave pmd cleared */
- if (wait_for_scan("Collapse and leave PMD cleared", p, nr_hpages,
- ops)) {
+ if (wait_for_scan("Collapse and leave PMD cleared", p, size, nr_hpages,
+ hpage_pmd_order, ops)) {
fail("Timeout");
return;
}
@@ -1098,7 +1182,7 @@ static void usage(void)
{
fprintf(stderr, "\nUsage: ./khugepaged [OPTIONS] <test type> [dir]\n\n");
fprintf(stderr, "\t<test type>\t: <context>:<mem_type>\n");
- fprintf(stderr, "\t<context>\t: [all|khugepaged|madvise]\n");
+ fprintf(stderr, "\t<context>\t: [all|khugepaged|mthp_khugepaged|madvise]\n");
fprintf(stderr, "\t<mem_type>\t: [all|anon|file|shmem]\n");
fprintf(stderr, "\n\t\"file,all\" mem_type requires [dir] argument\n");
fprintf(stderr, "\n\t\"file,all\" mem_type requires a file system\n");
@@ -1109,6 +1193,7 @@ static void usage(void)
fprintf(stderr, "\t\t-h: This help message.\n");
fprintf(stderr, "\t\t-s: mTHP size, expressed as page order.\n");
fprintf(stderr, "\t\t Defaults to 0. Use this size for anon or shmem allocations.\n");
+ fprintf(stderr, "\t\t-c: collapse order for mTHP collapse, expressed as page order.\n");
exit(1);
}
@@ -1118,11 +1203,14 @@ static void parse_test_type(int argc, char **argv)
char *buf;
const char *token;
- while ((opt = getopt(argc, argv, "s:h")) != -1) {
+ while ((opt = getopt(argc, argv, "s:c:h")) != -1) {
switch (opt) {
case 's':
anon_order = atoi(optarg);
break;
+ case 'c':
+ collapse_order = atoi(optarg);
+ break;
case 'h':
default:
usage();
@@ -1148,6 +1236,10 @@ static void parse_test_type(int argc, char **argv)
madvise_context = &__madvise_context;
} else if (!strcmp(token, "khugepaged")) {
khugepaged_context = &__khugepaged_context;
+ } else if (!strcmp(token, "mthp_khugepaged")) {
+ mthp_khugepaged_context = &__mthp_khugepaged_context;
+ if (collapse_order == 0 || collapse_order >= hpage_pmd_order)
+ usage();
} else if (!strcmp(token, "madvise")) {
madvise_context = &__madvise_context;
} else {
@@ -1213,7 +1305,6 @@ static int nr_test_cases;
int main(int argc, char **argv)
{
- int hpage_pmd_order;
struct thp_settings default_settings = {
.thp_enabled = THP_MADVISE,
.thp_defrag = THP_DEFRAG_ALWAYS,
@@ -1239,10 +1330,6 @@ int main(int argc, char **argv)
if (!thp_is_enabled())
ksft_exit_skip("Transparent Hugepages not available\n");
- parse_test_type(argc, argv);
-
- setbuf(stdout, NULL);
-
page_size = getpagesize();
hpage_pmd_size = read_pmd_pagesize();
if (!hpage_pmd_size)
@@ -1250,6 +1337,10 @@ int main(int argc, char **argv)
hpage_pmd_nr = hpage_pmd_size / page_size;
hpage_pmd_order = __builtin_ctz(hpage_pmd_nr);
+ parse_test_type(argc, argv);
+
+ setbuf(stdout, NULL);
+
default_settings.khugepaged.max_ptes_none = hpage_pmd_nr - 1;
default_settings.khugepaged.max_ptes_swap = hpage_pmd_nr / 8;
default_settings.khugepaged.max_ptes_shared = hpage_pmd_nr / 2;
@@ -1267,6 +1358,8 @@ int main(int argc, char **argv)
TEST(collapse_full, khugepaged_context, read_write_file_read_ops);
TEST(collapse_full, khugepaged_context, read_write_file_write_ops);
TEST(collapse_full, khugepaged_context, shmem_ops);
+ TEST(collapse_full, mthp_khugepaged_context, anon_ops);
+ TEST(collapse_full, mthp_khugepaged_context, shmem_ops);
TEST(collapse_full, madvise_context, anon_ops);
TEST(collapse_full, madvise_context, read_only_file_ops);
TEST(collapse_full, madvise_context, read_write_file_read_ops);
@@ -1274,8 +1367,12 @@ int main(int argc, char **argv)
TEST(collapse_full, madvise_context, shmem_ops);
TEST(collapse_empty, khugepaged_context, anon_ops);
+ TEST(collapse_empty, mthp_khugepaged_context, anon_ops);
TEST(collapse_empty, madvise_context, anon_ops);
+ TEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops);
+ TEST(collapse_single_mthp, mthp_khugepaged_context, shmem_ops);
+
TEST(collapse_single_pte_entry, khugepaged_context, anon_ops);
TEST(collapse_single_pte_entry, khugepaged_context, read_only_file_ops);
TEST(collapse_single_pte_entry, khugepaged_context, read_write_file_read_ops);
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 8c296dedf047..c0f4f3e5f1f1 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -411,6 +411,10 @@ CATEGORY="thp" run_test ./khugepaged all:shmem
CATEGORY="thp" run_test ./khugepaged -s 4 all:shmem
+CATEGORY="thp" run_test ./khugepaged -c 4 mthp_khugepaged:anon
+
+CATEGORY="thp" run_test ./khugepaged -c 4 mthp_khugepaged:shmem
+
# Try to create XFS if not provided
if [ -z "${SPLIT_HUGE_PAGE_TEST_XFS_PATH}" ]; then
if test_selected "thp"; then
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [RFC PATCH v2 00/11] add shmem mTHP collapse support
2026-06-10 10:29 [RFC PATCH v2 00/11] add shmem mTHP collapse support Baolin Wang
` (10 preceding siblings ...)
2026-06-10 10:29 ` [RFC PATCH v2 11/11] selftests: mm: add mTHP collapse test cases Baolin Wang
@ 2026-06-10 16:28 ` Nico Pache
11 siblings, 0 replies; 16+ messages in thread
From: Nico Pache @ 2026-06-10 16:28 UTC (permalink / raw)
To: Baolin Wang
Cc: akpm, david, ljs, hughd, willy, ziy, liam, ryan.roberts, dev.jain,
baohua, lance.yang, linux-mm, linux-kselftest, linux-kernel
On Wed, Jun 10, 2026 at 4:29 AM Baolin Wang
<baolin.wang@linux.alibaba.com> wrote:
>
> (Note: this patchset is not targeting v7.2, but posted for early feedback.)
>
> This is a follow-up patchset for mTHP collapse to support shmem mTHP collapse,
> which is based on Nico's patchset[1].
>
> The shmem mTHP collapse strategy follows the anonymous mTHP collapse approach:
> track present pages via a bitmap while scanning PMD ranges for collapse candidates,
> then use the bitmap after the scan completes to determine the most efficient
> mTHP order to collapse to. Built on the basic framework added for anonymous
> mTHP collapse, the shmem mTHP collapse implementation is straightforward
> (Thanks for Nico's work).
As promised I will review this series :)
>
> In addition, I have added some anon/shmem mTHP collapse selftests, and now all
> khugepaged test cases can pass.
Thank you for doing that!!
I was just working on adding the anon mTHP selftests. Any chance we
can separate out those bits and just send a series for adding anon
mTHP selftests without the file-related changes? Then your series adds
the bits you need here for shmem collapse? Thank you for doing all the
heavy lifting there to generalize those functions :)
Cheers,
-- Nico
>
> Note: I have not yet enabled large order collapse for file folios (file folios
> currently only support PMD-sized large folio collapse). Although file large order
> collapse would be more straightforward to implement after shmem mTHP collapse
> support is added (requiring some changes to file_thp_enabled()), I think this
> still need some discussion on whether it is necessary to support other large
> orders collapse for file folios.
>
> Comments are welcome. Thanks.
>
> Changes from RFC v1:
> https://lore.kernel.org/all/cover.1755677674.git.baolin.wang@linux.alibaba.com/
> - Rebase on the new code, and update to use the new functions.
> - Add more test cases.
>
> [1] https://lore.kernel.org/all/20260605161422.213817-1-npache@redhat.com/
>
> Baolin Wang (11):
> mm: khugepaged: add max_ptes_none check in collapse_file()
> mm: khugepaged: generalize collapse_file() for shmem mTHP support
> mm: khugepaged: add an order check for PMD-sized THP statistics
> mm: khugepaged: add shmem mTHP collapse support
> mm: shmem: run khugepaged for all shmem mTHP orders
> mm: khugepaged: allow khugepaged to check all shmem mTHP-sized orders
> mm: khugepaged: skip large folios that don't need to be collapsed
> selftests: mm: extend the check_huge() to support mTHP check
> selftests: mm: move gather_after_split_folio_orders() into vm_util.c
> file
> selftests: mm: implement the mTHP-sized hugepage check helpers
> selftests: mm: add mTHP collapse test cases
>
> include/linux/shmem_fs.h | 4 +-
> mm/khugepaged.c | 174 ++++++++++++----
> mm/shmem.c | 10 +-
> .../selftests/mm/folio_split_race_test.c | 2 +-
> tools/testing/selftests/mm/khugepaged.c | 195 +++++++++++++-----
> .../testing/selftests/mm/prctl_thp_disable.c | 2 +-
> tools/testing/selftests/mm/run_vmtests.sh | 4 +
> tools/testing/selftests/mm/soft-dirty.c | 2 +-
> .../selftests/mm/split_huge_page_test.c | 139 +------------
> tools/testing/selftests/mm/uffd-common.c | 4 +-
> tools/testing/selftests/mm/vm_util.c | 184 ++++++++++++++++-
> tools/testing/selftests/mm/vm_util.h | 8 +-
> 12 files changed, 492 insertions(+), 236 deletions(-)
>
> --
> 2.47.3
>
^ permalink raw reply [flat|nested] 16+ messages in thread