From: Nadav Amit <na...@vmware.com>

Test prefetch_page() in cases of invalid pointer, file-mmap and
anonymous memory. Partial checks are also done with mincore syscall to
ensure the output of prefetch_page() is consistent with mincore (taking
into account the different semantics of the two).

The tests are not fool-proof as they rely on the behavior of the
page-cache and page reclamation mechanism to get a major page-fault.
They should be robust in the sense of test being skipped if it failed.

There is a question though on how to know how much memory to access in
the test of anonymous memory to force the eviction of a page and trigger
a refault.

Cc: Andy Lutomirski <l...@kernel.org>
Cc: Peter Zijlstra <pet...@infradead.org>
Cc: Sean Christopherson <sea...@google.com>
Cc: Thomas Gleixner <t...@linutronix.de>
Cc: Ingo Molnar <mi...@redhat.com>
Cc: Borislav Petkov <b...@alien8.de>
Cc: Andrew Morton <a...@linux-foundation.org>
Cc: x...@kernel.org
Signed-off-by: Nadav Amit <na...@vmware.com>
---
 tools/testing/selftests/vDSO/Makefile         |   2 +
 .../selftests/vDSO/vdso_test_prefetch_page.c  | 265 ++++++++++++++++++
 2 files changed, 267 insertions(+)
 create mode 100644 tools/testing/selftests/vDSO/vdso_test_prefetch_page.c

diff --git a/tools/testing/selftests/vDSO/Makefile 
b/tools/testing/selftests/vDSO/Makefile
index d53a4d8008f9..dcd1ede8c0f7 100644
--- a/tools/testing/selftests/vDSO/Makefile
+++ b/tools/testing/selftests/vDSO/Makefile
@@ -11,6 +11,7 @@ ifeq ($(ARCH),$(filter $(ARCH),x86 x86_64))
 TEST_GEN_PROGS += $(OUTPUT)/vdso_standalone_test_x86
 endif
 TEST_GEN_PROGS += $(OUTPUT)/vdso_test_correctness
+TEST_GEN_PROGS += $(OUTPUT)/vdso_test_prefetch_page
 
 CFLAGS := -std=gnu99
 CFLAGS_vdso_standalone_test_x86 := -nostdlib -fno-asynchronous-unwind-tables 
-fno-stack-protector
@@ -33,3 +34,4 @@ $(OUTPUT)/vdso_test_correctness: vdso_test_correctness.c
                vdso_test_correctness.c \
                -o $@ \
                $(LDFLAGS_vdso_test_correctness)
