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 10 #include "../kselftest.h" 11 #include "vm_util.h" 12 #include "thp_settings.h" 13 14 #define PAGEMAP_FILE_PATH "/proc/self/pagemap" 15 #define TEST_ITERATIONS 10000 16 17 static void test_simple(int pagemap_fd, int pagesize) 18 { 19 int i; 20 char *map; 21 22 map = aligned_alloc(pagesize, pagesize); 23 if (!map) 24 ksft_exit_fail_msg("mmap failed\n"); 25 26 clear_softdirty(); 27 28 for (i = 0 ; i < TEST_ITERATIONS; i++) { 29 if (pagemap_is_softdirty(pagemap_fd, map) == 1) { 30 ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i); 31 break; 32 } 33 34 clear_softdirty(); 35 // Write something to the page to get the dirty bit enabled on the page 36 map[0]++; 37 38 if (pagemap_is_softdirty(pagemap_fd, map) == 0) { 39 ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i); 40 break; 41 } 42 43 clear_softdirty(); 44 } 45 free(map); 46 47 ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__); 48 } 49 50 static void test_vma_reuse(int pagemap_fd, int pagesize) 51 { 52 char *map, *map2; 53 54 map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0); 55 if (map == MAP_FAILED) 56 ksft_exit_fail_msg("mmap failed"); 57 58 // The kernel always marks new regions as soft dirty 59 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 60 "Test %s dirty bit of allocated page\n", __func__); 61 62 clear_softdirty(); 63 munmap(map, pagesize); 64 65 map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0); 66 if (map2 == MAP_FAILED) 67 ksft_exit_fail_msg("mmap failed"); 68 69 // Dirty bit is set for new regions even if they are reused 70 if (map == map2) 71 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, 72 "Test %s dirty bit of reused address page\n", __func__); 73 else 74 ksft_test_result_skip("Test %s dirty bit of reused address page\n", __func__); 75 76 munmap(map2, pagesize); 77 } 78 79 static void test_hugepage(int pagemap_fd, int pagesize) 80 { 81 char *map; 82 int i, ret; 83 84 if (!thp_is_enabled()) { 85 ksft_test_result_skip("Transparent Hugepages not available\n"); 86 return; 87 } 88 89 size_t hpage_len = read_pmd_pagesize(); 90 if (!hpage_len) 91 ksft_exit_fail_msg("Reading PMD pagesize failed"); 92 93 map = memalign(hpage_len, hpage_len); 94 if (!map) 95 ksft_exit_fail_msg("memalign failed\n"); 96 97 ret = madvise(map, hpage_len, MADV_HUGEPAGE); 98 if (ret) 99 ksft_exit_fail_msg("madvise failed %d\n", ret); 100 101 for (i = 0; i < hpage_len; i++) 102 map[i] = (char)i; 103 104 if (check_huge_anon(map, 1, hpage_len)) { 105 ksft_test_result_pass("Test %s huge page allocation\n", __func__); 106 107 clear_softdirty(); 108 for (i = 0 ; i < TEST_ITERATIONS ; i++) { 109 if (pagemap_is_softdirty(pagemap_fd, map) == 1) { 110 ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i); 111 break; 112 } 113 114 clear_softdirty(); 115 // Write something to the page to get the dirty bit enabled on the page 116 map[0]++; 117 118 if (pagemap_is_softdirty(pagemap_fd, map) == 0) { 119 ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i); 120 break; 121 } 122 clear_softdirty(); 123 } 124 125 ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__); 126 } else { 127 // hugepage allocation failed. skip these tests 128 ksft_test_result_skip("Test %s huge page allocation\n", __func__); 129 ksft_test_result_skip("Test %s huge page dirty bit\n", __func__); 130 } 131 free(map); 132 } 133 134 static void test_mprotect(int pagemap_fd, int pagesize, bool anon) 135 { 136 const char *type[] = {"file", "anon"}; 137 const char *fname = "./soft-dirty-test-file"; 138 int test_fd = 0; 139 char *map; 140 141 if (anon) { 142 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, 143 MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); 144 if (!map) 145 ksft_exit_fail_msg("anon mmap failed\n"); 146 } else { 147 test_fd = open(fname, O_RDWR | O_CREAT, 0664); 148 if (test_fd < 0) { 149 ksft_test_result_skip("Test %s open() file failed\n", __func__); 150 return; 151 } 152 unlink(fname); 153 ftruncate(test_fd, pagesize); 154 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, 155 MAP_SHARED, test_fd, 0); 156 if (!map) 157 ksft_exit_fail_msg("file mmap failed\n"); 158 } 159 160 *map = 1; 161 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 162 "Test %s-%s dirty bit of new written page\n", 163 __func__, type[anon]); 164 clear_softdirty(); 165 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, 166 "Test %s-%s soft-dirty clear after clear_refs\n", 167 __func__, type[anon]); 168 mprotect(map, pagesize, PROT_READ); 169 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, 170 "Test %s-%s soft-dirty clear after marking RO\n", 171 __func__, type[anon]); 172 mprotect(map, pagesize, PROT_READ|PROT_WRITE); 173 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, 174 "Test %s-%s soft-dirty clear after marking RW\n", 175 __func__, type[anon]); 176 *map = 2; 177 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 178 "Test %s-%s soft-dirty after rewritten\n", 179 __func__, type[anon]); 180 181 munmap(map, pagesize); 182 183 if (!anon) 184 close(test_fd); 185 } 186 187 static void test_mprotect_anon(int pagemap_fd, int pagesize) 188 { 189 test_mprotect(pagemap_fd, pagesize, true); 190 } 191 192 static void test_mprotect_file(int pagemap_fd, int pagesize) 193 { 194 test_mprotect(pagemap_fd, pagesize, false); 195 } 196 197 int main(int argc, char **argv) 198 { 199 int pagemap_fd; 200 int pagesize; 201 202 ksft_print_header(); 203 ksft_set_plan(15); 204 205 pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY); 206 if (pagemap_fd < 0) 207 ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH); 208 209 pagesize = getpagesize(); 210 211 test_simple(pagemap_fd, pagesize); 212 test_vma_reuse(pagemap_fd, pagesize); 213 test_hugepage(pagemap_fd, pagesize); 214 test_mprotect_anon(pagemap_fd, pagesize); 215 test_mprotect_file(pagemap_fd, pagesize); 216 217 close(pagemap_fd); 218 219 ksft_finished(); 220 } 221