[PATCH 05/16] selftests/mm: add khugepaged completion barrier helper

From: Kiryl Shutsemau

Date: Sun Aug 02 2026 - 15:56:13 EST


From: "Kiryl Shutsemau (Meta)" <kas@xxxxxxxxxx>

Race and functional tests need to drive khugepaged synchronously: set
up a layout, let exactly one full scan pass over it, check the result.
The khugepaged selftest already waits on full_scans advancing by two —
a completion barrier for one pass that started after setup — but it
relies on a short configured scan_sleep_millisecs to make progress.

Lift the pattern into a library helper, khugepaged_full_pass(), and
drive it by the sysfs wake path: any store to scan_sleep_millisecs
wakes the daemon, so the barrier completes promptly regardless of the
configured scan cadence. Wake exactly once per missing pass:
over-waking would queue a straggler pass behind the barrier that
overlaps and perturbs whatever the caller sets up next. One wake
completes one full pass only when the whole mm list fits in a single
scan batch, so callers must pair the helper with a large pages_to_scan.

Settings pushes and pops must not start passes nobody asked for
either, so thp_write_settings() now writes each khugepaged knob only
when it changes. Switch the khugepaged selftest order-parameterized
cases to the helper.

Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <kas@xxxxxxxxxx>
---
.../testing/selftests/mm/hugepage_settings.c | 66 ++++++++++++++++---
.../testing/selftests/mm/hugepage_settings.h | 2 +
tools/testing/selftests/mm/khugepaged.c | 17 +----
3 files changed, 61 insertions(+), 24 deletions(-)

diff --git a/tools/testing/selftests/mm/hugepage_settings.c b/tools/testing/selftests/mm/hugepage_settings.c
index d7917dce3aba..a26a0cffa9c5 100644
--- a/tools/testing/selftests/mm/hugepage_settings.c
+++ b/tools/testing/selftests/mm/hugepage_settings.c
@@ -183,6 +183,17 @@ void thp_read_settings(struct thp_settings *settings)
}
}

+/*
+ * Write only on change: any store to a khugepaged sysfs knob wakes the
+ * daemon, and settings pushes/pops must not start scan passes nobody
+ * asked for — khugepaged_full_pass() is the only sanctioned wake.
+ */
+static void thp_update_num(const char *name, unsigned long num)
+{
+ if (thp_read_num(name) != num)
+ thp_write_num(name, num);
+}
+
void thp_write_settings(struct thp_settings *settings)
{
struct khugepaged_settings *khugepaged = &settings->khugepaged;
@@ -198,15 +209,15 @@ void thp_write_settings(struct thp_settings *settings)
shmem_enabled_strings[settings->shmem_enabled]);
thp_write_num("use_zero_page", settings->use_zero_page);

- thp_write_num("khugepaged/defrag", khugepaged->defrag);
- thp_write_num("khugepaged/alloc_sleep_millisecs",
- khugepaged->alloc_sleep_millisecs);
- thp_write_num("khugepaged/scan_sleep_millisecs",
- khugepaged->scan_sleep_millisecs);
- thp_write_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none);
- thp_write_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap);
- thp_write_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared);
- thp_write_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan);
+ thp_update_num("khugepaged/defrag", khugepaged->defrag);
+ thp_update_num("khugepaged/alloc_sleep_millisecs",
+ khugepaged->alloc_sleep_millisecs);
+ thp_update_num("khugepaged/scan_sleep_millisecs",
+ khugepaged->scan_sleep_millisecs);
+ thp_update_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none);
+ thp_update_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap);
+ thp_update_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared);
+ thp_update_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan);

if (dev_queue_read_ahead_path[0])
write_num(dev_queue_read_ahead_path, settings->read_ahead_kb);
@@ -230,6 +241,43 @@ void thp_write_settings(struct thp_settings *settings)
}
}

+/*
+ * Completion barrier for khugepaged: wait until a full scan pass that
+ * started after this call has finished. full_scans must advance by two;
+ * a +1 step may complete a pass that examined this mm before the
+ * caller's setup was in place.
+ *
+ * Any store to scan_sleep_millisecs wakes the daemon, so the barrier
+ * works regardless of the configured scan cadence. It wakes exactly
+ * once per missing pass — over-waking would queue a straggler pass
+ * behind the barrier, perturbing whatever the caller sets up next.
+ * One wake completes one full pass only if the whole mm list fits in
+ * one scan batch, so callers must pair this with a large
+ * pages_to_scan.
+ */
+bool khugepaged_full_pass(unsigned int timeout_s)
+{
+ unsigned long deadline_ms = timeout_s * 1000UL;
+ unsigned long sleep_ms =
+ thp_read_num("khugepaged/scan_sleep_millisecs");
+ unsigned long elapsed_ms = 0;
+ int pass;
+
+ for (pass = 0; pass < 2; pass++) {
+ unsigned long target =
+ thp_read_num("khugepaged/full_scans") + 1;
+
+ thp_write_num("khugepaged/scan_sleep_millisecs", sleep_ms);
+ while (thp_read_num("khugepaged/full_scans") < target) {
+ if (elapsed_ms >= deadline_ms)
+ return false;
+ usleep(10 * 1000);
+ elapsed_ms += 10;
+ }
+ }
+ return true;
+}
+
struct thp_settings *thp_current_settings(void)
{
if (!settings_index) {
diff --git a/tools/testing/selftests/mm/hugepage_settings.h b/tools/testing/selftests/mm/hugepage_settings.h
index 726c73c43c05..8de446affeec 100644
--- a/tools/testing/selftests/mm/hugepage_settings.h
+++ b/tools/testing/selftests/mm/hugepage_settings.h
@@ -83,6 +83,8 @@ static inline void thp_save_settings(void)
hugepage_save_settings(/* thp = */ true, /* hugetlb = */ false);
}

+bool khugepaged_full_pass(unsigned int timeout_s);
+
void thp_set_read_ahead_path(char *path);
unsigned long thp_supported_orders(void);
unsigned long thp_shmem_supported_orders(void);
diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index 971e97a7330a..65fafab06410 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -1126,23 +1126,10 @@ static bool range_not_collapsed(void *p, size_t len)
return true;
}

-/*
- * Completion barrier: one full khugepaged pass that started after this
- * call. Waiting for full_scans to advance by two guarantees it; a +1
- * step might complete a pass that scanned our mm before the setup.
- */
static bool khugepaged_wait_full_pass(void)
{
- int full_scans = thp_read_num("khugepaged/full_scans") + 2;
- int timeout = 60; /* 30 seconds */
-
- while (timeout--) {
- if (thp_read_num("khugepaged/full_scans") >= full_scans)
- return true;
- printf(".");
- usleep(TICK);
- }
- return false;
+ /* Wait up to 30 seconds for the pass to complete. */
+ return khugepaged_full_pass(30);
}

/*
--
2.54.0