+$(OUTPUT)/vdso_test_prefetch_page: vdso_test_prefetch_page.c parse_vdso.c
diff --git a/tools/testing/selftests/vDSO/vdso_test_prefetch_page.c 
b/tools/testing/selftests/vDSO/vdso_test_prefetch_page.c
new file mode 100644
index 000000000000..35928c3f36ca
--- /dev/null
+++ b/tools/testing/selftests/vDSO/vdso_test_prefetch_page.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * vdso_test_prefetch_page.c: Test vDSO's prefetch_page())
+ */
+
+#define _GNU_SOURCE
+
+#include <stdint.h>
+#include <elf.h>
+#include <stdio.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "../kselftest.h"
+#include "parse_vdso.h"
+
+const char *version = "LINUX_2.6";
+const char *name = "__vdso_prefetch_page";
+
+struct getcpu_cache;
+typedef long (*prefetch_page_t)(const void *p);
+
+#define MEM_SIZE_K     (9500000ull)
+#define PAGE_SIZE      (4096ull)
+
+#define SKIP_MINCORE_BEFORE    (1 << 0)
+#define SKIP_MINCORE_AFTER     (1 << 1)
+
+static prefetch_page_t prefetch_page;
+
+static const void *ptr_align(const void *p)
+{
+       return (const void *)((unsigned long)p & ~(PAGE_SIZE - 1));
+}
+
+
+static int __test_prefetch(const void *p, bool expected_no_io,
+                          const char *test_name, unsigned int skip_mincore)
+{
+       bool no_io;
+       char vec;
+       long r;
+       uint64_t start;
+
+       p = ptr_align(p);
+
+       /*
+        * First, run a sanity check to use mincore() to see if the page is in
+        * memory when we expect it not to be.  We can only trust mincore to
+        * tell us when a page is already in memory when it should not be.
+        */
+       if (!(skip_mincore & SKIP_MINCORE_BEFORE)) {
+               if (mincore((void *)p, PAGE_SIZE, &vec)) {
+                       printf("[SKIP]\t%s: mincore failed: %s\n", test_name,
+                              strerror(errno));
+                       return 0;
+               }
+
+               no_io = vec & 1;
+               if (!skip_mincore && no_io && !expected_no_io) {
+                       printf("[SKIP]\t%s: unexpected page state: %s\n",
+                              test_name,
+                              no_io ? "in memory" : "not in memory");
+                       return 0;
+               }
+       }
+
+       /*
+        * Check we got the expected result from prefetch page.
+        */
+       r = prefetch_page(p);
+
+       no_io = r == 0;
+       if (no_io != expected_no_io) {
+               printf("[FAIL]\t%s: prefetch_page() returned %ld\n",
+                              test_name, r);
+               return KSFT_FAIL;
+       }
+
+       if (skip_mincore & SKIP_MINCORE_AFTER)
+               return 0;
+
+       /*
+        * Check again using mincore that the page state is as expected.
+        * A bit racy. Skip the test if mincore fails.
+        */
+       if (mincore((void *)p, PAGE_SIZE, &vec)) {
+               printf("[SKIP]\t%s: mincore failed: %s\n", test_name,
+                      strerror(errno));
+               return 0;
+       }
+
+       no_io = vec & 1;
+       if (0 && no_io != expected_no_io) {
+               printf("[FAIL]\t%s: mincore reported page is %s\n",
+                              test_name, no_io ? "in memory" : "not in 
memory");
+               return KSFT_FAIL;
+
+       }
+       return 0;
+}
+
+#define test_prefetch(p, expected_no_io, test_name, skip_mincore)      \
+       do {                                                            \
+               long _r = __test_prefetch(p, expected_no_io,            \
+                                         test_name, skip_mincore);     \
+                                                                       \
+               if (_r)                                                 \
+                       return _r;                                      \
+       } while (0)
+
+static void wait_for_io_completion(const void *p)
+{
+       char vec;
+       int i;
+
+       /* Wait to allow the I/O to complete */
+       p = ptr_align(p);
+
+       vec = 0;
+
+       /* Wait for 5 seconds and keep probing the page to get it */
+       for (i = 0; i < 5000; i++) {
+               if (mincore((void *)p, PAGE_SIZE, &vec) == 0 && (vec & 1))
+                       break;
+               prefetch_page(p);
+               usleep(1000);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       unsigned long sysinfo_ehdr;
+       long ret, i, test_ret = 0;
+       int fd, drop_fd;
+       char *p, vec;
+
+       printf("[RUN]\tTesting vdso_prefetch_page\n");
+
+       sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR);
+       if (!sysinfo_ehdr) {
+               printf("[SKIP]\tAT_SYSINFO_EHDR is not present!\n");
+               return KSFT_SKIP;
+       }
+
+       vdso_init_from_sysinfo_ehdr(getauxval(AT_SYSINFO_EHDR));
+
+       prefetch_page = (prefetch_page_t)vdso_sym(version, name);
+       if (!prefetch_page) {
+               printf("[SKIP]\tCould not find %s in vdso\n", name);
+               return KSFT_SKIP;
+       }
+
+       test_prefetch(NULL, false, "NULL access",
+                     SKIP_MINCORE_BEFORE|SKIP_MINCORE_AFTER);
+
+       test_prefetch(name, true, "present", 0);
+
+       p = mmap(0, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 
0);
+       if (p == MAP_FAILED) {
+               perror("mmap anon");
+               return KSFT_FAIL;
+       }
+
+       /*
+        * Mincore would not tell us that no I/O is needed to retrieve the page,
+        * so tell test_prefetch() to skip it.
+        */
+       test_prefetch(p, true, "anon prefetch", SKIP_MINCORE_BEFORE);
+
+       /* Drop the caches before testing file mmap */
+       drop_fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
+       if (drop_fd < 0) {
+               perror("open /proc/sys/vm/drop_caches");
+               return KSFT_FAIL;
+       }
+
+       sync();
+       ret = write(drop_fd, "3", 1);
+       if (ret != 1) {
+               perror("write to /proc/sys/vm/drop_caches");
+               return KSFT_FAIL;
+       }
+
+       /* close, which would also flush */
+       ret = close(drop_fd);
+       if (ret) {
+               perror("close /proc/sys/vm/drop_caches");
+               return KSFT_FAIL;
+       }
+
+       /* Using /etc/passwd as a file that should alway exist */
+       fd = open("/etc/hosts", O_RDONLY);
+       if (fd < 0) {
+               perror("open /etc/passwd");
+               return KSFT_FAIL;
+       }
+
+       p = mmap(0, PAGE_SIZE, PROT_READ, MAP_SHARED, fd, 0);
+       if (p == MAP_FAILED) {
+               perror("mmap file");
+               return KSFT_FAIL;
+       }
+
+       test_prefetch(p, false, "Minor-fault (io) file prefetch", 0);
+
+       wait_for_io_completion(p);
+
+       test_prefetch(p, true, "Minor-fault (cached) file prefetch", 0);
+
+       munmap(p, PAGE_SIZE);
+
+       /*
+        * Try to lock all to avoid unrelated page-faults before we create
+        * memory pressure to prevent unrelated page-faults.
+        */
+       mlockall(MCL_CURRENT);
+
+       p = mmap(0, 1024 * MEM_SIZE_K, PROT_READ|PROT_WRITE, 
MAP_PRIVATE|MAP_ANON, -1, 0);
+       if (p == MAP_FAILED) {
+               perror("mmap file");
+               return KSFT_FAIL;
+       }
+
+       /*
+        * Write random value to avoid try to prevent KSM from deduplicating
+        * this page.
+        */
+       *(volatile unsigned long *)p = 0x43454659;
+       ret = madvise(p, PAGE_SIZE, MADV_PAGEOUT);
+       if (ret != 0) {
+               perror("madvise(MADV_PAGEOUT)");
+               return KSFT_FAIL;
+       }
+
+       /* Wait to allow the page-out to complete */
+       usleep(2000000);
+
+       /* Cause some memory pressure */
+       for (i = PAGE_SIZE; i < MEM_SIZE_K * 1024; i += PAGE_SIZE)
+               *(volatile unsigned long *)((unsigned long)p + i) = i + 1;
+
+       /* Check if we managed to evict the page */
+       ret = mincore(p, PAGE_SIZE, &vec);
+       if (ret != 0) {
+               perror("mincore");
+               return KSFT_FAIL;
+       }
+
+       test_prefetch(p, false, "Minor-fault (io) anon prefetch", 0);
+       wait_for_io_completion(p);
+
+       test_prefetch(p, true, "Minor-fault (cached) anon prefetch", false);
+
+       printf("[PASS]\tvdso_prefetch_page\n");
+       return 0;
+}
-- 
2.25.1

Reply via email to