From: "Kiryl Shutsemau (Meta)" <[email protected]> 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) <[email protected]> --- 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
