Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: vm: add test for Soft-Dirty PTE bit

This introduces three tests:

1) Sanity check soft dirty basic semantics: allocate area, clean,
dirty, check if the SD bit is flipped.

2) Check VMA reuse: validate the VM_SOFTDIRTY usage

3) Check soft-dirty on huge pages

This was motivated by Will Deacon's fix commit 912efa17e512 ("mm: proc:
Invalidate TLB after clearing soft-dirty page state"). I was tracking the
same issue that he fixed, and this test would have caught it.

Link: https://lkml.kernel.org/r/20220420084036.4101604-2-usama.anjum@collabora.com
Signed-off-by: Gabriel Krisman Bertazi <krisman@collabora.com>
Signed-off-by: Muhammad Usama Anjum <usama.anjum@collabora.com>
Co-developed-by: Muhammad Usama Anjum <usama.anjum@collabora.com>
Cc: Will Deacon <will@kernel.org>
Cc: Shuah Khan <shuah@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Gabriel Krisman Bertazi and committed by
akpm
9f3265db 642bc52a

+150
+1
tools/testing/selftests/vm/.gitignore
··· 29 29 hmm-tests 30 30 memfd_secret 31 31 local_config.* 32 + soft-dirty 32 33 split_huge_page_test 33 34 ksm_tests
+2
tools/testing/selftests/vm/Makefile
··· 50 50 TEST_GEN_FILES += thuge-gen 51 51 TEST_GEN_FILES += transhuge-stress 52 52 TEST_GEN_FILES += userfaultfd 53 + TEST_GEN_PROGS += soft-dirty 53 54 TEST_GEN_PROGS += split_huge_page_test 54 55 TEST_GEN_FILES += ksm_tests 55 56 ··· 96 95 include ../lib.mk 97 96 98 97 $(OUTPUT)/madv_populate: vm_util.c 98 + $(OUTPUT)/soft-dirty: vm_util.c 99 99 $(OUTPUT)/split_huge_page_test: vm_util.c 100 100 101 101 ifeq ($(MACHINE),x86_64)
+2
tools/testing/selftests/vm/config
··· 4 4 CONFIG_DEVICE_PRIVATE=y 5 5 CONFIG_TEST_HMM=m 6 6 CONFIG_GUP_TEST=y 7 + CONFIG_TRANSPARENT_HUGEPAGE=y 8 + CONFIG_MEM_SOFT_DIRTY=y
+145
tools/testing/selftests/vm/soft-dirty.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + #include <stdio.h> 3 + #include <string.h> 4 + #include <stdbool.h> 5 + #include <fcntl.h> 6 + #include <stdint.h> 7 + #include <malloc.h> 8 + #include <sys/mman.h> 9 + #include "../kselftest.h" 10 + #include "vm_util.h" 11 + 12 + #define PAGEMAP_FILE_PATH "/proc/self/pagemap" 13 + #define TEST_ITERATIONS 10000 14 + 15 + static void test_simple(int pagemap_fd, int pagesize) 16 + { 17 + int i; 18 + char *map; 19 + 20 + map = aligned_alloc(pagesize, pagesize); 21 + if (!map) 22 + ksft_exit_fail_msg("mmap failed\n"); 23 + 24 + clear_softdirty(); 25 + 26 + for (i = 0 ; i < TEST_ITERATIONS; i++) { 27 + if (pagemap_is_softdirty(pagemap_fd, map) == 1) { 28 + ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i); 29 + break; 30 + } 31 + 32 + clear_softdirty(); 33 + // Write something to the page to get the dirty bit enabled on the page 34 + map[0]++; 35 + 36 + if (pagemap_is_softdirty(pagemap_fd, map) == 0) { 37 + ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i); 38 + break; 39 + } 40 + 41 + clear_softdirty(); 42 + } 43 + free(map); 44 + 45 + ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__); 46 + } 47 + 48 + static void test_vma_reuse(int pagemap_fd, int pagesize) 49 + { 50 + char *map, *map2; 51 + 52 + map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0); 53 + if (map == MAP_FAILED) 54 + ksft_exit_fail_msg("mmap failed"); 55 + 56 + // The kernel always marks new regions as soft dirty 57 + ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 58 + "Test %s dirty bit of allocated page\n", __func__); 59 + 60 + clear_softdirty(); 61 + munmap(map, pagesize); 62 + 63 + map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0); 64 + if (map2 == MAP_FAILED) 65 + ksft_exit_fail_msg("mmap failed"); 66 + 67 + // Dirty bit is set for new regions even if they are reused 68 + if (map == map2) 69 + ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, 70 + "Test %s dirty bit of reused address page\n", __func__); 71 + else 72 + ksft_test_result_skip("Test %s dirty bit of reused address page\n", __func__); 73 + 74 + munmap(map2, pagesize); 75 + } 76 + 77 + static void test_hugepage(int pagemap_fd, int pagesize) 78 + { 79 + char *map; 80 + int i, ret; 81 + size_t hpage_len = read_pmd_pagesize(); 82 + 83 + map = memalign(hpage_len, hpage_len); 84 + if (!map) 85 + ksft_exit_fail_msg("memalign failed\n"); 86 + 87 + ret = madvise(map, hpage_len, MADV_HUGEPAGE); 88 + if (ret) 89 + ksft_exit_fail_msg("madvise failed %d\n", ret); 90 + 91 + for (i = 0; i < hpage_len; i++) 92 + map[i] = (char)i; 93 + 94 + if (check_huge(map)) { 95 + ksft_test_result_pass("Test %s huge page allocation\n", __func__); 96 + 97 + clear_softdirty(); 98 + for (i = 0 ; i < TEST_ITERATIONS ; i++) { 99 + if (pagemap_is_softdirty(pagemap_fd, map) == 1) { 100 + ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i); 101 + break; 102 + } 103 + 104 + clear_softdirty(); 105 + // Write something to the page to get the dirty bit enabled on the page 106 + map[0]++; 107 + 108 + if (pagemap_is_softdirty(pagemap_fd, map) == 0) { 109 + ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i); 110 + break; 111 + } 112 + clear_softdirty(); 113 + } 114 + 115 + ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__); 116 + } else { 117 + // hugepage allocation failed. skip these tests 118 + ksft_test_result_skip("Test %s huge page allocation\n", __func__); 119 + ksft_test_result_skip("Test %s huge page dirty bit\n", __func__); 120 + } 121 + free(map); 122 + } 123 + 124 + int main(int argc, char **argv) 125 + { 126 + int pagemap_fd; 127 + int pagesize; 128 + 129 + ksft_print_header(); 130 + ksft_set_plan(5); 131 + 132 + pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY); 133 + if (pagemap_fd < 0) 134 + ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH); 135 + 136 + pagesize = getpagesize(); 137 + 138 + test_simple(pagemap_fd, pagesize); 139 + test_vma_reuse(pagemap_fd, pagesize); 140 + test_hugepage(pagemap_fd, pagesize); 141 + 142 + close(pagemap_fd); 143 + 144 + return ksft_exit_pass(); 145 + }