[RFC PATCH 52/57] selftests/mm: cover a hole-y window in a sub-PMD VMA

From: Kiryl Shutsemau

Date: Sun Aug 16 2026 - 18:57:57 EST


From: "Kiryl Shutsemau (Meta)" <kas@xxxxxxxxxx>

A collapse over a partially populated window has to zero the slots it
found empty. The destination comes from the allocator holding whatever
was last written to it, and a slot the process never touched must read
back zero.

Nothing in the suite checks that. validate_memory() only re-reads the
pattern fill_memory() wrote, and every caller passes it the faulted extent
alone, so a collapse that left stale bytes behind the holes would pass
every case here.

Check it where population and sub-PMD eligibility meet, which no existing
case covers either. One page is faulted in a window-sized VMA; the case
expects the window collapsed, the faulted page unchanged, and every byte
behind the holes zero.

Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <kas@xxxxxxxxxx>
---
tools/testing/selftests/mm/khugepaged.c | 50 +++++++++++++++++++++++++
1 file changed, 50 insertions(+)

diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index 85f138cfb9b2..b61e32566d47 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -1599,6 +1599,55 @@ static void collapse_order_sub_pmd_range(struct collapse_context *c,
__collapse_order_sub_pmd_vma(c, ops, nr_windows, __func__);
}

+/*
+ * A partially populated window in a sub-PMD VMA: population and
+ * sub-PMD eligibility at once. The unfaulted slots must come back
+ * zero-filled in the collapsed folio, and the faulted ones unchanged.
+ */
+static void collapse_order_sub_pmd_holes(struct collapse_context *c,
+ struct mem_ops *ops)
+{
+ size_t size = mthp_window_size();
+ char *bytes;
+ void *p;
+ size_t i;
+
+ mthp_push_target_order();
+
+ p = mmap(BASE_ADDR, size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ bytes = p;
+ if (p != BASE_ADDR)
+ ksft_exit_fail_msg("Failed to allocate VMA at %p\n", BASE_ADDR);
+
+ fill_memory(p, 0, page_size);
+ if (!window_not_collapsed(p, size))
+ ksft_exit_fail_msg("Unexpected large folio after fault\n");
+
+ madvise(p, size, MADV_HUGEPAGE);
+ ksft_print_msg("Collapse hole-y window inside a sub-PMD VMA...");
+ if (!khugepaged_wait_full_pass()) {
+ fail("Timeout");
+ } else if (window_collapsed(p, size)) {
+ /* The unfaulted tail must be zero-filled. */
+ for (i = page_size; i < size; i++) {
+ if (bytes[i])
+ break;
+ }
+ if (i == size)
+ success("OK");
+ else
+ fail("Fail");
+ } else {
+ fail("Fail");
+ }
+
+ validate_memory(p, 0, page_size);
+ munmap(p, size);
+ thp_pop_settings();
+ ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
static void usage(void)
{
fprintf(stderr, "\nUsage: ./khugepaged [OPTIONS] <test type> [dir]\n\n");
@@ -1888,6 +1937,7 @@ int main(int argc, char **argv)
TEST(collapse_order_mixed_sources, mthp_khugepaged_context, anon_ops);
TEST(collapse_order_sub_pmd_vma, mthp_khugepaged_context, anon_ops);
TEST(collapse_order_sub_pmd_range, mthp_khugepaged_context, anon_ops);
+ TEST(collapse_order_sub_pmd_holes, mthp_khugepaged_context, anon_ops);
}

TEST(collapse_full, madvise_context, anon_ops);
--
2.54.0