* [RFC Patch 0/2] selftests/mm: assert rmap behave as expected
@ 2025-06-04 8:21 Wei Yang
2025-06-04 8:21 ` [RFC Patch 1/2] selftests/mm: put general ksm operation into vm_util Wei Yang
2025-06-04 8:21 ` [RFC Patch 2/2] selftests/mm: assert rmap behave as expected Wei Yang
0 siblings, 2 replies; 9+ messages in thread
From: Wei Yang @ 2025-06-04 8:21 UTC (permalink / raw)
To: akpm, david, lorenzo.stoakes, riel, Liam.Howlett, vbabka,
harry.yoo
Cc: linux-mm, linux-kselftest, Wei Yang
As David suggested, currently we don't have a high level test case to
verify the behavior of rmap. This patch set introduce the verification
on rmap by migration.
Patch 1 is a preparation to move ksm related operation into vm_util.
Patch 2 is the new test case.
Currently it covers following four scenarios:
* anonymous page
* shmem page
* pagecache page
* ksm page
Wei Yang (2):
selftests/mm: put general ksm operation into vm_util
selftests/mm: assert rmap behave as expected
MAINTAINERS | 1 +
tools/testing/selftests/mm/.gitignore | 1 +
tools/testing/selftests/mm/Makefile | 3 +
.../selftests/mm/ksm_functional_tests.c | 76 +--
tools/testing/selftests/mm/rmap.c | 466 ++++++++++++++++++
tools/testing/selftests/mm/run_vmtests.sh | 4 +
tools/testing/selftests/mm/vm_util.c | 71 +++
tools/testing/selftests/mm/vm_util.h | 7 +
8 files changed, 563 insertions(+), 66 deletions(-)
create mode 100644 tools/testing/selftests/mm/rmap.c
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC Patch 1/2] selftests/mm: put general ksm operation into vm_util
2025-06-04 8:21 [RFC Patch 0/2] selftests/mm: assert rmap behave as expected Wei Yang
@ 2025-06-04 8:21 ` Wei Yang
2025-07-11 15:37 ` David Hildenbrand
2025-06-04 8:21 ` [RFC Patch 2/2] selftests/mm: assert rmap behave as expected Wei Yang
1 sibling, 1 reply; 9+ messages in thread
From: Wei Yang @ 2025-06-04 8:21 UTC (permalink / raw)
To: akpm, david, lorenzo.stoakes, riel, Liam.Howlett, vbabka,
harry.yoo
Cc: linux-mm, linux-kselftest, Wei Yang
There are some general ksm operations could be used by other related
test case. Put them into vm_util for common use.
This is a preparation patch for later use.
Signed-off-by: Wei Yang <richard.weiyang@gmail.com>
Suggested-by: David Hildenbrand <david@redhat.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Cc: Rik van Riel <riel@surriel.com>
Cc: Liam R. Howlett <Liam.Howlett@oracle.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Harry Yoo <harry.yoo@oracle.com>
---
.../selftests/mm/ksm_functional_tests.c | 76 +++----------------
tools/testing/selftests/mm/vm_util.c | 71 +++++++++++++++++
tools/testing/selftests/mm/vm_util.h | 7 ++
3 files changed, 88 insertions(+), 66 deletions(-)
diff --git a/tools/testing/selftests/mm/ksm_functional_tests.c b/tools/testing/selftests/mm/ksm_functional_tests.c
index b61803e36d1c..9fb79f6bf86d 100644
--- a/tools/testing/selftests/mm/ksm_functional_tests.c
+++ b/tools/testing/selftests/mm/ksm_functional_tests.c
@@ -73,74 +73,18 @@ static bool range_maps_duplicates(char *addr, unsigned long size)
return false;
}
-static long get_my_ksm_zero_pages(void)
-{
- char buf[200];
- char *substr_ksm_zero;
- size_t value_pos;
- ssize_t read_size;
- unsigned long my_ksm_zero_pages;
-
- if (!proc_self_ksm_stat_fd)
- return 0;
-
- read_size = pread(proc_self_ksm_stat_fd, buf, sizeof(buf) - 1, 0);
- if (read_size < 0)
- return -errno;
-
- buf[read_size] = 0;
-
- substr_ksm_zero = strstr(buf, "ksm_zero_pages");
- if (!substr_ksm_zero)
- return 0;
-
- value_pos = strcspn(substr_ksm_zero, "0123456789");
- my_ksm_zero_pages = strtol(substr_ksm_zero + value_pos, NULL, 10);
-
- return my_ksm_zero_pages;
-}
-
-static long get_my_merging_pages(void)
-{
- char buf[10];
- ssize_t ret;
-
- if (proc_self_ksm_merging_pages_fd < 0)
- return proc_self_ksm_merging_pages_fd;
-
- ret = pread(proc_self_ksm_merging_pages_fd, buf, sizeof(buf) - 1, 0);
- if (ret <= 0)
- return -errno;
- buf[ret] = 0;
-
- return strtol(buf, NULL, 10);
-}
-
-static long ksm_get_full_scans(void)
-{
- char buf[10];
- ssize_t ret;
-
- ret = pread(ksm_full_scans_fd, buf, sizeof(buf) - 1, 0);
- if (ret <= 0)
- return -errno;
- buf[ret] = 0;
-
- return strtol(buf, NULL, 10);
-}
-
static int ksm_merge(void)
{
long start_scans, end_scans;
/* Wait for two full scans such that any possible merging happened. */
- start_scans = ksm_get_full_scans();
+ start_scans = ksm_get_full_scans(ksm_full_scans_fd);
if (start_scans < 0)
return start_scans;
- if (write(ksm_fd, "1", 1) != 1)
+ if (ksm_start_and_merge(ksm_fd) != 1)
return -errno;
do {
- end_scans = ksm_get_full_scans();
+ end_scans = ksm_get_full_scans(ksm_full_scans_fd);
if (end_scans < 0)
return end_scans;
} while (end_scans < start_scans + 2);
@@ -150,7 +94,7 @@ static int ksm_merge(void)
static int ksm_unmerge(void)
{
- if (write(ksm_fd, "2", 1) != 1)
+ if (ksm_stop_and_unmerge(ksm_fd) != 1)
return -errno;
return 0;
}
@@ -168,7 +112,7 @@ static char *__mmap_and_merge_range(char val, unsigned long size, int prot,
return err_map;
}
- if (get_my_merging_pages() > 0) {
+ if (ksm_get_self_merging_pages(proc_self_ksm_merging_pages_fd) > 0) {
ksft_print_msg("Still pages merged\n");
return err_map;
}
@@ -227,7 +171,7 @@ static char *__mmap_and_merge_range(char val, unsigned long size, int prot,
* Check if anything was merged at all. Ignore the zero page that is
* accounted differently (depending on kernel support).
*/
- if (val && !get_my_merging_pages()) {
+ if (val && !ksm_get_self_merging_pages(proc_self_ksm_merging_pages_fd)) {
ksft_print_msg("No pages got merged\n");
goto unmap;
}
@@ -294,7 +238,7 @@ static void test_unmerge_zero_pages(void)
ksft_test_result_skip("open \"/sys/kernel/mm/ksm/use_zero_pages\" failed\n");
return;
}
- if (write(ksm_use_zero_pages_fd, "1", 1) != 1) {
+ if (ksm_use_zero_pages(ksm_use_zero_pages_fd) != 1) {
ksft_test_result_skip("write \"/sys/kernel/mm/ksm/use_zero_pages\" failed\n");
return;
}
@@ -306,7 +250,7 @@ static void test_unmerge_zero_pages(void)
/* Check if ksm_zero_pages is updated correctly after KSM merging */
pages_expected = size / pagesize;
- if (pages_expected != get_my_ksm_zero_pages()) {
+ if (pages_expected != ksm_get_self_zero_pages(proc_self_ksm_stat_fd)) {
ksft_test_result_fail("'ksm_zero_pages' updated after merging\n");
goto unmap;
}
@@ -319,7 +263,7 @@ static void test_unmerge_zero_pages(void)
/* Check if ksm_zero_pages is updated correctly after unmerging */
pages_expected /= 2;
- if (pages_expected != get_my_ksm_zero_pages()) {
+ if (pages_expected != ksm_get_self_zero_pages(proc_self_ksm_stat_fd)) {
ksft_test_result_fail("'ksm_zero_pages' updated after unmerging\n");
goto unmap;
}
@@ -329,7 +273,7 @@ static void test_unmerge_zero_pages(void)
*((unsigned int *)&map[offs]) = offs;
/* Now we should have no zeropages remaining. */
- if (get_my_ksm_zero_pages()) {
+ if (ksm_get_self_zero_pages(proc_self_ksm_stat_fd)) {
ksft_test_result_fail("'ksm_zero_pages' updated after write fault\n");
goto unmap;
}
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 1357e2d6a7b6..115422e9eb68 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -486,3 +486,74 @@ int close_procmap(struct procmap_fd *procmap)
{
return close(procmap->fd);
}
+
+int ksm_use_zero_pages(int ksm_use_zero_pages_fd)
+{
+ return write(ksm_use_zero_pages_fd, "1", 1);
+}
+
+int ksm_start_and_merge(int ksm_fd)
+{
+ return write(ksm_fd, "1", 1);
+}
+
+int ksm_stop_and_unmerge(int ksm_fd)
+{
+ return write(ksm_fd, "2", 1);
+}
+
+long ksm_get_full_scans(int ksm_full_scans_fd)
+{
+ char buf[10];
+ ssize_t ret;
+
+ ret = pread(ksm_full_scans_fd, buf, sizeof(buf) - 1, 0);
+ if (ret <= 0)
+ return -errno;
+ buf[ret] = 0;
+
+ return strtol(buf, NULL, 10);
+}
+
+long ksm_get_self_merging_pages(int proc_self_ksm_merging_pages_fd)
+{
+ char buf[10];
+ ssize_t ret;
+
+ if (proc_self_ksm_merging_pages_fd < 0)
+ return proc_self_ksm_merging_pages_fd;
+
+ ret = pread(proc_self_ksm_merging_pages_fd, buf, sizeof(buf) - 1, 0);
+ if (ret <= 0)
+ return -errno;
+ buf[ret] = 0;
+
+ return strtol(buf, NULL, 10);
+}
+
+long ksm_get_self_zero_pages(int proc_self_ksm_stat_fd)
+{
+ char buf[200];
+ char *substr_ksm_zero;
+ size_t value_pos;
+ ssize_t read_size;
+ unsigned long my_ksm_zero_pages;
+
+ if (!proc_self_ksm_stat_fd)
+ return 0;
+
+ read_size = pread(proc_self_ksm_stat_fd, buf, sizeof(buf) - 1, 0);
+ if (read_size < 0)
+ return -errno;
+
+ buf[read_size] = 0;
+
+ substr_ksm_zero = strstr(buf, "ksm_zero_pages");
+ if (!substr_ksm_zero)
+ return 0;
+
+ value_pos = strcspn(substr_ksm_zero, "0123456789");
+ my_ksm_zero_pages = strtol(substr_ksm_zero + value_pos, NULL, 10);
+
+ return my_ksm_zero_pages;
+}
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index 9211ba640d9c..99c1b1aa1813 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -95,6 +95,13 @@ static inline int open_self_procmap(struct procmap_fd *procmap_out)
return open_procmap(pid, procmap_out);
}
+int ksm_use_zero_pages(int ksm_use_zero_pages_fd);
+int ksm_start_and_merge(int ksm_fd);
+int ksm_stop_and_unmerge(int ksm_fd);
+long ksm_get_full_scans(int ksm_full_scans_fd);
+long ksm_get_self_merging_pages(int proc_self_ksm_merging_pages_fd);
+long ksm_get_self_zero_pages(int proc_self_ksm_stat_fd);
+
/*
* On ppc64 this will only work with radix 2M hugepage size
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [RFC Patch 2/2] selftests/mm: assert rmap behave as expected
2025-06-04 8:21 [RFC Patch 0/2] selftests/mm: assert rmap behave as expected Wei Yang
2025-06-04 8:21 ` [RFC Patch 1/2] selftests/mm: put general ksm operation into vm_util Wei Yang
@ 2025-06-04 8:21 ` Wei Yang
2025-06-04 8:34 ` Wei Yang
1 sibling, 1 reply; 9+ messages in thread
From: Wei Yang @ 2025-06-04 8:21 UTC (permalink / raw)
To: akpm, david, lorenzo.stoakes, riel, Liam.Howlett, vbabka,
harry.yoo
Cc: linux-mm, linux-kselftest, Wei Yang
As David suggested, currently we don't have a high level test case to
verify the behavior of rmap. This patch introduce the verification on
rmap by migration.
The general idea is if migrate one shared page between processes, this
would be reflected in all related processes. Otherwise, we have problem
in rmap.
Currently it covers following four scenarios:
* anonymous page
* shmem page
* pagecache page
* ksm page
Signed-off-by: Wei Yang <richard.weiyang@gmail.com>
Suggested-by: David Hildenbrand <david@redhat.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Cc: Rik van Riel <riel@surriel.com>
Cc: Liam R. Howlett <Liam.Howlett@oracle.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Harry Yoo <harry.yoo@oracle.com>
---
MAINTAINERS | 1 +
tools/testing/selftests/mm/.gitignore | 1 +
tools/testing/selftests/mm/Makefile | 3 +
tools/testing/selftests/mm/rmap.c | 466 ++++++++++++++++++++++
tools/testing/selftests/mm/run_vmtests.sh | 4 +
5 files changed, 475 insertions(+)
create mode 100644 tools/testing/selftests/mm/rmap.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 0ecc6063b2b5..879138db85e6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15664,6 +15664,7 @@ L: linux-mm@kvack.org
S: Maintained
F: include/linux/rmap.h
F: mm/rmap.c
+F: tools/testing/selftests/mm/rmap.c
MEMORY MANAGEMENT - SECRETMEM
M: Andrew Morton <akpm@linux-foundation.org>
diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore
index 824266982aa3..ff4428d8bc5d 100644
--- a/tools/testing/selftests/mm/.gitignore
+++ b/tools/testing/selftests/mm/.gitignore
@@ -60,3 +60,4 @@ pkey_sighandler_tests_32
pkey_sighandler_tests_64
guard-regions
merge
+rmap
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index ae6f994d3add..c7e3a19b5555 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -100,6 +100,7 @@ TEST_GEN_FILES += hugetlb_dio
TEST_GEN_FILES += droppable
TEST_GEN_FILES += guard-regions
TEST_GEN_FILES += merge
+TEST_GEN_FILES += rmap
ifneq ($(ARCH),arm64)
TEST_GEN_FILES += soft-dirty
@@ -227,6 +228,8 @@ $(OUTPUT)/ksm_tests: LDLIBS += -lnuma
$(OUTPUT)/migration: LDLIBS += -lnuma
+$(OUTPUT)/rmap: LDLIBS += -lnuma
+
local_config.mk local_config.h: check_config.sh
/bin/sh ./check_config.sh $(CC)
diff --git a/tools/testing/selftests/mm/rmap.c b/tools/testing/selftests/mm/rmap.c
new file mode 100644
index 000000000000..5b3ea5252d83
--- /dev/null
+++ b/tools/testing/selftests/mm/rmap.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RMAP functional tests
+ *
+ * Author(s): Wei Yang <richard.weiyang@gmail.com>
+ */
+
+#include "../kselftest_harness.h"
+#include <strings.h>
+#include <pthread.h>
+#include <numa.h>
+#include <numaif.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/sem.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "vm_util.h"
+
+#define TOTAL_LEVEL 5
+#define MAX_CHILDREN 3
+
+#define FAIL_ON_CHECK (1 << 0)
+#define FAIL_ON_WORK (1 << 1)
+
+struct sembuf sem_wait = {0, -1, 0};
+struct sembuf sem_signal = {0, 1, 0};
+
+static const char initial_data[] = "Hello, world 0!";
+static const char updated_data[] = "Hello, World 0!";
+
+enum backend_type {
+ ANON,
+ SHM,
+ NORM_FILE,
+};
+
+#define PREFIX "kst_rmap"
+#define MAX_FILENAME_LEN 256
+const char *suffixes[] = {
+ "",
+ "_shm",
+ "_norm",
+};
+
+struct global_data;
+typedef int (*work_fn)(struct global_data *data);
+typedef int (*check_fn)(struct global_data *data);
+typedef void (*prepare_fn)(struct global_data *data);
+
+struct global_data {
+ int worker_level;
+
+ int semid;
+ int pipefd[2];
+
+ unsigned int mapsize;
+ unsigned int rand_seed;
+ char *region;
+
+ prepare_fn do_prepare;
+ work_fn do_work;
+ check_fn do_check;
+
+ enum backend_type backend;
+ char filename[MAX_FILENAME_LEN];
+
+ unsigned long *expected_pfn;
+};
+
+/*
+ * Create a process tree with TOTAL_LEVEL height and at most MAX_CHILDREN
+ * children for each.
+ *
+ * It will randomly select one process as 'worker' process which will
+ * 'do_work' until all processes are created. And all other processes will
+ * wait until 'worker' finish its work.
+ */
+int propagate_children(struct global_data *data)
+{
+ pid_t pid;
+ unsigned int num_child;
+ int status;
+ int ret = 0;
+ int curr_child, worker_child;
+ int curr_level = 1;
+ bool is_worker = true;
+
+repeat:
+ num_child = rand_r(&data->rand_seed) % MAX_CHILDREN + 1;
+ worker_child = is_worker ? rand_r(&data->rand_seed) % num_child : -1;
+
+ for (curr_child = 0; curr_child < num_child; curr_child++) {
+ pid = fork();
+
+ if (pid < 0) {
+ perror("Error: fork\n");
+ } else if (pid == 0) {
+ curr_level++;
+
+ if (curr_child != worker_child)
+ is_worker = false;
+
+ if (curr_level == TOTAL_LEVEL)
+ break;
+
+ data->rand_seed += curr_child;
+ goto repeat;
+ }
+ }
+
+ if (data->do_prepare)
+ data->do_prepare(data);
+
+ close(data->pipefd[1]);
+
+ if (is_worker && curr_level == data->worker_level) {
+ /* This is the worker process, first wait last process created */
+ char buf;
+
+ while (read(data->pipefd[0], &buf, 1) > 0)
+ ;
+
+ if (data->do_work)
+ ret = data->do_work(data);
+
+ /* Kick others */
+ semctl(data->semid, 0, IPC_RMID);
+ } else {
+ /* Wait worker finish */
+ semop(data->semid, &sem_wait, 1);
+ if (data->do_check)
+ ret = data->do_check(data);
+ }
+
+ /* Wait all child to quit */
+ while (wait(&status) > 0) {
+ if (WIFEXITED(status))
+ ret |= WEXITSTATUS(status);
+ }
+
+ return ret;
+}
+
+FIXTURE(migrate)
+{
+ struct global_data data;
+};
+
+FIXTURE_SETUP(migrate)
+{
+ struct global_data *data = &self->data;
+
+ ASSERT_EQ(numa_available(), 0);
+ if (numa_bitmask_weight(numa_all_nodes_ptr) <= 1)
+ SKIP(return, "Not enough NUMA nodes available");
+
+ data->mapsize = getpagesize();
+
+ /* Prepare semaphore */
+ data->semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
+ ASSERT_NE(data->semid, -1);
+ ASSERT_NE(semctl(data->semid, 0, SETVAL, 0), -1);
+
+ /* Prepare pipe */
+ ASSERT_NE(pipe(data->pipefd), -1);
+
+ data->rand_seed = time(NULL);
+ srand(data->rand_seed);
+
+ data->worker_level = rand() % TOTAL_LEVEL + 1;
+
+ data->do_prepare = NULL;
+ data->do_work = NULL;
+ data->do_check = NULL;
+
+ data->backend = ANON;
+};
+
+FIXTURE_TEARDOWN(migrate)
+{
+ struct global_data *data = &self->data;
+
+ if (data->region != MAP_FAILED)
+ munmap(data->region, data->mapsize);
+ data->region = MAP_FAILED;
+ if (data->expected_pfn != MAP_FAILED)
+ munmap(data->expected_pfn, sizeof(unsigned long));
+ data->expected_pfn = MAP_FAILED;
+ semctl(data->semid, 0, IPC_RMID);
+ data->semid = -1;
+
+ close(data->pipefd[0]);
+
+ data->do_work = NULL;
+ data->do_check = NULL;
+
+ switch (data->backend) {
+ case ANON:
+ break;
+ case SHM:
+ shm_unlink(data->filename);
+ break;
+ case NORM_FILE:
+ unlink(data->filename);
+ break;
+ }
+}
+
+int try_to_move_page(char *region)
+{
+ int ret;
+ int node;
+ int status = 0;
+
+ ksft_print_msg("worker %d move_pages of content: %.15s\n", getpid(), region);
+
+ ret = move_pages(0, 1, (void **)®ion, NULL, &status, MPOL_MF_MOVE_ALL);
+ if (ret != 0)
+ return FAIL_ON_WORK;
+
+ /* Pick up a different target node */
+ for (node = 0; node <= numa_max_node(); node++) {
+ if (numa_bitmask_isbitset(numa_all_nodes_ptr, node) && node != status)
+ break;
+ }
+
+ if (node > numa_max_node()) {
+ ksft_print_msg("Couldn't find available numa node for testing\n");
+ return FAIL_ON_WORK;
+ }
+
+ ret = move_pages(0, 1, (void **)®ion, &node, &status, MPOL_MF_MOVE_ALL);
+ if (ret != 0)
+ return FAIL_ON_WORK;
+
+ return 0;
+}
+
+int move_and_update(struct global_data *data)
+{
+ int ret;
+
+ ret = try_to_move_page(data->region);
+ if (ret != 0)
+ return ret;
+
+ /* Change the content */
+ strcpy(data->region, updated_data);
+
+ return ret;
+}
+
+int data_updated(struct global_data *data)
+{
+ if (data->region == MAP_FAILED)
+ return 0;
+
+ if (strncmp((char *)data->region, updated_data, strlen(updated_data)))
+ return FAIL_ON_CHECK;
+ return 0;
+}
+
+TEST_F(migrate, anon)
+{
+ pid_t root_pid;
+ int ret;
+ struct global_data *data = &self->data;
+
+ /* Map a shared area and fault in */
+ data->region = mmap(0, data->mapsize, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(data->region, MAP_FAILED);
+ strcpy(data->region, initial_data);
+
+ data->do_work = move_and_update;
+ data->do_check = data_updated;
+
+ root_pid = getpid();
+
+ ret = propagate_children(data);
+
+ if (getpid() == root_pid) {
+ if (ret & FAIL_ON_WORK)
+ SKIP(return, "Failed on moving page");
+
+ ASSERT_EQ(ret, 0);
+ } else {
+ exit(ret);
+ }
+}
+
+TEST_F(migrate, shm)
+{
+ pid_t root_pid;
+ int ret;
+ int shm_fd;
+ struct global_data *data = &self->data;
+
+ snprintf(data->filename, MAX_FILENAME_LEN, "%s%s", PREFIX, suffixes[SHM]);
+ shm_fd = shm_open(data->filename, O_CREAT | O_RDWR, 0666);
+ ASSERT_NE(shm_fd, -1);
+ ftruncate(shm_fd, data->mapsize);
+ data->backend = SHM;
+
+ /* Map a shared area and fault in */
+ data->region = mmap(0, data->mapsize, PROT_READ | PROT_WRITE,
+ MAP_SHARED, shm_fd, 0);
+ ASSERT_NE(data->region, MAP_FAILED);
+ strcpy(data->region, initial_data);
+ close(shm_fd);
+
+ data->do_work = move_and_update;
+ data->do_check = data_updated;
+
+ root_pid = getpid();
+
+ ret = propagate_children(data);
+
+ if (getpid() == root_pid) {
+ if (ret & FAIL_ON_WORK)
+ SKIP(return, "Failed on moving page");
+
+ ASSERT_EQ(ret, 0);
+ } else {
+ exit(ret);
+ }
+}
+
+TEST_F(migrate, file)
+{
+ pid_t root_pid;
+ int ret;
+ int fd;
+ struct global_data *data = &self->data;
+
+ snprintf(data->filename, MAX_FILENAME_LEN, "%s%s", PREFIX, suffixes[NORM_FILE]);
+ fd = open(data->filename, O_CREAT | O_RDWR | O_EXCL, 0666);
+ ASSERT_NE(fd, -1);
+ ftruncate(fd, data->mapsize);
+ data->backend = NORM_FILE;
+
+ /* Map a shared area and fault in */
+ data->region = mmap(0, data->mapsize, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ ASSERT_NE(data->region, MAP_FAILED);
+ strcpy(data->region, initial_data);
+ close(fd);
+
+ data->do_work = move_and_update;
+ data->do_check = data_updated;
+
+ root_pid = getpid();
+
+ ret = propagate_children(data);
+
+ if (getpid() == root_pid) {
+ if (ret & FAIL_ON_WORK)
+ SKIP(return, "Failed on moving page");
+
+ ASSERT_EQ(ret, 0);
+ } else {
+ exit(ret);
+ }
+}
+
+void prepare_local_region(struct global_data *data)
+{
+ /* Allocate range and set the same data */
+ data->region = mmap(NULL, data->mapsize, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANON, -1, 0);
+ if (data->region == MAP_FAILED)
+ return;
+
+ memset(data->region, 0xcf, data->mapsize);
+}
+
+int merge_and_migrate(struct global_data *data)
+{
+ long start_scans, end_scans;
+ int ksm_fd, ksm_full_scans_fd, pagemap_fd;
+ int ret = 0;
+
+ if (data->region == MAP_FAILED)
+ return FAIL_ON_WORK;
+
+ ksm_fd = open("/sys/kernel/mm/ksm/run", O_RDWR);
+ ksm_full_scans_fd = open("/sys/kernel/mm/ksm/full_scans", O_RDONLY);
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+
+ if (ksm_fd == -1 || ksm_full_scans_fd == -1 || pagemap_fd == -1)
+ return FAIL_ON_WORK;
+
+ /* Wait for two full scans such that any possible merging happened. */
+ start_scans = ksm_get_full_scans(ksm_full_scans_fd);
+ if (start_scans < 0)
+ return FAIL_ON_WORK;
+ if (ksm_start_and_merge(ksm_fd) != 1)
+ return FAIL_ON_WORK;
+ do {
+ end_scans = ksm_get_full_scans(ksm_full_scans_fd);
+ if (end_scans < 0)
+ return FAIL_ON_WORK;
+ } while (end_scans < start_scans + 3);
+
+ ret = try_to_move_page(data->region);
+
+ *data->expected_pfn = pagemap_get_pfn(pagemap_fd, data->region);
+
+ return ret;
+}
+
+int has_same_pfn(struct global_data *data)
+{
+ unsigned long pfn;
+ int pagemap_fd;
+
+ if (data->region == MAP_FAILED)
+ return 0;
+
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+ if (pagemap_fd == -1)
+ return FAIL_ON_CHECK;
+
+ pfn = pagemap_get_pfn(pagemap_fd, data->region);
+ if (pfn != *data->expected_pfn)
+ return FAIL_ON_CHECK;
+
+ return 0;
+}
+
+TEST_F(migrate, ksm)
+{
+ pid_t root_pid;
+ int ret;
+ struct global_data *data = &self->data;
+
+ ASSERT_EQ(prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0), 0);
+ data->do_prepare = prepare_local_region;
+ data->do_work = merge_and_migrate;
+ data->do_check = has_same_pfn;
+
+ data->expected_pfn = mmap(0, sizeof(unsigned long),
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(data->expected_pfn, MAP_FAILED);
+
+ root_pid = getpid();
+
+ ret = propagate_children(data);
+
+ if (getpid() == root_pid) {
+ if (ret & FAIL_ON_WORK)
+ SKIP(return, "Failed in worker");
+
+ ASSERT_EQ(ret, 0);
+ } else {
+ exit(ret);
+ }
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index dddd1dd8af14..652386999756 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -83,6 +83,8 @@ separated by spaces:
test handling of page fragment allocation and freeing
- vma_merge
test VMA merge cases behave as expected
+- rmap
+ test rmap behave as expected
example: ./run_vmtests.sh -t "hmm mmap ksm"
EOF
@@ -521,6 +523,8 @@ CATEGORY="page_frag" run_test ./test_page_frag.sh aligned
CATEGORY="page_frag" run_test ./test_page_frag.sh nonaligned
+CATEGORY="rmap" run_test ./rmap
+
echo "SUMMARY: PASS=${count_pass} SKIP=${count_skip} FAIL=${count_fail}" | tap_prefix
echo "1..${count_total}" | tap_output
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [RFC Patch 2/2] selftests/mm: assert rmap behave as expected
2025-06-04 8:21 ` [RFC Patch 2/2] selftests/mm: assert rmap behave as expected Wei Yang
@ 2025-06-04 8:34 ` Wei Yang
2025-07-11 15:39 ` David Hildenbrand
0 siblings, 1 reply; 9+ messages in thread
From: Wei Yang @ 2025-06-04 8:34 UTC (permalink / raw)
To: Wei Yang
Cc: akpm, david, lorenzo.stoakes, riel, Liam.Howlett, vbabka,
harry.yoo, linux-mm, linux-kselftest
On Wed, Jun 04, 2025 at 08:21:45AM +0000, Wei Yang wrote:
[...]
>+int try_to_move_page(char *region)
>+{
>+ int ret;
>+ int node;
>+ int status = 0;
>+
>+ ksft_print_msg("worker %d move_pages of content: %.15s\n", getpid(), region);
One thing confused me here.
If I don't access region here, the following move_pages() would report
-ENOENT occationally. The reason is do_pages_stat_array() ->
folio_walk_start() returns NULL.
Not sure which part I missed.
>+
>+ ret = move_pages(0, 1, (void **)®ion, NULL, &status, MPOL_MF_MOVE_ALL);
>+ if (ret != 0)
>+ return FAIL_ON_WORK;
>+
>+ /* Pick up a different target node */
>+ for (node = 0; node <= numa_max_node(); node++) {
>+ if (numa_bitmask_isbitset(numa_all_nodes_ptr, node) && node != status)
>+ break;
>+ }
>+
>+ if (node > numa_max_node()) {
>+ ksft_print_msg("Couldn't find available numa node for testing\n");
>+ return FAIL_ON_WORK;
>+ }
>+
>+ ret = move_pages(0, 1, (void **)®ion, &node, &status, MPOL_MF_MOVE_ALL);
>+ if (ret != 0)
>+ return FAIL_ON_WORK;
>+
>+ return 0;
>+}
>+
--
Wei Yang
Help you, Help me
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC Patch 1/2] selftests/mm: put general ksm operation into vm_util
2025-06-04 8:21 ` [RFC Patch 1/2] selftests/mm: put general ksm operation into vm_util Wei Yang
@ 2025-07-11 15:37 ` David Hildenbrand
2025-07-14 2:45 ` Wei Yang
0 siblings, 1 reply; 9+ messages in thread
From: David Hildenbrand @ 2025-07-11 15:37 UTC (permalink / raw)
To: Wei Yang, akpm, lorenzo.stoakes, riel, Liam.Howlett, vbabka,
harry.yoo
Cc: linux-mm, linux-kselftest
> diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
> index 1357e2d6a7b6..115422e9eb68 100644
> --- a/tools/testing/selftests/mm/vm_util.c
> +++ b/tools/testing/selftests/mm/vm_util.c
> @@ -486,3 +486,74 @@ int close_procmap(struct procmap_fd *procmap)
> {
> return close(procmap->fd);
> }
> +
I think we should just let all these functions open/close the fds. So
there will not be a need to pass in the fds.
> +int ksm_use_zero_pages(int ksm_use_zero_pages_fd)
> +{
> + return write(ksm_use_zero_pages_fd, "1", 1);
> +}
> +
> +int ksm_start_and_merge(int ksm_fd)
> +{
> + return write(ksm_fd, "1", 1);
> +}> +
> +int ksm_stop_and_unmerge(int ksm_fd)
> +{
> + return write(ksm_fd, "2", 1);
> +}
Can we make all these functions return "0" on success? This, way, the
"write" will be an internal implementation detail.
E.g.,
int ksm_stop_and_unmerge(void)
{
int ksm_fd = ...
ssize_t ret;
...
ret = write(ksm_fd, "2", 1);
close(ksm_fd);
return ret == 1 ? 0 : ret;
}
> +
> +long ksm_get_full_scans(int ksm_full_scans_fd)
> +{
> + char buf[10];
> + ssize_t ret;
> +
> + ret = pread(ksm_full_scans_fd, buf, sizeof(buf) - 1, 0);
> + if (ret <= 0)
> + return -errno;
> + buf[ret] = 0;
> +
> + return strtol(buf, NULL, 10);
> +}
> +
> +long ksm_get_self_merging_pages(int proc_self_ksm_merging_pages_fd)
> +{
> + char buf[10];
> + ssize_t ret;
> +
> + if (proc_self_ksm_merging_pages_fd < 0)
> + return proc_self_ksm_merging_pages_fd;
> +
> + ret = pread(proc_self_ksm_merging_pages_fd, buf, sizeof(buf) - 1, 0);
> + if (ret <= 0)
> + return -errno;
> + buf[ret] = 0;
> +
> + return strtol(buf, NULL, 10);
> +}
> +
> +long ksm_get_self_zero_pages(int proc_self_ksm_stat_fd)
> +{
> + char buf[200];
> + char *substr_ksm_zero;
> + size_t value_pos;
> + ssize_t read_size;
> + unsigned long my_ksm_zero_pages;
> +
> + if (!proc_self_ksm_stat_fd)
> + return 0;
> +
> + read_size = pread(proc_self_ksm_stat_fd, buf, sizeof(buf) - 1, 0);
> + if (read_size < 0)
> + return -errno;
> +
> + buf[read_size] = 0;
> +
> + substr_ksm_zero = strstr(buf, "ksm_zero_pages");
> + if (!substr_ksm_zero)
> + return 0;
> +
> + value_pos = strcspn(substr_ksm_zero, "0123456789");
> + my_ksm_zero_pages = strtol(substr_ksm_zero + value_pos, NULL, 10);
> +
> + return my_ksm_zero_pages;
> +}
> diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
> index 9211ba640d9c..99c1b1aa1813 100644
> --- a/tools/testing/selftests/mm/vm_util.h
> +++ b/tools/testing/selftests/mm/vm_util.h
> @@ -95,6 +95,13 @@ static inline int open_self_procmap(struct procmap_fd *procmap_out)
> return open_procmap(pid, procmap_out);
> }
>
> +int ksm_use_zero_pages(int ksm_use_zero_pages_fd);
> +int ksm_start_and_merge(int ksm_fd);
> +int ksm_stop_and_unmerge(int ksm_fd);
> +long ksm_get_full_scans(int ksm_full_scans_fd);
> +long ksm_get_self_merging_pages(int proc_self_ksm_merging_pages_fd);
> +long ksm_get_self_zero_pages(int proc_self_ksm_stat_fd);
With the fd parameters removed, that interface will look quite neat I think.
--
Cheers,
David / dhildenb
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC Patch 2/2] selftests/mm: assert rmap behave as expected
2025-06-04 8:34 ` Wei Yang
@ 2025-07-11 15:39 ` David Hildenbrand
2025-07-14 14:42 ` Wei Yang
0 siblings, 1 reply; 9+ messages in thread
From: David Hildenbrand @ 2025-07-11 15:39 UTC (permalink / raw)
To: Wei Yang
Cc: akpm, lorenzo.stoakes, riel, Liam.Howlett, vbabka, harry.yoo,
linux-mm, linux-kselftest
On 04.06.25 10:34, Wei Yang wrote:
> On Wed, Jun 04, 2025 at 08:21:45AM +0000, Wei Yang wrote:
> [...]
>> +int try_to_move_page(char *region)
>> +{
>> + int ret;
>> + int node;
>> + int status = 0;
>> +
>> + ksft_print_msg("worker %d move_pages of content: %.15s\n", getpid(), region);
>
> One thing confused me here.
>
> If I don't access region here, the following move_pages() would report
> -ENOENT occationally. The reason is do_pages_stat_array() ->
> folio_walk_start() returns NULL.
Right, the pages were not faulted in. The man page mentions that as
"-ENOENT: The page is not present."
--
Cheers,
David / dhildenb
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC Patch 1/2] selftests/mm: put general ksm operation into vm_util
2025-07-11 15:37 ` David Hildenbrand
@ 2025-07-14 2:45 ` Wei Yang
0 siblings, 0 replies; 9+ messages in thread
From: Wei Yang @ 2025-07-14 2:45 UTC (permalink / raw)
To: David Hildenbrand
Cc: Wei Yang, akpm, lorenzo.stoakes, riel, Liam.Howlett, vbabka,
harry.yoo, linux-mm, linux-kselftest
On Fri, Jul 11, 2025 at 05:37:54PM +0200, David Hildenbrand wrote:
>> diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
>> index 1357e2d6a7b6..115422e9eb68 100644
>> --- a/tools/testing/selftests/mm/vm_util.c
>> +++ b/tools/testing/selftests/mm/vm_util.c
>> @@ -486,3 +486,74 @@ int close_procmap(struct procmap_fd *procmap)
>> {
>> return close(procmap->fd);
>> }
>> +
>
>I think we should just let all these functions open/close the fds. So there
>will not be a need to pass in the fds.
>
Sure. Will update it.
>> +int ksm_use_zero_pages(int ksm_use_zero_pages_fd)
>> +{
>> + return write(ksm_use_zero_pages_fd, "1", 1);
>> +}
>> +
>> +int ksm_start_and_merge(int ksm_fd)
>> +{
>> + return write(ksm_fd, "1", 1);
>> +}> +
>> +int ksm_stop_and_unmerge(int ksm_fd)
>> +{
>> + return write(ksm_fd, "2", 1);
>> +}
>
>Can we make all these functions return "0" on success? This, way, the "write"
>will be an internal implementation detail.
>
>E.g.,
>
>int ksm_stop_and_unmerge(void)
>{
> int ksm_fd = ...
> ssize_t ret;
>
> ...
>
> ret = write(ksm_fd, "2", 1);
> close(ksm_fd);
> return ret == 1 ? 0 : ret;
>}
>
Sure will do it.
--
Wei Yang
Help you, Help me
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC Patch 2/2] selftests/mm: assert rmap behave as expected
2025-07-11 15:39 ` David Hildenbrand
@ 2025-07-14 14:42 ` Wei Yang
2025-07-14 14:45 ` David Hildenbrand
0 siblings, 1 reply; 9+ messages in thread
From: Wei Yang @ 2025-07-14 14:42 UTC (permalink / raw)
To: David Hildenbrand
Cc: Wei Yang, akpm, lorenzo.stoakes, riel, Liam.Howlett, vbabka,
harry.yoo, linux-mm, linux-kselftest
On Fri, Jul 11, 2025 at 05:39:39PM +0200, David Hildenbrand wrote:
>On 04.06.25 10:34, Wei Yang wrote:
>> On Wed, Jun 04, 2025 at 08:21:45AM +0000, Wei Yang wrote:
>> [...]
>> > +int try_to_move_page(char *region)
>> > +{
>> > + int ret;
>> > + int node;
>> > + int status = 0;
>> > +
>> > + ksft_print_msg("worker %d move_pages of content: %.15s\n", getpid(), region);
>>
>> One thing confused me here.
>>
>> If I don't access region here, the following move_pages() would report
>> -ENOENT occationally. The reason is do_pages_stat_array() ->
>> folio_walk_start() returns NULL.
>
>Right, the pages were not faulted in. The man page mentions that as
>
>"-ENOENT: The page is not present."
>
Thanks I see the man page, but from the code point of view, I don't follow it.
The move_pages() return -ENOENT in a child process, and the child duplicate it
memory space during fork().
dup_mmap()
for_each_vma()
copy_page_range()
...
copy_pte_range() -> copy_present_ptes()
__copy_present_ptes()
set_ptes()
Even we map the range by MAP_SHARED, we don't need to wrprotect it.
But we still set_ptes() in the child process page table.
So it looks the child has prepared the page table and not need to fault in to
setup it.
Do I miss something?
>--
>Cheers,
>
>David / dhildenb
--
Wei Yang
Help you, Help me
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC Patch 2/2] selftests/mm: assert rmap behave as expected
2025-07-14 14:42 ` Wei Yang
@ 2025-07-14 14:45 ` David Hildenbrand
0 siblings, 0 replies; 9+ messages in thread
From: David Hildenbrand @ 2025-07-14 14:45 UTC (permalink / raw)
To: Wei Yang
Cc: akpm, lorenzo.stoakes, riel, Liam.Howlett, vbabka, harry.yoo,
linux-mm, linux-kselftest
On 14.07.25 16:42, Wei Yang wrote:
> On Fri, Jul 11, 2025 at 05:39:39PM +0200, David Hildenbrand wrote:
>> On 04.06.25 10:34, Wei Yang wrote:
>>> On Wed, Jun 04, 2025 at 08:21:45AM +0000, Wei Yang wrote:
>>> [...]
>>>> +int try_to_move_page(char *region)
>>>> +{
>>>> + int ret;
>>>> + int node;
>>>> + int status = 0;
>>>> +
>>>> + ksft_print_msg("worker %d move_pages of content: %.15s\n", getpid(), region);
>>>
>>> One thing confused me here.
>>>
>>> If I don't access region here, the following move_pages() would report
>>> -ENOENT occationally. The reason is do_pages_stat_array() ->
>>> folio_walk_start() returns NULL.
>>
>> Right, the pages were not faulted in. The man page mentions that as
>>
>> "-ENOENT: The page is not present."
>>
>
> Thanks I see the man page, but from the code point of view, I don't follow it.
>
> The move_pages() return -ENOENT in a child process, and the child duplicate it
> memory space during fork().
>
> dup_mmap()
> for_each_vma()
> copy_page_range()
> ...
> copy_pte_range() -> copy_present_ptes()
> __copy_present_ptes()
> set_ptes()
>
> Even we map the range by MAP_SHARED, we don't need to wrprotect it.
> But we still set_ptes() in the child process page table.
>
>
> So it looks the child has prepared the page table and not need to fault in to
> setup it.
>
> Do I miss something?
See copy_page_range() -> vma_needs_copy(), where we essentially
optimize-out copying of page tables for most MAP_SHARED mappings (or
MAP_PRIVATE mappings when we never placed private pages).
--
Cheers,
David / dhildenb
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-07-14 14:46 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-04 8:21 [RFC Patch 0/2] selftests/mm: assert rmap behave as expected Wei Yang
2025-06-04 8:21 ` [RFC Patch 1/2] selftests/mm: put general ksm operation into vm_util Wei Yang
2025-07-11 15:37 ` David Hildenbrand
2025-07-14 2:45 ` Wei Yang
2025-06-04 8:21 ` [RFC Patch 2/2] selftests/mm: assert rmap behave as expected Wei Yang
2025-06-04 8:34 ` Wei Yang
2025-07-11 15:39 ` David Hildenbrand
2025-07-14 14:42 ` Wei Yang
2025-07-14 14:45 ` David Hildenbrand
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).