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

The harness races collapse against faults, pins, fork, mremap and
MADV_DONTNEED, but nothing in it elevates a source folio's refcount from
the reclaim or compaction side.

Add two more threads, and run every mode and occupancy limit both with and
without them:

 - pageout: cycles MADV_PAGEOUT over a dedicated neighbour region, faults
   it back in and checks the content each round, since a page's pattern
   must survive the trip through swap.  Idle when the host has no swap,
   because then there is no anon reclaim to drive.

 - compactor: writes /proc/sys/vm/compact_memory in a loop.  Compaction
   isolates and migrates folios, so it competes with a collapse for the
   pages it is gathering, with refcount elevations and migration entries
   of its own.

-p narrows a run to the combinations that have them, the way -z narrows
the occupancy and -m the mode.  Each result says which it ran:

  ok 2 stepped/strict/pressure: 5s, 88 steps, no corruption

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

diff --git a/tools/testing/selftests/mm/khugepaged_race.c 
b/tools/testing/selftests/mm/khugepaged_race.c
index a2a91906285b..abd142db7cbe 100644
--- a/tools/testing/selftests/mm/khugepaged_race.c
+++ b/tools/testing/selftests/mm/khugepaged_race.c
@@ -18,6 +18,12 @@
  * zero-filled, and re-checked under the page table lock at install time in
  * case a racing fault got there first.
  *
+ * -p adds memory pressure to any of the above: MADV_PAGEOUT cycling
+ * on a dedicated neighbor region (swap traffic and LRU churn; skipped
+ * with a note when the host has no swap) and a compact_memory trigger
+ * loop (compaction migrates source folios, racing collapse's freeze
+ * with refcount elevation and migration entries of its own).
+ *
  * Correctness signals: every racing page must read as its pattern or
  * zero (MADV_DONTNEED), never anything else.  The faulters and the fork
  * children check that continuously, a final sweep checks it once more, plus
@@ -61,6 +67,8 @@ static unsigned long page_size;
 static char *region;           /* NR_AREAS * hpage_pmd_size */
 static char *mremap_area;      /* region + NR_SHARED_AREAS areas */
 static char *mremap_scratch;   /* well above the region */
+static char *pageout_area;     /* -p: dedicated pressure region */
+static size_t pageout_size;
 static int gup_fd = -1;
 static volatile int stop;
 static volatile int corrupted;
@@ -188,6 +196,70 @@ static void *mremapper_fn(void *arg)
        return NULL;
 }
 
