* [PATCH 1/3] tools/testing/selftests: add mremap() shrink test for multiple VMAs
2025-07-21 17:33 [PATCH 0/3] tools/testing: expand mremap testing Lorenzo Stoakes
@ 2025-07-21 17:33 ` Lorenzo Stoakes
2025-07-21 17:33 ` [PATCH 2/3] tools/testing/selftests: test MREMAP_DONTUNMAP on multiple VMA move Lorenzo Stoakes
2025-07-21 17:33 ` [PATCH 3/3] tools/testing/selftests: explicitly test split multi VMA mremap move Lorenzo Stoakes
2 siblings, 0 replies; 4+ messages in thread
From: Lorenzo Stoakes @ 2025-07-21 17:33 UTC (permalink / raw)
To: Andrew Morton
Cc: Liam R . Howlett, Vlastimil Babka, Jann Horn, Pedro Falcato,
linux-mm, linux-kernel
There is a an apparently little-known feature of mremap() whereby, in stark
contrast to other modes (other than the recently introduced capacity to
move multiple VMAs), the input source range span multiple VMAs with gaps
between.
This is, when shrinking a VMA, whether moving it or not, and the shrink
would reduce the range to a single VMA - this is permitted, as the shrink
is actioned by an unmap.
This patch adds tests to assert that this behaves as expected.
Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
---
tools/testing/selftests/mm/mremap_test.c | 83 +++++++++++++++++++++++-
1 file changed, 82 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/mm/mremap_test.c b/tools/testing/selftests/mm/mremap_test.c
index 0a49be11e614..141a9032414e 100644
--- a/tools/testing/selftests/mm/mremap_test.c
+++ b/tools/testing/selftests/mm/mremap_test.c
@@ -523,6 +523,85 @@ static void mremap_move_multiple_vmas(unsigned int pattern_seed,
ksft_test_result_fail("%s\n", test_name);
}
+static void mremap_shrink_multiple_vmas(unsigned long page_size,
+ bool inplace)
+{
+ char *test_name = "mremap shrink multiple vmas";
+ const size_t size = 10 * page_size;
+ bool success = true;
+ char *ptr, *tgt_ptr;
+ void *res;
+ int i;
+
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (ptr == MAP_FAILED) {
+ perror("mmap");
+ success = false;
+ goto out;
+ }
+
+ tgt_ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (tgt_ptr == MAP_FAILED) {
+ perror("mmap");
+ success = false;
+ goto out;
+ }
+ if (munmap(tgt_ptr, size)) {
+ perror("munmap");
+ success = false;
+ goto out_unmap;
+ }
+
+ /*
+ * Unmap so we end up with:
+ *
+ * 0 2 4 6 8 10 offset in buffer
+ * |*| |*| |*| |*| |*| |*|
+ * |*| |*| |*| |*| |*| |*|
+ */
+ for (i = 1; i < 10; i += 2) {
+ if (munmap(&ptr[i * page_size], page_size)) {
+ perror("munmap");
+ success = false;
+ goto out_unmap;
+ }
+ }
+
+ /*
+ * Shrink in-place across multiple VMAs and gaps so we end up with:
+ *
+ * 0
+ * |*|
+ * |*|
+ */
+ if (inplace)
+ res = mremap(ptr, size, page_size, 0);
+ else
+ res = mremap(ptr, size, page_size, MREMAP_MAYMOVE | MREMAP_FIXED,
+ tgt_ptr);
+
+ if (res == MAP_FAILED) {
+ perror("mremap");
+ success = false;
+ goto out_unmap;
+ }
+
+out_unmap:
+ if (munmap(tgt_ptr, size))
+ perror("munmap tgt");
+ if (munmap(ptr, size))
+ perror("munmap src");
+out:
+ if (success)
+ ksft_test_result_pass("%s%s\n", test_name,
+ inplace ? " [inplace]" : "");
+ else
+ ksft_test_result_fail("%s%s\n", test_name,
+ inplace ? " [inplace]" : "");
+}
+
/* Returns the time taken for the remap on success else returns -1. */
static long long remap_region(struct config c, unsigned int threshold_mb,
char *rand_addr)
@@ -864,7 +943,7 @@ int main(int argc, char **argv)
char *rand_addr;
size_t rand_size;
int num_expand_tests = 2;
- int num_misc_tests = 3;
+ int num_misc_tests = 5;
struct test test_cases[MAX_TEST] = {};
struct test perf_test_cases[MAX_PERF_TEST];
int page_size;
@@ -992,6 +1071,8 @@ int main(int argc, char **argv)
mremap_move_within_range(pattern_seed, rand_addr);
mremap_move_1mb_from_start(pattern_seed, rand_addr);
mremap_move_multiple_vmas(pattern_seed, page_size);
+ mremap_shrink_multiple_vmas(page_size, /* inplace= */true);
+ mremap_shrink_multiple_vmas(page_size, /* inplace= */false);
if (run_perf_tests) {
ksft_print_msg("\n%s\n",
--
2.50.1
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 2/3] tools/testing/selftests: test MREMAP_DONTUNMAP on multiple VMA move
2025-07-21 17:33 [PATCH 0/3] tools/testing: expand mremap testing Lorenzo Stoakes
2025-07-21 17:33 ` [PATCH 1/3] tools/testing/selftests: add mremap() shrink test for multiple VMAs Lorenzo Stoakes
@ 2025-07-21 17:33 ` Lorenzo Stoakes
2025-07-21 17:33 ` [PATCH 3/3] tools/testing/selftests: explicitly test split multi VMA mremap move Lorenzo Stoakes
2 siblings, 0 replies; 4+ messages in thread
From: Lorenzo Stoakes @ 2025-07-21 17:33 UTC (permalink / raw)
To: Andrew Morton
Cc: Liam R . Howlett, Vlastimil Babka, Jann Horn, Pedro Falcato,
linux-mm, linux-kernel
We support MREMAP_MAYMOVE | MREMAP_FIXED | MREMAP_DONTUNMAP for moving
multiple VMAs via mremap(), so assert that the tests pass with both
MREMAP_DONTUNMAP set and not set.
Additionally, add success = false settings when mremap() fails. This is
something that cannot realistically happen, so in no way impacted test
outcome, but it is incorrect to indicate a test pass when something has
failed.
Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
---
tools/testing/selftests/mm/mremap_test.c | 29 ++++++++++++++++--------
1 file changed, 19 insertions(+), 10 deletions(-)
diff --git a/tools/testing/selftests/mm/mremap_test.c b/tools/testing/selftests/mm/mremap_test.c
index 141a9032414e..d3b4772cd8c9 100644
--- a/tools/testing/selftests/mm/mremap_test.c
+++ b/tools/testing/selftests/mm/mremap_test.c
@@ -406,14 +406,19 @@ static bool is_multiple_vma_range_ok(unsigned int pattern_seed,
}
static void mremap_move_multiple_vmas(unsigned int pattern_seed,
- unsigned long page_size)
+ unsigned long page_size,
+ bool dont_unmap)
{
+ int mremap_flags = MREMAP_FIXED | MREMAP_MAYMOVE;
char *test_name = "mremap move multiple vmas";
const size_t size = 11 * page_size;
bool success = true;
char *ptr, *tgt_ptr;
int i;
+ if (dont_unmap)
+ mremap_flags |= MREMAP_DONTUNMAP;
+
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON, -1, 0);
if (ptr == MAP_FAILED) {
@@ -467,8 +472,7 @@ static void mremap_move_multiple_vmas(unsigned int pattern_seed,
}
/* First, just move the whole thing. */
- if (mremap(ptr, size, size,
- MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr) == MAP_FAILED) {
+ if (mremap(ptr, size, size, mremap_flags, tgt_ptr) == MAP_FAILED) {
perror("mremap");
success = false;
goto out_unmap;
@@ -480,9 +484,10 @@ static void mremap_move_multiple_vmas(unsigned int pattern_seed,
}
/* Move next to itself. */
- if (mremap(tgt_ptr, size, size,
- MREMAP_MAYMOVE | MREMAP_FIXED, &tgt_ptr[size]) == MAP_FAILED) {
+ if (mremap(tgt_ptr, size, size, mremap_flags,
+ &tgt_ptr[size]) == MAP_FAILED) {
perror("mremap");
+ success = false;
goto out_unmap;
}
/* Check that the move is ok. */
@@ -500,8 +505,9 @@ static void mremap_move_multiple_vmas(unsigned int pattern_seed,
}
/* Move and overwrite. */
if (mremap(&tgt_ptr[size], size, size,
- MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr) == MAP_FAILED) {
+ mremap_flags, tgt_ptr) == MAP_FAILED) {
perror("mremap");
+ success = false;
goto out_unmap;
}
/* Check that the move is ok. */
@@ -518,9 +524,11 @@ static void mremap_move_multiple_vmas(unsigned int pattern_seed,
out:
if (success)
- ksft_test_result_pass("%s\n", test_name);
+ ksft_test_result_pass("%s%s\n", test_name,
+ dont_unmap ? " [dontunnmap]" : "");
else
- ksft_test_result_fail("%s\n", test_name);
+ ksft_test_result_fail("%s%s\n", test_name,
+ dont_unmap ? " [dontunnmap]" : "");
}
static void mremap_shrink_multiple_vmas(unsigned long page_size,
@@ -943,7 +951,7 @@ int main(int argc, char **argv)
char *rand_addr;
size_t rand_size;
int num_expand_tests = 2;
- int num_misc_tests = 5;
+ int num_misc_tests = 6;
struct test test_cases[MAX_TEST] = {};
struct test perf_test_cases[MAX_PERF_TEST];
int page_size;
@@ -1070,9 +1078,10 @@ int main(int argc, char **argv)
mremap_move_within_range(pattern_seed, rand_addr);
mremap_move_1mb_from_start(pattern_seed, rand_addr);
- mremap_move_multiple_vmas(pattern_seed, page_size);
mremap_shrink_multiple_vmas(page_size, /* inplace= */true);
mremap_shrink_multiple_vmas(page_size, /* inplace= */false);
+ mremap_move_multiple_vmas(pattern_seed, page_size, /* dontunmap= */ false);
+ mremap_move_multiple_vmas(pattern_seed, page_size, /* dontunmap= */ true);
if (run_perf_tests) {
ksft_print_msg("\n%s\n",
--
2.50.1
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] tools/testing/selftests: explicitly test split multi VMA mremap move
2025-07-21 17:33 [PATCH 0/3] tools/testing: expand mremap testing Lorenzo Stoakes
2025-07-21 17:33 ` [PATCH 1/3] tools/testing/selftests: add mremap() shrink test for multiple VMAs Lorenzo Stoakes
2025-07-21 17:33 ` [PATCH 2/3] tools/testing/selftests: test MREMAP_DONTUNMAP on multiple VMA move Lorenzo Stoakes
@ 2025-07-21 17:33 ` Lorenzo Stoakes
2 siblings, 0 replies; 4+ messages in thread
From: Lorenzo Stoakes @ 2025-07-21 17:33 UTC (permalink / raw)
To: Andrew Morton
Cc: Liam R . Howlett, Vlastimil Babka, Jann Horn, Pedro Falcato,
linux-mm, linux-kernel
Check that moving a range of VMAs where we are offset into the first and
last VMAs works correctly.
This results in the VMAs being split at these points at which we are offset
into VMAs.
We explicitly test both the ordinary MREMAP_FIXED multi VMA move case and
the MREMAP_DONTUNMAP multi VMA move case.
Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
---
tools/testing/selftests/mm/mremap_test.c | 127 ++++++++++++++++++++++-
1 file changed, 126 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/mm/mremap_test.c b/tools/testing/selftests/mm/mremap_test.c
index d3b4772cd8c9..fccf9e797a0c 100644
--- a/tools/testing/selftests/mm/mremap_test.c
+++ b/tools/testing/selftests/mm/mremap_test.c
@@ -610,6 +610,129 @@ static void mremap_shrink_multiple_vmas(unsigned long page_size,
inplace ? " [inplace]" : "");
}
+static void mremap_move_multiple_vmas_split(unsigned int pattern_seed,
+ unsigned long page_size,
+ bool dont_unmap)
+{
+ char *test_name = "mremap move multiple vmas split";
+ int mremap_flags = MREMAP_FIXED | MREMAP_MAYMOVE;
+ const size_t size = 10 * page_size;
+ bool success = true;
+ char *ptr, *tgt_ptr;
+ int i;
+
+ if (dont_unmap)
+ mremap_flags |= MREMAP_DONTUNMAP;
+
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (ptr == MAP_FAILED) {
+ perror("mmap");
+ success = false;
+ goto out;
+ }
+
+ tgt_ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (tgt_ptr == MAP_FAILED) {
+ perror("mmap");
+ success = false;
+ goto out;
+ }
+ if (munmap(tgt_ptr, size)) {
+ perror("munmap");
+ success = false;
+ goto out_unmap;
+ }
+
+ /*
+ * Unmap so we end up with:
+ *
+ * 0 1 2 3 4 5 6 7 8 9 10 offset in buffer
+ * |**********| |*******|
+ * |**********| |*******|
+ * 0 1 2 3 4 5 6 7 8 9 pattern offset
+ */
+ if (munmap(&ptr[5 * page_size], page_size)) {
+ perror("munmap");
+ success = false;
+ goto out_unmap;
+ }
+
+ /* Set up random patterns. */
+ srand(pattern_seed);
+ for (i = 0; i < 10; i++) {
+ int j;
+ char *buf = &ptr[i * page_size];
+
+ if (i == 5)
+ continue;
+
+ for (j = 0; j < page_size; j++)
+ buf[j] = rand();
+ }
+
+ /*
+ * Move the below:
+ *
+ * <------------->
+ * 0 1 2 3 4 5 6 7 8 9 10 offset in buffer
+ * |**********| |*******|
+ * |**********| |*******|
+ * 0 1 2 3 4 5 6 7 8 9 pattern offset
+ *
+ * Into:
+ *
+ * 0 1 2 3 4 5 6 7 offset in buffer
+ * |*****| |*****|
+ * |*****| |*****|
+ * 2 3 4 5 6 7 pattern offset
+ */
+ if (mremap(&ptr[2 * page_size], size - 3 * page_size, size - 3 * page_size,
+ mremap_flags, tgt_ptr) == MAP_FAILED) {
+ perror("mremap");
+ success = false;
+ goto out_unmap;
+ }
+
+ /* Offset into random pattern. */
+ srand(pattern_seed);
+ for (i = 0; i < 2 * page_size; i++)
+ rand();
+
+ /* Check pattern. */
+ for (i = 0; i < 7; i++) {
+ int j;
+ char *buf = &tgt_ptr[i * page_size];
+
+ if (i == 3)
+ continue;
+
+ for (j = 0; j < page_size; j++) {
+ char chr = rand();
+
+ if (chr != buf[j]) {
+ ksft_print_msg("page %d offset %d corrupted, expected %d got %d\n",
+ i, j, chr, buf[j]);
+ goto out_unmap;
+ }
+ }
+ }
+
+out_unmap:
+ if (munmap(tgt_ptr, size))
+ perror("munmap tgt");
+ if (munmap(ptr, size))
+ perror("munmap src");
+out:
+ if (success)
+ ksft_test_result_pass("%s%s\n", test_name,
+ dont_unmap ? " [dontunnmap]" : "");
+ else
+ ksft_test_result_fail("%s%s\n", test_name,
+ dont_unmap ? " [dontunnmap]" : "");
+}
+
/* Returns the time taken for the remap on success else returns -1. */
static long long remap_region(struct config c, unsigned int threshold_mb,
char *rand_addr)
@@ -951,7 +1074,7 @@ int main(int argc, char **argv)
char *rand_addr;
size_t rand_size;
int num_expand_tests = 2;
- int num_misc_tests = 6;
+ int num_misc_tests = 8;
struct test test_cases[MAX_TEST] = {};
struct test perf_test_cases[MAX_PERF_TEST];
int page_size;
@@ -1082,6 +1205,8 @@ int main(int argc, char **argv)
mremap_shrink_multiple_vmas(page_size, /* inplace= */false);
mremap_move_multiple_vmas(pattern_seed, page_size, /* dontunmap= */ false);
mremap_move_multiple_vmas(pattern_seed, page_size, /* dontunmap= */ true);
+ mremap_move_multiple_vmas_split(pattern_seed, page_size, /* dontunmap= */ false);
+ mremap_move_multiple_vmas_split(pattern_seed, page_size, /* dontunmap= */ true);
if (run_perf_tests) {
ksft_print_msg("\n%s\n",
--
2.50.1
^ permalink raw reply [flat|nested] 4+ messages in thread