From: Nadav Amit <namit@xxxxxxxxxx> 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 <luto@xxxxxxxxxx> Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx> Cc: Sean Christopherson <seanjc@xxxxxxxxxx> Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx> Cc: Ingo Molnar <mingo@xxxxxxxxxx> Cc: Borislav Petkov <bp@xxxxxxxxx> Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> Cc: x86@xxxxxxxxxx Signed-off-by: Nadav Amit <namit@xxxxxxxxxx> --- 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