+/*
+ * -p: swap traffic and LRU churn on a region of our own. The content
+ * check is exact: a page out and back through swap must preserve the
+ * pattern, and nothing else ever writes here.
+ */
+static void *pageout_fn(void *arg)
+{
+       unsigned int seed = (unsigned long)arg;
+       unsigned long nr = pageout_size / page_size;
+       unsigned long i;
+
+       for (i = 0; i < nr; i++)
+               *(unsigned int *)(pageout_area + i * page_size) = pattern(i);
+
+       while (!stop) {
+               madvise(pageout_area, pageout_size, MADV_PAGEOUT);
+               for (i = 0; i < nr && !stop; i++) {
+                       unsigned int val = *(unsigned int *)(pageout_area +
+                                                            i * page_size);
+
+                       if (val != pattern(i)) {
+                               corrupted = 1;
+                               ksft_print_msg("Pageout corruption at page %lu: 
%#x != %#x\n",
+                                              i, val, pattern(i));
+                       }
+               }
+               usleep(rand_r(&seed) % 2000);
+       }
+       return NULL;
+}
+
+/* -p: compaction migrates the collapse sources out from under us. */
+static void *compactor_fn(void *arg)
+{
+       unsigned int seed = (unsigned long)arg;
+       int fd = open("/proc/sys/vm/compact_memory", O_WRONLY);
+
+       if (fd < 0) {
+               ksft_print_msg("No compact_memory; compactor idle\n");
+               return NULL;
+       }
+       while (!stop) {
+               if (write(fd, "1", 1) < 0)
+                       break;
+               usleep(10000 + rand_r(&seed) % 100000);
+       }
+       close(fd);
+       return NULL;
+}
+
+static bool swap_available(void)
+{
+       char line[256];
+       int lines = 0;
+       FILE *fp = fopen("/proc/swaps", "r");
+
+       if (!fp)
+               return false;
+       while (fgets(line, sizeof(line), fp))
+               lines++;
+       fclose(fp);
+       return lines > 1;
+}
+
 static unsigned long now_ms(void)
 {
        struct timeval tv;
@@ -199,11 +271,14 @@ static unsigned long now_ms(void)
 static void usage(void)
 {
        fprintf(stderr,
-               "Usage: khugepaged_race [-d seconds] [-m stepped|free|madvise] 
[-z] [-a areas]\n"
+               "Usage: khugepaged_race [-d seconds] [-m stepped|free|madvise] 
[-z] [-p] [-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"
+               "\tRuns with and without memory pressure unless -p asks for\n"
+               "\tpressure only.\n"
+               "\t-p: only with the pageout and compaction threads\n"
                "\t-a: number of shared PMD-sized playground areas (default 
3)\n");
        exit(1);
 }
@@ -212,18 +287,22 @@ int main(int argc, char **argv)
 {
        static const char * const thread_names[] = {
                "faulter", "faulter2", "dontneed", "pinner", "forker",
-               "mremapper",
+               "mremapper", "pageout", "compactor",
        };
        void *(*const thread_fns[])(void *) = {
                faulter_fn, faulter_fn, dontneed_fn, pinner_fn, forker_fn,
-               mremapper_fn,
+               mremapper_fn, pageout_fn, compactor_fn,
        };
+       const unsigned long pageout_bit = 1UL << 6, compactor_bit = 1UL << 7;
        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 */
+       static const int all_press[] = { 0, 1 };        /* quiet, under 
pressure */
        const int *nones = all_nones;
+       const int *press = all_press;
        int nr_nones = ARRAY_SIZE(all_nones);
+       int nr_press = ARRAY_SIZE(all_press);
        const char *one_mode[1];
        const char * const *modes = all_modes;
        int nr_modes = ARRAY_SIZE(all_modes);
@@ -232,13 +311,15 @@ int main(int argc, char **argv)
        unsigned long end_ms;
        int duration_s = 5;
        unsigned long thread_mask = ~0UL;
+       unsigned long base_mask;
        int nr_areas_arg = 0;
        bool holes_only = false;
+       bool pressure_only = false;
        unsigned long i;
        int steps = 0;
        int opt;
 
-       while ((opt = getopt(argc, argv, "a:d:m:t:zh")) != -1) {
+       while ((opt = getopt(argc, argv, "a:d:m:t:zph")) != -1) {
                switch (opt) {
                case 'a':
                        nr_areas_arg = atoi(optarg);
@@ -256,6 +337,9 @@ int main(int argc, char **argv)
                case 'z':
                        holes_only = true;
                        break;
+               case 'p':
+                       pressure_only = true;
+                       break;
                default:
                        usage();
                }
@@ -265,6 +349,11 @@ int main(int argc, char **argv)
                nr_nones = 1;
        }
 
+       if (pressure_only) {
+               press = all_press + 1;
+               nr_press = 1;
+       }
+
        if (mode_arg) {
                if (strcmp(mode_arg, "stepped") && strcmp(mode_arg, "free") &&
                    strcmp(mode_arg, "madvise"))
@@ -304,7 +393,12 @@ int main(int argc, char **argv)
                 -1, 0) != (void *)mremap_scratch)
                ksft_exit_fail_perror("mmap() mremap scratch");
 
-       ksft_set_plan(nr_modes * nr_nones);
+       base_mask = thread_mask;
+       if (!swap_available())
+               /* No swap, no anon reclaim: compaction-only pressure. */
+               ksft_print_msg("no swap: the pageout thread stays idle\n");
+
+       ksft_set_plan(nr_modes * nr_nones * nr_press);
 
        thp_save_settings();
        thp_read_settings(&settings);
@@ -316,9 +410,17 @@ int main(int argc, char **argv)
         */
        thp_push_settings(&settings);
 
-       for (int mn = 0; mn < nr_modes * nr_nones; mn++) {
-               const char *mode = modes[mn / nr_nones];
-               bool holes = nones[mn % nr_nones];
+       for (int run = 0; run < nr_modes * nr_nones * nr_press; run++) {
+               int rem = run % (nr_nones * nr_press);
+               const char *mode = modes[run / (nr_nones * nr_press)];
+               bool holes = nones[rem / nr_press];
+               bool pressure = press[rem % nr_press];
+
+               thread_mask = base_mask;
+               if (!pressure)
+                       thread_mask &= ~(pageout_bit | compactor_bit);
+               else if (!swap_available())
+                       thread_mask &= ~pageout_bit;
 
                thp_read_settings(&settings);
                settings.thp_enabled = THP_MADVISE;
@@ -354,6 +456,24 @@ int main(int argc, char **argv)
                        ksft_exit_fail_perror("mmap() playground");
                mremap_area = region + nr_shared_areas * hpage_pmd_size;
 
+               if (thread_mask & pageout_bit) {
+                       /*
+                        * Big enough to cycle real reclaim, small enough not
+                        * to dominate a TCG guest: 4 PMD areas, clamped to
+                        * [16M, 64M].
+                        */
+                       pageout_size = 4 * hpage_pmd_size;
+                       pageout_size = pageout_size < (16UL << 20) ?
+                                      (16UL << 20) :
+                                      pageout_size > (64UL << 20) ?
+                                      (64UL << 20) : pageout_size;
+                       pageout_area = mmap(NULL, pageout_size,
+                                           PROT_READ | PROT_WRITE,
+                                           MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+                       if (pageout_area == MAP_FAILED)
+                               ksft_exit_fail_perror("mmap() pageout area");
+               }
+
                /* Populate so the first pass has something to collapse. */
                for (i = 0; i < nr_shared_areas * hpage_pmd_size / page_size; 
i++)
                        *(unsigned int *)(region + i * page_size) = pattern(i);
@@ -403,8 +523,9 @@ int main(int argc, char **argv)
                        check_page(i);
 
                ksft_test_result(!corrupted,
-                                "%s/%s: %ds, %d steps, no corruption\n",
+                                "%s/%s%s: %ds, %d steps, no corruption\n",
                                 mode, holes ? "holes" : "strict",
+                                pressure ? "/pressure" : "",
                                 duration_s, steps);
 
                /*
@@ -413,15 +534,19 @@ int main(int argc, char **argv)
                 * and its scan cadence differs.
                 */
                munmap(region, nr_areas * hpage_pmd_size);
+               if (pageout_area) {
+                       munmap(pageout_area, pageout_size);
+                       pageout_area = NULL;
+               }
                thp_pop_settings();
                stop = 0;
                steps = 0;
 
                if (corrupted) {
                        /* Memory is suspect; the rest would prove nothing. */
-                       while (++mn < nr_modes * nr_nones)
+                       while (++run < nr_modes * nr_nones * nr_press)
                                ksft_test_result_skip("%s: skipped after 
corruption\n",
-                                                     modes[mn / nr_nones]);
+                                                     modes[run / (nr_nones * 
nr_press)]);
                        break;
                }
        }
-- 
2.54.0


Reply via email to