[PATCH 2/2] selftests/mm: cover merging KSM pages aged by smart scan
From: Qiliang Yuan
Date: Fri Oct 16 2026 - 10:52:18 EST
ksm_functional_tests checks merging and unmerging with ksmd's default
settings, but nothing there exercises smart scan, which skips pages KSM
has failed to merge for a while.
Smart scan used to time those skips per page, so two identical pages
that became mergeable some scans apart could end up never being looked
at in the same scan, and were never merged. "mm/ksm: align smart scan
skips with the scan number" fixed that.
Add a case that lets sixteen unique pages age for a dozen scans, then
makes a duplicate of each mergeable one scan apart, and expects every
pair to be merged 32 scans later:
kernel failures
before the fix 20/20
with the fix 0/20
Signed-off-by: Qiliang Yuan <odys.yuan@xxxxxxxxx>
---
tools/testing/selftests/mm/ksm_functional_tests.c | 108 +++++++++++++++++++++-
1 file changed, 107 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/mm/ksm_functional_tests.c b/tools/testing/selftests/mm/ksm_functional_tests.c
index 31c06c72203fd..71d9b3cbdb8c1 100644
--- a/tools/testing/selftests/mm/ksm_functional_tests.c
+++ b/tools/testing/selftests/mm/ksm_functional_tests.c
@@ -695,6 +695,111 @@ static void test_fork_ksm_merging_page_count(void)
munmap(map, size);
}
+static int ksm_wait_full_scans(long nr)
+{
+ long start, cur;
+
+ start = ksm_get_full_scans();
+ if (start < 0)
+ return start;
+ do {
+ cur = ksm_get_full_scans();
+ if (cur < 0)
+ return cur;
+ } while (cur < start + nr);
+
+ return 0;
+}
+
+static void test_merge_aged_pairs(void)
+{
+ const unsigned int pairs = 16;
+ const unsigned long size = 2 * pairs * pagesize;
+ char *map, old_smart_scan = '1';
+ int smart_scan_fd;
+ unsigned int i;
+
+ ksft_print_msg("[RUN] %s\n", __func__);
+
+ smart_scan_fd = open("/sys/kernel/mm/ksm/smart_scan", O_RDWR);
+ if (smart_scan_fd < 0) {
+ ksft_test_result_skip("smart_scan not supported\n");
+ return;
+ }
+ if (pread(smart_scan_fd, &old_smart_scan, 1, 0) != 1 ||
+ pwrite(smart_scan_fd, "1", 1, 0) != 1) {
+ ksft_test_result_fail("Enabling smart_scan failed\n");
+ goto close_fd;
+ }
+
+ if (ksm_stop() < 0) {
+ ksft_test_result_fail("Disabling (unmerging) KSM failed\n");
+ goto restore;
+ }
+
+ map = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (map == MAP_FAILED) {
+ ksft_test_result_fail("mmap() failed\n");
+ goto restore;
+ }
+ if (madvise(map, size, MADV_NOHUGEPAGE) && errno != EINVAL) {
+ ksft_test_result_fail("MADV_NOHUGEPAGE failed\n");
+ goto unmap;
+ }
+
+ /*
+ * Page i of the first half and page i of the second half form a
+ * pair with content no other page shares.
+ */
+ for (i = 0; i < pairs; i++) {
+ memset(map + i * pagesize, i + 1, pagesize);
+ memset(map + (pairs + i) * pagesize, i + 1, pagesize);
+ }
+
+ /* Let the first page of each pair age without a duplicate. */
+ if (madvise(map, size / 2, MADV_MERGEABLE)) {
+ ksft_test_result_fail("MADV_MERGEABLE failed\n");
+ goto unmap;
+ }
+ if (ksm_start() < 0 || ksm_wait_full_scans(12) < 0) {
+ ksft_test_result_fail("Running KSM failed\n");
+ goto unmap;
+ }
+
+ /*
+ * Make the duplicates mergeable one scan apart so that each pair
+ * starts out with a different distance in scans.
+ */
+ for (i = 0; i < pairs; i++) {
+ if (madvise(map + (pairs + i) * pagesize, pagesize,
+ MADV_MERGEABLE)) {
+ ksft_test_result_fail("MADV_MERGEABLE failed\n");
+ goto unmap;
+ }
+ if (ksm_wait_full_scans(1) < 0) {
+ ksft_test_result_fail("Running KSM failed\n");
+ goto unmap;
+ }
+ }
+
+ if (ksm_wait_full_scans(32) < 0) {
+ ksft_test_result_fail("Running KSM failed\n");
+ goto unmap;
+ }
+
+ ksft_test_result(ksm_get_self_merging_pages() == 2 * pairs,
+ "Pages aged by smart scan got merged\n");
+unmap:
+ ksm_stop();
+ munmap(map, size);
+restore:
+ if (pwrite(smart_scan_fd, &old_smart_scan, 1, 0) != 1)
+ ksft_print_msg("Restoring smart_scan failed\n");
+close_fd:
+ close(smart_scan_fd);
+}
+
static void init_global_file_handles(void)
{
mem_fd = open("/proc/self/mem", O_RDWR);
@@ -720,7 +825,7 @@ static void init_global_file_handles(void)
int main(int argc, char **argv)
{
- unsigned int tests = 9;
+ unsigned int tests = 10;
int err;
if (argc > 1 && !strcmp(argv[1], FORK_EXEC_CHILD_PRG_NAME)) {
@@ -753,6 +858,7 @@ int main(int argc, char **argv)
test_prctl_fork_exec();
test_prctl_unmerge();
test_fork_ksm_merging_page_count();
+ test_merge_aged_pairs();
err = ksft_get_fail_cnt();
if (err)
--
2.43.0