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

The harness pins max_ptes_none to 0, so khugepaged only collapses a window
once every PTE in it is present.  Collapsing a window that has holes never
happens, and that is a different path: a hole is not copied from anywhere
but zero-filled into the new folio, and the slot is re-checked under the
page table lock at install time in case a racing fault filled it first.

Run both ends of the occupancy scale, one after the other, for every
driver mode -- mTHP collapse supports only those two, 0 and
HPAGE_PMD_NR - 1, and coerces anything between them to 0.  Each result
says which end it ran:

  ok 1 stepped/strict: 5s, 231 steps, no corruption
  ok 2 stepped/holes: 5s, 194 steps, no corruption

-z narrows a run to the hole-heavy end, the way -m narrows it to one mode.

Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]>
---
 tools/testing/selftests/mm/khugepaged_race.c | 57 +++++++++++++-------
 1 file changed, 39 insertions(+), 18 deletions(-)

diff --git a/tools/testing/selftests/mm/khugepaged_race.c 
b/tools/testing/selftests/mm/khugepaged_race.c
index 3dc651dc56b3..a2a91906285b 100644
--- a/tools/testing/selftests/mm/khugepaged_race.c
+++ b/tools/testing/selftests/mm/khugepaged_race.c
@@ -11,9 +11,12 @@
  *   free      khugepaged left to run (scan_sleep_millisecs=0), for soak;
  *   madvise   MADV_COLLAPSE in a loop.
  *
- * All anon THP orders are enabled (inherit) and max_ptes_none is 0, so a
- * window has to be fully populated before khugepaged will collapse it, and
- * the racing MADV_DONTNEED decides which orders it can still use.
+ * All anon THP orders are enabled (inherit).  Occupancy runs at both ends
+ * of what mTHP collapse supports: max_ptes_none 0, where a window must be
+ * fully populated, and HPAGE_PMD_NR - 1, where a window full of holes
+ * collapses too.  The holes are not copied from anywhere -- they are
+ * zero-filled, and re-checked under the page table lock at install time in
+ * case a racing fault got there first.
  *
  * Correctness signals: every racing page must read as its pattern or
  * zero (MADV_DONTNEED), never anything else.  The faulters and the fork
@@ -196,9 +199,11 @@ static unsigned long now_ms(void)
 static void usage(void)
 {
        fprintf(stderr,
-               "Usage: khugepaged_race [-d seconds] [-m stepped|free|madvise] 
[-a areas]\n"
+               "Usage: khugepaged_race [-d seconds] [-m stepped|free|madvise] 
[-z] [-a areas]\n"
                "\tWithout -m, every mode runs in turn.\n"
                "\t-d: seconds per mode (default 5)\n"
+               "\tBoth occupancy limits run unless -z asks for holes only.\n"
+               "\t-z: only max_ptes_none = HPAGE_PMD_NR - 1 (hole-heavy)\n"
                "\t-a: number of shared PMD-sized playground areas (default 
3)\n");
        exit(1);
 }
@@ -216,6 +221,9 @@ int main(int argc, char **argv)
        const int nr_threads = ARRAY_SIZE(thread_names);
        pthread_t threads[ARRAY_SIZE(thread_names)];
        static const char * const all_modes[] = { "stepped", "free", "madvise" 
};
+       static const int all_nones[] = { 0, 1 };        /* strict, holes */
+       const int *nones = all_nones;
+       int nr_nones = ARRAY_SIZE(all_nones);
        const char *one_mode[1];
        const char * const *modes = all_modes;
        int nr_modes = ARRAY_SIZE(all_modes);
@@ -225,11 +233,12 @@ int main(int argc, char **argv)
        int duration_s = 5;
        unsigned long thread_mask = ~0UL;
        int nr_areas_arg = 0;
+       bool holes_only = false;
        unsigned long i;
        int steps = 0;
        int opt;
 
