From: "Kiryl Shutsemau (Meta)" <[email protected]>

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.

Without -c, run the mTHP cases once per supported anon THP order below the
PMD, and include that context in both the no-argument invocation and
"all".  -c still pins one order, and now says what is wrong when the order
is above the PMD or unsupported 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 rejects a
negative order and -c anything at or below 0, rather than letting either
reach them.

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 does.

A case carries the order it was registered at, so a result names it:

  # Run test: collapse_order_max_ptes_none (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
Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]>
---
 tools/testing/selftests/mm/khugepaged.c   | 111 ++++++++++++++++++----
 tools/testing/selftests/mm/run_vmtests.sh |   2 -
 2 files changed, 91 insertions(+), 22 deletions(-)

diff --git a/tools/testing/selftests/mm/khugepaged.c 
b/tools/testing/selftests/mm/khugepaged.c
index a5683f08694f..a202fe359cf6 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;
 
@@ -1556,6 +1559,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);
@@ -1563,6 +1567,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;
@@ -1574,6 +1579,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:
@@ -1581,12 +1587,23 @@ 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)
+               ksft_exit_fail_msg("-s takes an order, which cannot be 
negative\n");
+       if (collapse_order_given && collapse_order <= 0)
+               ksft_exit_fail_msg("-c takes an order above 0, not %d\n",
+                                  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;
@@ -1597,13 +1614,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 {
@@ -1619,20 +1642,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();
        }
@@ -1654,9 +1677,14 @@ struct test_case {
        struct mem_ops *ops;
        const char *desc;
        test_fn fn;
+       int order;              /* mTHP contexts: the collapse order */
 };
 
-#define MAX_TEST_CASES 64
+/*
+ * Enough for every case at every order the kernel offers: the mTHP context
+ * runs its cases once per supported order below the PMD.
+ */
+#define MAX_TEST_CASES 256
 static struct test_case test_cases[MAX_TEST_CASES];
 static int nr_test_cases;
 
@@ -1669,6 +1697,7 @@ static int nr_test_cases;
                        .ops    = o,                                    \
                        .desc   = #t,                                   \
                        .fn     = t,                                    \
+                       .order  = collapse_order,                       \
                };                                                      \
        }                                                               \
        } while (0)
@@ -1709,10 +1738,40 @@ 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 (collapse_order >= hpage_pmd_order)
+                               ksft_exit_fail_msg("-c takes an order below the 
PMD order (%d)\n",
+                                                  hpage_pmd_order);
+                       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 ? anon_order + 1 : 
MIN_MTHP_ORDER;
+
+                       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);
@@ -1773,7 +1832,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);
@@ -1781,14 +1850,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);
@@ -1862,7 +1925,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


Reply via email to