From: "Kiryl Shutsemau (Meta)" <[email protected]> The new engine can collapse an mTHP inside a VMA smaller than a PMD. The old mechanism could not: its coverage was rooted at whole PMD-aligned spans, so a VMA that could not hold one was passed over however many mTHP-sized windows it held. On arm64 with 64K pages, where a PMD is 512M, that was every VMA below 512M.
Nothing covers that, so add two cases: - a VMA of exactly one window, the smallest thing that can be collapsed at all; - a VMA of several windows, which also walks from one window to the next inside a single VMA. The second takes as many windows as fit in half a table. At the order just below the PMD order two windows are already a whole table, so there is no room for several and the case skips. Assisted-by: Claude-Code:claude-opus-5 Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]> --- tools/testing/selftests/mm/khugepaged.c | 65 +++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c index 172e7307eeee..85f138cfb9b2 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -1536,6 +1536,69 @@ static void collapse_order_mixed_sources(struct collapse_context *c, ksft_test_result_report(exit_status, "%s\n", __func__); } +/* + * A VMA smaller than a PMD is a valid collapse target, so long as it holds a + * naturally aligned window of the target order. This is the case khugepaged + * used to pass over entirely, its coverage being rooted at whole PMD-aligned + * spans, which on arm64 with 64K pages meant every VMA below 512M. + */ +static void __collapse_order_sub_pmd_vma(struct collapse_context *c, + struct mem_ops *ops, int nr_windows, + const char *name) +{ + size_t size = nr_windows * mthp_window_size(); + void *p; + + mthp_push_target_order(); + + p = mmap(BASE_ADDR, size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (p != BASE_ADDR) + ksft_exit_fail_msg("Failed to allocate VMA at %p\n", BASE_ADDR); + + fill_memory(p, 0, 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 inside a sub-PMD VMA (%d windows)...", + nr_windows); + if (!khugepaged_wait_full_pass()) + fail("Timeout"); + else if (window_collapsed(p, size)) + success("OK"); + else + fail("Fail"); + + validate_memory(p, 0, size); + munmap(p, size); + thp_pop_settings(); + ksft_test_result_report(exit_status, "%s\n", name); +} + +static void collapse_order_sub_pmd_vma(struct collapse_context *c, + struct mem_ops *ops) +{ + __collapse_order_sub_pmd_vma(c, ops, 1, __func__); +} + +static void collapse_order_sub_pmd_range(struct collapse_context *c, + struct mem_ops *ops) +{ + size_t window = mthp_window_size(); + int nr_windows = 16; + + while (nr_windows > 1 && nr_windows * window > hpage_pmd_size / 2) + nr_windows /= 2; + + if (nr_windows == 1) { + ksft_test_result_skip("%s: no room for multiple windows below the PMD\n", + __func__); + return; + } + __collapse_order_sub_pmd_vma(c, ops, nr_windows, __func__); +} + static void usage(void) { fprintf(stderr, "\nUsage: ./khugepaged [OPTIONS] <test type> [dir]\n\n"); @@ -1823,6 +1886,8 @@ int main(int argc, char **argv) TEST(collapse_order_partial_window, mthp_khugepaged_context, anon_ops); TEST(collapse_order_max_ptes_none, mthp_khugepaged_context, anon_ops); 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_full, madvise_context, anon_ops); -- 2.54.0