-       while ((opt = getopt(argc, argv, "a:d:m:t:h")) != -1) {
+       while ((opt = getopt(argc, argv, "a:d:m:t:zh")) != -1) {
                switch (opt) {
                case 'a':
                        nr_areas_arg = atoi(optarg);
@@ -244,10 +253,18 @@ int main(int argc, char **argv)
                        /* debug: bitmask of racing threads to start */
                        thread_mask = strtoul(optarg, NULL, 0);
                        break;
+               case 'z':
+                       holes_only = true;
+                       break;
                default:
                        usage();
                }
        }
+       if (holes_only) {
+               nones = all_nones + 1;
+               nr_nones = 1;
+       }
+
        if (mode_arg) {
                if (strcmp(mode_arg, "stepped") && strcmp(mode_arg, "free") &&
                    strcmp(mode_arg, "madvise"))
@@ -287,7 +304,7 @@ int main(int argc, char **argv)
                 -1, 0) != (void *)mremap_scratch)
                ksft_exit_fail_perror("mmap() mremap scratch");
 
-       ksft_set_plan(nr_modes);
+       ksft_set_plan(nr_modes * nr_nones);
 
        thp_save_settings();
        thp_read_settings(&settings);
@@ -299,8 +316,9 @@ int main(int argc, char **argv)
         */
        thp_push_settings(&settings);
 
-       for (int m = 0; m < nr_modes; m++) {
-               const char *mode = modes[m];
+       for (int mn = 0; mn < nr_modes * nr_nones; mn++) {
+               const char *mode = modes[mn / nr_nones];
+               bool holes = nones[mn % nr_nones];
 
                thp_read_settings(&settings);
                settings.thp_enabled = THP_MADVISE;
@@ -310,14 +328,16 @@ int main(int argc, char **argv)
                settings.khugepaged.scan_sleep_millisecs =
                        strcmp(mode, "free") ? 1000 : 0;
                settings.khugepaged.alloc_sleep_millisecs = 10;
+
                /*
-                * Strict occupancy: mTHP collapse only supports 0 or
-                * HPAGE_PMD_NR - 1 and coerces anything else to 0 anyway, and 0
-                * also keeps khugepaged from burning the whole step in doomed
-                * PMD-sized allocations on 512M-PMD configs: under racing
-                * MADV_DONTNEED a fully populated PMD area is rare.
+                * mTHP collapse only supports the two ends of the occupancy
+                * scale: 0 or HPAGE_PMD_NR - 1 (anything else coerces to 0).
+                * Strict needs a fully populated window, which is rare under
+                * racing MADV_DONTNEED; hole-heavy windows collapse instead,
+                * so the two ends race different paths.
                 */
-               settings.khugepaged.max_ptes_none = 0;
+               settings.khugepaged.max_ptes_none = holes ?
+                       (hpage_pmd_size / page_size) - 1 : 0;
                settings.khugepaged.pages_to_scan =
                        nr_areas * (hpage_pmd_size / page_size) * 8;
                for (i = 0; i < NR_ORDERS; i++) {
@@ -383,8 +403,9 @@ int main(int argc, char **argv)
                        check_page(i);
 
                ksft_test_result(!corrupted,
-                                "%s: %ds, %d steps, no corruption\n",
-                                mode, duration_s, steps);
+                                "%s/%s: %ds, %d steps, no corruption\n",
+                                mode, holes ? "holes" : "strict",
+                                duration_s, steps);
 
                /*
                 * Hand the address space and the settings back before the
@@ -398,9 +419,9 @@ int main(int argc, char **argv)
 
                if (corrupted) {
                        /* Memory is suspect; the rest would prove nothing. */
-                       while (++m < nr_modes)
+                       while (++mn < nr_modes * nr_nones)
                                ksft_test_result_skip("%s: skipped after 
corruption\n",
-                                                     modes[m]);
+                                                     modes[mn / nr_nones]);
                        break;
                }
        }
-- 
2.54.0


Reply via email to