From: "Kiryl Shutsemau (Meta)" The mTHP collapse cases only run when the caller names both the context and an order, so a plain ./khugepaged covers the PMD contexts on anon and nothing else. run_vmtests.sh pinned order 4 and covered no other. Run the mTHP cases once per supported anon THP order below the PMD when -c is absent, and pull that context into both the no-argument invocation and "all". Around that: - -c still pins one order, and now says what is wrong instead of printing the usage text. An order at or below the -s source order is skipped: the sources would already be the size being asked for. - Both orders end up as array indices and shift counts, so -s and -c are range-checked before they get there. - The mTHP context has only anon cases, so a run that names a different mem_type -- "all:shmem", say -- drops it again rather than refusing to start. Naming both explicitly still refuses. - A case carries the order it was registered at, so a result names it: # Run test: collapse_single_mthp (mthp_khugepaged:anon, order 6) On x86-64 with 4K pages that is orders 2 through 8, and ./khugepaged goes from 28 results to 77 in 21 seconds, so run_vmtests.sh can drop its pinned order-4 line. Assisted-by: Claude-Code:claude-opus-5 Tested-by: Muhammad Usama Anjum Signed-off-by: Kiryl Shutsemau (Meta) --- tools/testing/selftests/mm/khugepaged.c | 105 +++++++++++++++++----- tools/testing/selftests/mm/run_vmtests.sh | 2 - 2 files changed, 85 insertions(+), 22 deletions(-) diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c index 856decd2950a..172e7307eeee 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -31,6 +31,9 @@ static unsigned long page_size; static int hpage_pmd_nr; static int anon_order; static int collapse_order; +static bool collapse_order_given; +static int collapse_orders[NR_ORDERS]; +static int nr_collapse_orders; static int pagemap_fd = -1; static int kpageflags_fd = -1; @@ -1550,6 +1553,7 @@ static void usage(void) 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"); + fprintf(stderr, "\t\t Defaults to every supported order below the PMD.\n"); fprintf(stderr, "\t\t With -s, -s names the mTHP source order for the\n"); fprintf(stderr, "\t\t mixed-source case (source order below the target).\n"); exit(1); @@ -1557,6 +1561,7 @@ static void usage(void) static void parse_test_type(int argc, char **argv) { + bool mthp_context_implied = false; int opt; char *buf; const char *token; @@ -1568,6 +1573,7 @@ static void parse_test_type(int argc, char **argv) break; case 'c': collapse_order = atoi(optarg); + collapse_order_given = true; break; case 'h': default: @@ -1575,12 +1581,25 @@ static void parse_test_type(int argc, char **argv) } } + /* + * Both orders end up as array indices and shift counts, so neither + * can be negative, and a zero collapse order asks for base pages. + */ + if (anon_order < 0 || anon_order > hpage_pmd_order) + ksft_exit_fail_msg("-s takes an order in 0..%d, not %d\n", + hpage_pmd_order, anon_order); + if (collapse_order_given && + (collapse_order <= 0 || collapse_order >= hpage_pmd_order)) + ksft_exit_fail_msg("-c takes an order in 1..%d, not %d\n", + hpage_pmd_order - 1, collapse_order); + argv += optind; argc -= optind; if (argc == 0) { - /* Backwards compatibility */ + /* Everything that needs no argument of its own: anon, every context */ khugepaged_context = &__khugepaged_context; + mthp_khugepaged_context = &__mthp_khugepaged_context; madvise_context = &__madvise_context; anon_ops = &__anon_ops; return; @@ -1591,13 +1610,19 @@ static void parse_test_type(int argc, char **argv) if (!strcmp(token, "all")) { khugepaged_context = &__khugepaged_context; + mthp_khugepaged_context = &__mthp_khugepaged_context; madvise_context = &__madvise_context; + + /* + * "all" sweeps the mTHP context in, but it only has anon + * cases: step it aside for the other mem_types rather than + * refusing the whole run. + */ + mthp_context_implied = true; } 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 { @@ -1613,20 +1638,20 @@ static void parse_test_type(int argc, char **argv) read_write_file_write_ops = &__read_write_file_write_ops; anon_ops = &__anon_ops; shmem_ops = &__shmem_ops; - if (mthp_khugepaged_context) - usage(); } else if (!strcmp(buf, "anon")) { anon_ops = &__anon_ops; } else if (!strcmp(buf, "file")) { read_only_file_ops = &__read_only_file_ops; read_write_file_read_ops = &__read_write_file_read_ops; read_write_file_write_ops = &__read_write_file_write_ops; - if (mthp_khugepaged_context) + if (mthp_khugepaged_context && !mthp_context_implied) usage(); + mthp_khugepaged_context = NULL; } else if (!strcmp(buf, "shmem")) { shmem_ops = &__shmem_ops; - if (mthp_khugepaged_context) + if (mthp_khugepaged_context && !mthp_context_implied) usage(); + mthp_khugepaged_context = NULL; } else { usage(); } @@ -1648,6 +1673,7 @@ struct test_case { struct mem_ops *ops; const char *desc; test_fn fn; + int order; /* mTHP contexts: the collapse order */ }; #define MAX_TEST_CASES 256 @@ -1663,6 +1689,7 @@ static int nr_test_cases; .ops = o, \ .desc = #t, \ .fn = t, \ + .order = collapse_order, \ }; \ } \ } while (0) @@ -1703,10 +1730,37 @@ int main(int argc, char **argv) parse_test_type(argc, argv); - if (mthp_khugepaged_context && - !(thp_supported_orders() & (1UL << collapse_order))) - ksft_exit_skip("Order %d is not a supported anon THP order\n", - collapse_order); + if (mthp_khugepaged_context) { + unsigned long orders = thp_supported_orders(); + + if (collapse_order_given) { + /* -c pins one order; it has to be one we can build */ + if (!(orders & (1UL << collapse_order))) + ksft_exit_skip("Order %d is not a supported anon THP order\n", + collapse_order); + if (collapse_order <= anon_order) + ksft_exit_skip("-c %d needs a source order below it, -s says %d\n", + collapse_order, anon_order); + collapse_orders[nr_collapse_orders++] = collapse_order; + } else { + /* + * Otherwise every order a collapse could produce. -s + * makes the fault path hand out folios of that order, + * so a target at or below it has nothing to collapse: + * the sources are already the size being asked for. + */ + int first = anon_order + 1; + + if (first < MIN_MTHP_ORDER) + first = MIN_MTHP_ORDER; + for (int i = first; i < hpage_pmd_order; i++) { + if (orders & (1UL << i)) + collapse_orders[nr_collapse_orders++] = i; + } + if (!nr_collapse_orders) + ksft_print_msg("mTHP cases skipped: no order above the source\n"); + } + } if (mthp_khugepaged_context) { pagemap_fd = open("/proc/self/pagemap", O_RDONLY); @@ -1760,7 +1814,17 @@ 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); + for (int i = 0; i < nr_collapse_orders; i++) { + collapse_order = collapse_orders[i]; + TEST(collapse_full, mthp_khugepaged_context, anon_ops); + TEST(collapse_empty, mthp_khugepaged_context, anon_ops); + TEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops); + TEST(collapse_order_single_window, mthp_khugepaged_context, anon_ops); + 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_full, madvise_context, anon_ops); TEST(collapse_full, madvise_context, read_only_file_ops); TEST(collapse_full, madvise_context, read_write_file_read_ops); @@ -1768,15 +1832,8 @@ 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_order_single_window, mthp_khugepaged_context, anon_ops); - 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_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); @@ -1849,7 +1906,15 @@ int main(int argc, char **argv) for (int i = 0; i < nr_test_cases; i++) { struct test_case *t = &test_cases[i]; - ksft_print_msg("\n# Run test: %s (%s:%s)\n", t->desc, t->ctx->name, t->ops->name); + if (t->ctx == &__mthp_khugepaged_context) { + collapse_order = t->order; + ksft_print_msg("\n# Run test: %s (%s:%s, order %d)\n", + t->desc, t->ctx->name, t->ops->name, + t->order); + } else { + ksft_print_msg("\n# Run test: %s (%s:%s)\n", t->desc, + t->ctx->name, t->ops->name); + } t->fn(t->ctx, t->ops); } diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh index 2652a7920b80..8bf898b71350 100755 --- a/tools/testing/selftests/mm/run_vmtests.sh +++ b/tools/testing/selftests/mm/run_vmtests.sh @@ -412,8 +412,6 @@ 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 - # Try to create XFS if not provided if [ -z "${SPLIT_HUGE_PAGE_TEST_XFS_PATH}" ]; then if test_selected "thp"; then -- 2.54.0