From: Lian Wang lianux.mm@gmail.com
Let's add a simple test for MADV_DONTNEED and PROCESS_MADV_DONTNEED, and inspired by SeongJae Park's test at GitHub[1] add batch test for PROCESS_MADV_DONTNEED,but for now it influence by workload and need add some race conditions test.We can add it later.
Signed-off-by: Lian Wang lianux.mm@gmail.com References ==========
[1] https://github.com/sjp38/eval_proc_madvise
--- tools/testing/selftests/mm/.gitignore | 1 + tools/testing/selftests/mm/Makefile | 1 + tools/testing/selftests/mm/madv_dontneed.c | 220 +++++++++++++++++++++ tools/testing/selftests/mm/run_vmtests.sh | 5 + 4 files changed, 227 insertions(+) create mode 100644 tools/testing/selftests/mm/madv_dontneed.c
diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore index 824266982aa3..911f39d634be 100644 --- a/tools/testing/selftests/mm/.gitignore +++ b/tools/testing/selftests/mm/.gitignore @@ -25,6 +25,7 @@ pfnmap protection_keys protection_keys_32 protection_keys_64 +madv_dontneed madv_populate uffd-stress uffd-unit-tests diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile index ae6f994d3add..2352252f3914 100644 --- a/tools/testing/selftests/mm/Makefile +++ b/tools/testing/selftests/mm/Makefile @@ -67,6 +67,7 @@ TEST_GEN_FILES += hugepage-mremap TEST_GEN_FILES += hugepage-shm TEST_GEN_FILES += hugepage-vmemmap TEST_GEN_FILES += khugepaged +TEST_GEN_FILES += madv_dontneed TEST_GEN_FILES += madv_populate TEST_GEN_FILES += map_fixed_noreplace TEST_GEN_FILES += map_hugetlb diff --git a/tools/testing/selftests/mm/madv_dontneed.c b/tools/testing/selftests/mm/madv_dontneed.c new file mode 100644 index 000000000000..b88444da7f9e --- /dev/null +++ b/tools/testing/selftests/mm/madv_dontneed.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MADV_DONTNEED and PROCESS_MADV_DONTNEED tests + * + * Copyright (C) 2025, Linx Software Corp. + * + * Author(s): Lian Wang lianux.mm@gmail.com + */ +#define _GNU_SOURCE +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/mman.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include "vm_util.h" +#include <time.h> + +#include "../kselftest.h" + +/* + * For now, we're using 2 MiB of private anonymous memory for all tests. + */ +#define SIZE (256 * 1024 * 1024) + +static size_t pagesize; + +static void sense_support(void) +{ + char *addr; + int ret; + + addr = mmap(0, pagesize, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + if (!addr) + ksft_exit_fail_msg("mmap failed\n"); + + ret = madvise(addr, pagesize, MADV_DONTNEED); + if (ret) + ksft_exit_skip("MADV_DONTNEED is not available\n"); + + munmap(addr, pagesize); +} + +/* + * Read pagemap to check page is present in mermory + */ +static bool is_page_present(void *addr) +{ + uintptr_t vaddr = (uintptr_t)addr; + uintptr_t offset = (vaddr / pagesize) * sizeof(uint64_t); + ssize_t bytes_read; + uint64_t entry; + bool ret; + int fd; + + fd = open("/proc/self/pagemap", O_RDONLY); + if (fd < 0) { + ksft_exit_fail_msg("opening pagemap failed\n"); + ret = false; + } + + if ((lseek(fd, offset, SEEK_SET)) == -1) { + close(fd); + ret = false; + } + + bytes_read = read(fd, &entry, sizeof(entry)); + close(fd); + + if (bytes_read != sizeof(entry)) { + perror("read failed"); + return false; + } + + if (entry & (1ULL << 63)) + ret = true; + + return ret; +} + +/* + * test madvsise_dontneed + */ +static void test_madv_dontneed(void) +{ + unsigned long rss_anon_before, rss_anon_after; + bool present, rss; + char *addr; + int ret; + + ksft_print_msg("[RUN] %s\n", __func__); + + addr = mmap(0, SIZE, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + if (!addr) + ksft_exit_fail_msg("mmap failed\n"); + + memset(addr, 0x7A, SIZE); + + rss_anon_before = rss_anon(); + if (!rss_anon_before) + ksft_exit_fail_msg("No RssAnon is allocated before dontneed\n"); + ret = madvise(addr, SIZE, MADV_DONTNEED); + ksft_test_result(!ret, "MADV_DONTNEED\n"); + + rss_anon_after = rss_anon(); + if (rss_anon_after < rss_anon_before) + rss = true; + ksft_test_result(rss, "MADV_DONTNEED rss is correct\n"); + + for (size_t i = 0; i < SIZE; i += pagesize) { + present = is_page_present(addr + i); + if (present) { + ksft_print_msg("Page not zero at offset %zu\n", + (size_t)i); + } + } + + ksft_test_result(!present, "MADV_DONTNEED page is present\n"); + munmap(addr, SIZE); +} + +/* + * Measure performance of batched process_madvise vs madvise + */ +static int measure_process_madvise_batching(int hint, int total_size, + int single_unit, int batch_size) +{ + struct iovec *vec = malloc(sizeof(*vec) * batch_size); + struct timespec start, end; + unsigned long elapsed_ns = 0; + unsigned long nr_measures = 0; + pid_t pid = getpid(); + char *buf; + int pidfd; + + pidfd = syscall(SYS_pidfd_open, pid, 0); + if (pidfd == -1) { + perror("pidfd_open fail"); + return -1; + } + + buf = mmap(NULL, total_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (buf == MAP_FAILED) { + perror("mmap fail"); + goto out; + } + + if (!vec) { + perror("malloc vec failed"); + goto unmap_out; + } + + while (elapsed_ns < 5UL * 1000 * 1000 * 1000) { + memset(buf, 0x7A, total_size); + + clock_gettime(CLOCK_MONOTONIC, &start); + + for (int off = 0; off < total_size; + off += single_unit * batch_size) { + for (int i = 0; i < batch_size; i++) { + vec[i].iov_base = buf + off + i * single_unit; + vec[i].iov_len = single_unit; + } + syscall(SYS_process_madvise, pidfd, vec, batch_size, + hint, 0); + } + + clock_gettime(CLOCK_MONOTONIC, &end); + elapsed_ns += (end.tv_sec - start.tv_sec) * 1e9 + + (end.tv_nsec - start.tv_nsec); + nr_measures++; + } + + ksft_print_msg("[RESULT] batch=%d time=%.3f us/op\n", batch_size, + (double)(elapsed_ns / nr_measures) / + (total_size / single_unit)); + + free(vec); +unmap_out: + munmap(buf, total_size); +out: + close(pidfd); + return 0; +} + +static void test_perf_batch_process(void) +{ + ksft_print_msg("[RUN] %s\n", __func__); + measure_process_madvise_batching(MADV_DONTNEED, SIZE, pagesize, 1); + measure_process_madvise_batching(MADV_DONTNEED, SIZE, pagesize, 2); + measure_process_madvise_batching(MADV_DONTNEED, SIZE, pagesize, 4); + ksft_test_result(1, "All test were done\n"); +} + +int main(int argc, char **argv) +{ + int err; + + pagesize = getpagesize(); + + ksft_print_header(); + ksft_set_plan(4); + + sense_support(); + test_madv_dontneed(); + test_perf_batch_process(); + + err = ksft_get_fail_cnt(); + if (err) + ksft_exit_fail_msg("%d out of %d tests failed\n", err, + ksft_test_num()); + ksft_exit_pass(); +} diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh index dddd1dd8af14..f96d43153fc0 100755 --- a/tools/testing/selftests/mm/run_vmtests.sh +++ b/tools/testing/selftests/mm/run_vmtests.sh @@ -47,6 +47,8 @@ separated by spaces: hmm smoke tests - madv_guard test madvise(2) MADV_GUARD_INSTALL and MADV_GUARD_REMOVE options +- madv_dontneed + test memadvise(2) MADV_DONTNEED and PROCESS_MADV_DONTNEED options - madv_populate test memadvise(2) MADV_POPULATE_{READ,WRITE} options - memfd_secret @@ -422,6 +424,9 @@ CATEGORY="hmm" run_test bash ./test_hmm.sh smoke # MADV_GUARD_INSTALL and MADV_GUARD_REMOVE tests CATEGORY="madv_guard" run_test ./guard-regions
+# MADV_DONTNEED and PROCESS_DONTNEED tests +CATEGORY="madv_dontneed" run_test ./madv_dontneed + # MADV_POPULATE_READ and MADV_POPULATE_WRITE tests CATEGORY="madv_populate" run_test ./madv_populate