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 if (!hpage_len) 84 ksft_exit_fail_msg("Reading PMD pagesize failed"); 85 86 map = memalign(hpage_len, hpage_len); 87 if (!map) 88 ksft_exit_fail_msg("memalign failed\n"); 89 90 ret = madvise(map, hpage_len, MADV_HUGEPAGE); 91 if (ret) 92 ksft_exit_fail_msg("madvise failed %d\n", ret); 93 94 for (i = 0; i < hpage_len; i++) 95 map[i] = (char)i; 96 97 if (check_huge_anon(map, 1, hpage_len)) { 98 ksft_test_result_pass("Test %s huge page allocation\n", __func__); 99 100 clear_softdirty(); 101 for (i = 0 ; i < TEST_ITERATIONS ; i++) { 102 if (pagemap_is_softdirty(pagemap_fd, map) == 1) { 103 ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i); 104 break; 105 } 106 107 clear_softdirty(); 108 // Write something to the page to get the dirty bit enabled on the page 109 map[0]++; 110 111 if (pagemap_is_softdirty(pagemap_fd, map) == 0) { 112 ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i); 113 break; 114 } 115 clear_softdirty(); 116 } 117 118 ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__); 119 } else { 120 // hugepage allocation failed. skip these tests 121 ksft_test_result_skip("Test %s huge page allocation\n", __func__); 122 ksft_test_result_skip("Test %s huge page dirty bit\n", __func__); 123 } 124 free(map); 125 } 126 127 static void test_mprotect(int pagemap_fd, int pagesize, bool anon) 128 { 129 const char *type[] = {"file", "anon"}; 130 const char *fname = "./soft-dirty-test-file"; 131 int test_fd; 132 char *map; 133 134 if (anon) { 135 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, 136 MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); 137 if (!map) 138 ksft_exit_fail_msg("anon mmap failed\n"); 139 } else { 140 test_fd = open(fname, O_RDWR | O_CREAT, 0664); 141 if (test_fd < 0) { 142 ksft_test_result_skip("Test %s open() file failed\n", __func__); 143 return; 144 } 145 unlink(fname); 146 ftruncate(test_fd, pagesize); 147 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, 148 MAP_SHARED, test_fd, 0); 149 if (!map) 150 ksft_exit_fail_msg("file mmap failed\n"); 151 } 152 153 *map = 1; 154 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 155 "Test %s-%s dirty bit of new written page\n", 156 __func__, type[anon]); 157 clear_softdirty(); 158 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, 159 "Test %s-%s soft-dirty clear after clear_refs\n", 160 __func__, type[anon]); 161 mprotect(map, pagesize, PROT_READ); 162 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, 163 "Test %s-%s soft-dirty clear after marking RO\n", 164 __func__, type[anon]); 165 mprotect(map, pagesize, PROT_READ|PROT_WRITE); 166 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, 167 "Test %s-%s soft-dirty clear after marking RW\n", 168 __func__, type[anon]); 169 *map = 2; 170 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 171 "Test %s-%s soft-dirty after rewritten\n", 172 __func__, type[anon]); 173 174 munmap(map, pagesize); 175 176 if (!anon) 177 close(test_fd); 178 } 179 180 static void test_mprotect_anon(int pagemap_fd, int pagesize) 181 { 182 test_mprotect(pagemap_fd, pagesize, true); 183 } 184 185 static void test_mprotect_file(int pagemap_fd, int pagesize) 186 { 187 test_mprotect(pagemap_fd, pagesize, false); 188 } 189 190 int main(int argc, char **argv) 191 { 192 int pagemap_fd; 193 int pagesize; 194 195 ksft_print_header(); 196 ksft_set_plan(15); 197 198 pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY); 199 if (pagemap_fd < 0) 200 ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH); 201 202 pagesize = getpagesize(); 203 204 test_simple(pagemap_fd, pagesize); 205 test_vma_reuse(pagemap_fd, pagesize); 206 test_hugepage(pagemap_fd, pagesize); 207 test_mprotect_anon(pagemap_fd, pagesize); 208 test_mprotect_file(pagemap_fd, pagesize); 209 210 close(pagemap_fd); 211 212 ksft_finished(); 213 } 214