]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tools/testing/selftests: add mremap() shrink test for multiple VMAs
authorLorenzo Stoakes <lorenzo.stoakes@oracle.com>
Mon, 21 Jul 2025 17:33:25 +0000 (18:33 +0100)
committerAndrew Morton <akpm@linux-foundation.org>
Fri, 25 Jul 2025 02:12:42 +0000 (19:12 -0700)
Patch series "tools/testing: expand mremap testing".

Expand our mremap() testing to further assert that behaviour is as
expected.

There is a poorly documented mremap() feature whereby it is possible to
mremap() multiple VMAs (even with gaps) when shrinking, as long as the
resultant shrunk range spans only a single VMA.

So we start by asserting this behaviour functions correctly both with an
in-place shrink and a shrink/move.

Next, we further test the newly introduced ability to mremap() multiple
VMAs when performing a MAP_FIXED move (that is without the size being
changed), firstly by asserting that MREMAP_DONTUNMAP has no bearing on
this behaviour.

Finally, we explicitly test that such moves, when splitting source VMAs,
function correctly.

This patch (of 3):

There is 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.

Link: https://lkml.kernel.org/r/cover.1753119043.git.lorenzo.stoakes@oracle.com
Link: https://lkml.kernel.org/r/f08122893a26092a2bec6e69443e87f468ffdbed.1753119043.git.lorenzo.stoakes@oracle.com
Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Cc: Jann Horn <jannh@google.com>
Cc: Liam Howlett <liam.howlett@oracle.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
tools/testing/selftests/mm/mremap_test.c

index 0a49be11e614260524653b42f9c2923777ae2c08..141a9032414e275db8674631a8dd855101eac1c5 100644 (file)
@@ -523,6 +523,85 @@ out:
                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",