[PATCH v7 6/6] ksm: add mremap selftests for ksm_rmap_walk

From: xu.xin16

Date: Sat May 30 2026 - 05:10:37 EST


From: xu xin <xu.xin16@xxxxxxxxxx>

The existing tools/testing/selftests/mm/rmap.c has already one testcase
for ksm_rmap_walk in TEST_F(migrate, ksm), which takes use of migration
of page from one NUMA node to another NUMA node. However, it just lacks
the scenario of mremapped VMAs.

We add the calling of mremap() and then trigger KSM to merge pages before
migrating, which is specifically to test an optimization which is
introduced by this patch ("ksm: Optimize rmap_walk_ksm by passing a
suitable address pgoff").

This test can reproduce the issue that Hugh points out at
https://lore.kernel.org/all/02e1b8df-d568-8cbb-b8f6-46d5476d9d75@xxxxxxxxxx/

Signed-off-by: xu xin <xu.xin16@xxxxxxxxxx>
---
tools/testing/selftests/mm/rmap.c | 97 ++++++++++++++++++++++++++++
tools/testing/selftests/mm/vm_util.c | 47 ++++++++++++++
tools/testing/selftests/mm/vm_util.h | 2 +
3 files changed, 146 insertions(+)

diff --git a/tools/testing/selftests/mm/rmap.c b/tools/testing/selftests/mm/rmap.c
index 53f2058b0ef2..e548d00762b3 100644
--- a/tools/testing/selftests/mm/rmap.c
+++ b/tools/testing/selftests/mm/rmap.c
@@ -430,4 +430,101 @@ TEST_F(migrate, ksm)
propagate_children(_metadata, data);
}

+static void prepare_pages(struct global_data *data, int nr_pages)
+{
+ /* Allocate exactly pages for the test */
+ data->mapsize = nr_pages * getpagesize();
+ data->region = mmap(NULL, data->mapsize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (data->region == MAP_FAILED)
+ ksft_exit_fail_perror("mmap failed");
+
+ /* Fill all pages with identical content to encourage KSM merging */
+ memset(data->region, 0x77, data->mapsize);
+}
+
+static int mremap_merge_and_migrate(struct global_data *data)
+{
+ int ret;
+ void *old_region;
+ void *new_region;
+ int nr_pages = 32;
+ long base_shared, base_sharing;
+ long shared, sharing;
+
+ /* Take baseline before creating our pages */
+ base_shared = ksm_get_pages_shared();
+ base_sharing = ksm_get_pages_sharing();
+ if (base_shared < 0 || base_sharing < 0)
+ return FAIL_ON_CHECK;
+
+ prepare_pages(data, nr_pages);
+
+ if (ksm_start() < 0)
+ return FAIL_ON_CHECK;
+
+ old_region = data->region;
+ /*
+ * Mremap the second half region to the first half location (FIXED).
+ */
+ new_region = mremap(old_region + data->mapsize / 2, data->mapsize / 2,
+ data->mapsize / 2, MREMAP_MAYMOVE | MREMAP_FIXED,
+ old_region);
+ if (new_region == MAP_FAILED) {
+ ksft_print_msg("mremap failed: %s\n", strerror(errno));
+ return FAIL_ON_CHECK;
+ }
+ data->region = new_region;
+ data->mapsize /= 2; /* mapping is now half of original */
+
+ if (ksm_start() < 0)
+ return FAIL_ON_CHECK;
+
+ /* Attempt to migrate the merged KSM page */
+ ret = try_to_move_page(data->region);
+ if (ret != 0) {
+ ksft_print_msg("migration of KSM page after mremap failed\n");
+ return FAIL_ON_CHECK;
+ }
+
+ /* Ensure ksmd scan two turns at least to update ksm counters */
+ if (ksm_start() < 0)
+ return FAIL_ON_CHECK;
+
+ shared = ksm_get_pages_shared();
+ sharing = ksm_get_pages_sharing();
+ if (shared < 0 || sharing < 0)
+ return FAIL_ON_CHECK;
+
+ if (shared - base_shared != 1 ||
+ sharing - base_sharing != nr_pages / 2 - 1) {
+ ksft_print_msg("Unexpected KSM counters: shared delta=%ld, sharing delta=%ld\n",
+ shared - base_shared, sharing - base_sharing);
+ return FAIL_ON_CHECK;
+ }
+
+ return 0;
+}
+
+
+TEST_F(migrate, ksm_and_mremap)
+{
+ struct global_data *data = &self->data;
+ int ret;
+
+ /* Skip if KSM is not available */
+ if (ksm_stop() < 0)
+ SKIP(return, "accessing \"/sys/kernel/mm/ksm/run\" failed");
+ if (ksm_get_full_scans() < 0)
+ SKIP(return, "accessing \"/sys/kernel/mm/ksm/full_scan\" failed");
+
+ ret = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0);
+ if (ret < 0 && errno == EINVAL)
+ SKIP(return, "PR_SET_MEMORY_MERGE not supported");
+ else if (ret)
+ ksft_exit_fail_perror("PR_SET_MEMORY_MERGE=1 failed");
+
+ ASSERT_EQ(mremap_merge_and_migrate(data), 0);
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index db94564f4431..bfa88937485e 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -648,6 +648,53 @@ long ksm_get_self_merging_pages(void)
return strtol(buf, NULL, 10);
}

+long ksm_get_pages_shared(void)
+{
+ int fd;
+ char buf[10];
+ ssize_t ret;
+ int saved_errno;
+
+ fd = open("/sys/kernel/mm/ksm/pages_shared", O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ ret = pread(fd, buf, sizeof(buf) - 1, 0);
+ saved_errno = errno;
+ close(fd);
+ if (ret <= 0) {
+ if (ret == 0)
+ return -ENODATA; /* unexpected EOF */
+ return -saved_errno;
+ }
+ buf[ret] = 0;
+ return strtol(buf, NULL, 10);
+}
+
+long ksm_get_pages_sharing(void)
+{
+ int fd;
+ char buf[10];
+ ssize_t ret;
+ int saved_errno;
+
+ fd = open("/sys/kernel/mm/ksm/pages_sharing", O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ ret = pread(fd, buf, sizeof(buf) - 1, 0);
+ saved_errno = errno;
+ close(fd);
+ if (ret <= 0) {
+ if (ret == 0)
+ return -ENODATA;
+ return -saved_errno;
+ }
+ buf[ret] = 0;
+ return strtol(buf, NULL, 10);
+}
+
+
long ksm_get_full_scans(void)
{
int ksm_full_scans_fd;
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index 1a07305ceff4..3b40727c3f1f 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -151,6 +151,8 @@ void *sys_mremap(void *old_address, unsigned long old_size,

long ksm_get_self_zero_pages(void);
long ksm_get_self_merging_pages(void);
+long ksm_get_pages_shared(void);
+long ksm_get_pages_sharing(void);
long ksm_get_full_scans(void);
int ksm_use_zero_pages(void);
int ksm_start(void);
--
2.25.1