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

