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_merge(int pagemap_fd, int pagesize) 188 { 189 char *reserved, *map, *map2; 190 191 /* 192 * Reserve space for tests: 193 * 194 * ---padding to --- 195 * | avoid adj. | 196 * v merge v 197 * |---|---|---|---|---| 198 * | | 1 | 2 | 3 | | 199 * |---|---|---|---|---| 200 */ 201 reserved = mmap(NULL, 5 * pagesize, PROT_NONE, 202 MAP_ANON | MAP_PRIVATE, -1, 0); 203 if (reserved == MAP_FAILED) 204 ksft_exit_fail_msg("mmap failed\n"); 205 munmap(reserved, 4 * pagesize); 206 207 /* 208 * Establish initial VMA: 209 * 210 * S/D 211 * |---|---|---|---|---| 212 * | | 1 | | | | 213 * |---|---|---|---|---| 214 */ 215 map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE, 216 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 217 if (map == MAP_FAILED) 218 ksft_exit_fail_msg("mmap failed\n"); 219 220 /* This will clear VM_SOFTDIRTY too. */ 221 clear_softdirty(); 222 223 /* 224 * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from 225 * map: 226 * 227 * - S/D 228 * |---|---|---|---|---| 229 * | | 1 | | 2 | | 230 * |---|---|---|---|---| 231 */ 232 map2 = mmap(&reserved[3 * pagesize], pagesize, PROT_READ | PROT_WRITE, 233 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 234 if (map2 == MAP_FAILED) 235 ksft_exit_fail_msg("mmap failed\n"); 236 237 /* 238 * Now remap it immediately adjacent to map, if the merge correctly 239 * propagates VM_SOFTDIRTY, we should then observe the VMA as a whole 240 * being marked soft-dirty: 241 * 242 * merge 243 * S/D 244 * |---|-------|---|---| 245 * | | 1 | | | 246 * |---|-------|---|---| 247 */ 248 map2 = mremap(map2, pagesize, pagesize, MREMAP_FIXED | MREMAP_MAYMOVE, 249 &reserved[2 * pagesize]); 250 if (map2 == MAP_FAILED) 251 ksft_exit_fail_msg("mremap failed\n"); 252 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 253 "Test %s-anon soft-dirty after remap merge 1st pg\n", 254 __func__); 255 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, 256 "Test %s-anon soft-dirty after remap merge 2nd pg\n", 257 __func__); 258 259 munmap(map, 2 * pagesize); 260 261 /* 262 * Now establish another VMA: 263 * 264 * S/D 265 * |---|---|---|---|---| 266 * | | 1 | | | | 267 * |---|---|---|---|---| 268 */ 269 map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE, 270 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 271 if (map == MAP_FAILED) 272 ksft_exit_fail_msg("mmap failed\n"); 273 274 /* Clear VM_SOFTDIRTY... */ 275 clear_softdirty(); 276 /* ...and establish incompatible adjacent VMA: 277 * 278 * - S/D 279 * |---|---|---|---|---| 280 * | | 1 | 2 | | | 281 * |---|---|---|---|---| 282 */ 283 map2 = mmap(&reserved[2 * pagesize], pagesize, 284 PROT_READ | PROT_WRITE | PROT_EXEC, 285 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 286 if (map2 == MAP_FAILED) 287 ksft_exit_fail_msg("mmap failed\n"); 288 289 /* 290 * Now mprotect() VMA 1 so it's compatible with 2 and therefore merges: 291 * 292 * merge 293 * S/D 294 * |---|-------|---|---| 295 * | | 1 | | | 296 * |---|-------|---|---| 297 */ 298 if (mprotect(map, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC)) 299 ksft_exit_fail_msg("mprotect failed\n"); 300 301 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 302 "Test %s-anon soft-dirty after mprotect merge 1st pg\n", 303 __func__); 304 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, 305 "Test %s-anon soft-dirty after mprotect merge 2nd pg\n", 306 __func__); 307 308 munmap(map, 2 * pagesize); 309 } 310 311 static void test_mprotect_anon(int pagemap_fd, int pagesize) 312 { 313 test_mprotect(pagemap_fd, pagesize, true); 314 } 315 316 static void test_mprotect_file(int pagemap_fd, int pagesize) 317 { 318 test_mprotect(pagemap_fd, pagesize, false); 319 } 320 321 int main(int argc, char **argv) 322 { 323 int pagemap_fd; 324 int pagesize; 325 326 ksft_print_header(); 327 328 if (!softdirty_supported()) 329 ksft_exit_skip("soft-dirty is not support\n"); 330 331 ksft_set_plan(19); 332 pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY); 333 if (pagemap_fd < 0) 334 ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH); 335 336 pagesize = getpagesize(); 337 338 test_simple(pagemap_fd, pagesize); 339 test_vma_reuse(pagemap_fd, pagesize); 340 test_hugepage(pagemap_fd, pagesize); 341 test_mprotect_anon(pagemap_fd, pagesize); 342 test_mprotect_file(pagemap_fd, pagesize); 343 test_merge(pagemap_fd, pagesize); 344 345 close(pagemap_fd); 346 347 ksft_finished(); 348 } 349