MREMAP_DONTUNMAP moves a mapping's page tables to a new address while
leaving the source mapping in place. Check that mlock accounting charges
the page only once after the remap.

Add a test to mremap_dontunmap that verifies mlock charges one page and
that MREMAP_DONTUNMAP does not increase VmLck again. Skip the test when
mlock is unavailable.

Signed-off-by: Sahaj Chaudhari <[email protected]>
---
 tools/testing/selftests/mm/mremap_dontunmap.c | 92 ++++++++++++++++++-
 1 file changed, 91 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/mm/mremap_dontunmap.c 
b/tools/testing/selftests/mm/mremap_dontunmap.c
index 96ba537facf7..e4b0e1868a6f 100644
--- a/tools/testing/selftests/mm/mremap_dontunmap.c
+++ b/tools/testing/selftests/mm/mremap_dontunmap.c
@@ -90,6 +90,26 @@ static int check_region_contains_byte(void *addr, unsigned 
long size, char byte)
        return 0;
 }
 
+static int read_vmlck(unsigned long *vmlck)
+{
+       char line[128];
+       FILE *status = fopen("/proc/self/status", "r");
+
+       if (!status)
+               return -1;
+
+       while (fgets(line, sizeof(line), status)) {
+               if (sscanf(line, "VmLck: %lu kB", vmlck) == 1) {
+                       fclose(status);
+                       return 0;
+               }
+       }
+
+       fclose(status);
+       errno = EINVAL;
+       return -1;
+}
+
 // this test validates that MREMAP_DONTUNMAP moves the pagetables while leaving
 // the source mapping mapped.
 static void mremap_dontunmap_simple()
@@ -125,6 +145,75 @@ static void mremap_dontunmap_simple()
        ksft_test_result_pass("%s\n", __func__);
 }
 
+static void mremap_dontunmap_mlock_accounting(void)
+{
+       unsigned long before, after_mlock, after_remap;
+       char message[128];
+       char *source, *dest = MAP_FAILED;
+       int failed = 0;
+       int err;
+
+       source = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+                     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+       if (source == MAP_FAILED)
+               ksft_exit_fail_perror("mmap");
+       if (read_vmlck(&before))
+               ksft_exit_fail_perror("read VmLck");
+
+       if (mlock(source, page_size)) {
+               err = errno;
+               if (munmap(source, page_size))
+                       ksft_exit_fail_perror("munmap");
+               if (err == EPERM || err == ENOMEM) {
+                       ksft_test_result_skip("%s: mlock unavailable\n", 
__func__);
+                       return;
+               }
+               errno = err;
+               ksft_exit_fail_perror("mlock");
+       }
+
+       if (read_vmlck(&after_mlock))
+               ksft_exit_fail_perror("read VmLck after mlock");
+       if (after_mlock != before + page_size / 1024) {
+               snprintf(message, sizeof(message),
+                        "mlock changed VmLck from %lu to %lu kB", before,
+                        after_mlock);
+               failed = 1;
+               goto out;
+       }
+
+       dest = mremap(source, page_size, page_size,
+                     MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);
+       if (dest == MAP_FAILED)
+               ksft_exit_fail_perror("mremap");
+       if (read_vmlck(&after_remap))
+               ksft_exit_fail_perror("read VmLck after mremap");
+
+       if (after_remap != after_mlock) {
+               snprintf(message, sizeof(message),
+                        "VmLck changed from %lu to %lu kB", after_mlock,
+                        after_remap);
+               failed = 1;
+       }
+
+out:
+       if (dest != MAP_FAILED) {
+               if (munlock(dest, page_size))
+                       ksft_exit_fail_perror("munlock destination");
+               if (munmap(dest, page_size))
+                       ksft_exit_fail_perror("munmap destination");
+       }
+       if (munlock(source, page_size))
+               ksft_exit_fail_perror("munlock source");
+       if (munmap(source, page_size))
+               ksft_exit_fail_perror("munmap source");
+
+       if (failed)
+               ksft_test_result_fail("%s\n", message);
+       else
+               ksft_test_result_pass("MREMAP_DONTUNMAP preserves VmLck 
accounting\n");
+}
+
 // This test validates that MREMAP_DONTUNMAP on a shared mapping works as 
expected.
 static void mremap_dontunmap_simple_shmem()
 {
@@ -348,7 +437,7 @@ int main(void)
                ksft_finished();
        }
 
-       ksft_set_plan(5);
+       ksft_set_plan(6);
 
        // Keep a page sized buffer around for when we need it.
        page_buffer =
@@ -361,6 +450,7 @@ int main(void)
        mremap_dontunmap_simple_fixed();
        mremap_dontunmap_partial_mapping();
        mremap_dontunmap_partial_mapping_overwrite();
+       mremap_dontunmap_mlock_accounting();
 
        BUG_ON(munmap(page_buffer, page_size) == -1,
               "unable to unmap page buffer");

Reply via email to