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. A window with holes takes a different route, and never gets raced. A hole is zero-filled into the new folio by clear_user_highpage() rather than copied from anywhere. Which slots count as holes keeps moving under the racing MADV_DONTNEED, right up to the moment the PMD is detached.
Run both ends of the occupancy scale for every driver mode, one after the other. 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 Assisted-by: Claude-Code:claude-opus-5 Tested-by: Muhammad Usama Anjum <[email protected]> Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]> --- tools/testing/selftests/mm/khugepaged_race.c | 45 ++++++++++++-------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/mm/khugepaged_race.c b/tools/testing/selftests/mm/khugepaged_race.c index 3128031e28bc..3b369046edf5 100644 --- a/tools/testing/selftests/mm/khugepaged_race.c +++ b/tools/testing/selftests/mm/khugepaged_race.c @@ -11,9 +11,11 @@ * free khugepaged left to run (scan_sleep_millisecs=0), for soak; * madvise MADV_COLLAPSE and MADV_DONTNEED 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. A hole is zero-filled into the new folio rather than + * copied, and the racing MADV_DONTNEED keeps moving which slots are holes. * * Correctness signals: every racing page must read as its pattern or * zero (MADV_DONTNEED), never anything else. The faulters and the fork @@ -247,6 +249,8 @@ 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 bool occupancies[] = { false, true }; /* strict, holes */ + const int nr_occupancies = ARRAY_SIZE(occupancies); const char *one_mode[1]; const char * const *modes = all_modes; int nr_modes = ARRAY_SIZE(all_modes); @@ -279,6 +283,7 @@ int main(int argc, char **argv) usage(); } } + if (mode_arg) { if (strcmp(mode_arg, "stepped") && strcmp(mode_arg, "free") && strcmp(mode_arg, "madvise")) @@ -318,7 +323,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_occupancies); thp_save_settings(); thp_read_settings(&settings); @@ -330,8 +335,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_occupancies; mn++) { + const char *mode = modes[mn / nr_occupancies]; + bool holes = occupancies[mn % nr_occupancies]; thp_read_settings(&settings); settings.thp_enabled = THP_MADVISE; @@ -341,14 +347,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++) { @@ -415,8 +423,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 @@ -430,9 +439,11 @@ int main(int argc, char **argv) if (corrupted) { /* Memory is suspect; the rest would prove nothing. */ - while (++m < nr_modes) - ksft_test_result_skip("%s: skipped after corruption\n", - modes[m]); + while (++mn < nr_modes * nr_occupancies) + ksft_test_result_skip("%s/%s: skipped after corruption\n", + modes[mn / nr_occupancies], + occupancies[mn % nr_occupancies] ? + "holes" : "strict"); break; } } -- 2.54.0

