[PATCH v4 5/5] ksm: add mremap selftests for ksm_rmap_walk
From: xu.xin16
Date: Sun May 03 2026 - 08:52:04 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.
There are one worker process and several checker processes, and in worker,
we add the calling of mremap() and then trigger KSM to merge pages before
migrating, , which is specailly to test a optimization which is introduced
by this patch ("ksm: Optimize rmap_walk_ksm by passing a suitable address
range"). In other checker processes, we just trigger KSM to merge pages
from each child process and check their PFN.
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 | 79 ++++++++++++++++++++++++++++
tools/testing/selftests/mm/vm_util.c | 38 +++++++++++++
tools/testing/selftests/mm/vm_util.h | 2 +
3 files changed, 119 insertions(+)
diff --git a/tools/testing/selftests/mm/rmap.c b/tools/testing/selftests/mm/rmap.c
index 53f2058b0ef2..fced1f6304ac 100644
--- a/tools/testing/selftests/mm/rmap.c
+++ b/tools/testing/selftests/mm/rmap.c
@@ -430,4 +430,83 @@ TEST_F(migrate, ksm)
propagate_children(_metadata, data);
}
+static void prepare_two_pages(struct global_data *data)
+{
+ /* Allocate exactly 2 pages for the test */
+ data->mapsize = 2 * 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 both 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, pagemap_fd;
+ void *old_region = data->region;
+ unsigned long page_sz = getpagesize();
+
+ /*
+ * Mremap the second page to the first page's location (FIXED).
+ * This effectively overwrites the first page, leaving the second page
+ * unmapped. The physical page originally at the second page is now
+ * mapped at the first page's virtual address.
+ */
+ data->region = mremap(old_region + page_sz, page_sz, page_sz,
+ MREMAP_MAYMOVE | MREMAP_FIXED, old_region);
+ if (data->region == MAP_FAILED) {
+ ksft_print_msg("mremap failed: %s\n", strerror(errno));
+ return FAIL_ON_CHECK;
+ }
+
+ /* Ensure KSM is active and wait for merging */
+ if (ksm_start() < 0) {
+ ksft_print_msg("KSM start failed\n");
+ 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;
+ }
+
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+ if (pagemap_fd == -1)
+ return FAIL_ON_WORK;
+ *data->expected_pfn = pagemap_get_pfn(pagemap_fd, data->region);
+
+ 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");
+
+ /* Assign the three callbacks required by propagate_children */
+ data->do_prepare = prepare_two_pages;
+ data->do_work = mremap_merge_and_migrate;
+ data->do_check = has_same_pfn;
+
+ /* Run the test in a process tree to stress rmap locking */
+ propagate_children(_metadata, data);
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index db94564f4431..a33a4069de7c 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -648,6 +648,44 @@ long ksm_get_self_merging_pages(void)
return strtol(buf, NULL, 10);
}
+long ksm_get_pages_shared(void)
+{
+ int ksm_pages_shared_fd;
+ char buf[10];
+ ssize_t ret;
+
+ ksm_pages_shared_fd = open("/sys/kernel/mm/ksm/pages_shared", O_RDONLY);
+ if (ksm_pages_shared_fd < 0)
+ return -errno;
+
+ ret = pread(ksm_pages_shared_fd, buf, sizeof(buf) - 1, 0);
+ close(ksm_pages_shared_fd);
+ if (ret <= 0)
+ return -errno;
+ buf[ret] = 0;
+
+ return strtol(buf, NULL, 10);
+}
+
+long ksm_get_pages_sharing(void)
+{
+ int ksm_pages_sharing_fd;
+ char buf[10];
+ ssize_t ret;
+
+ ksm_pages_sharing_fd = open("/sys/kernel/mm/ksm/pages_sharing", O_RDONLY);
+ if (ksm_pages_sharing_fd < 0)
+ return -errno;
+
+ ret = pread(ksm_pages_sharing_fd, buf, sizeof(buf) - 1, 0);
+ close(ksm_pages_sharing_fd);
+ if (ret <= 0)
+ return -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