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 13 #define PAGEMAP_FILE_PATH "/proc/self/pagemap" 14 #define TEST_ITERATIONS 10000 15 16 static void test_simple(int pagemap_fd, int pagesize) 17 { 18 int i; 19 char *map; 20 21 map = aligned_alloc(pagesize, pagesize); 22 if (!map) 23 ksft_exit_fail_msg("mmap failed\n"); 24 25 clear_softdirty(); 26 27 for (i = 0 ; i < TEST_ITERATIONS; i++) { 28 if (pagemap_is_softdirty(pagemap_fd, map) == 1) { 29 ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i); 30 break; 31 } 32 33 clear_softdirty(); 34 // Write something to the page to get the dirty bit enabled on the page 35 map[0]++; 36 37 if (pagemap_is_softdirty(pagemap_fd, map) == 0) { 38 ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i); 39 break; 40 } 41 42 clear_softdirty(); 43 } 44 free(map); 45 46 ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__); 47 } 48 49 static void test_vma_reuse(int pagemap_fd, int pagesize) 50 { 51 char *map, *map2; 52 53 map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0); 54 if (map == MAP_FAILED) 55 ksft_exit_fail_msg("mmap failed"); 56 57 // The kernel always marks new regions as soft dirty 58 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 59 "Test %s dirty bit of allocated page\n", __func__); 60 61 clear_softdirty(); 62 munmap(map, pagesize); 63 64 map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0); 65 if (map2 == MAP_FAILED) 66 ksft_exit_fail_msg("mmap failed"); 67 68 // Dirty bit is set for new regions even if they are reused 69 if (map == map2) 70 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, 71 "Test %s dirty bit of reused address page\n", __func__); 72 else 73 ksft_test_result_skip("Test %s dirty bit of reused address page\n", __func__); 74 75 munmap(map2, pagesize); 76 } 77 78 static void test_hugepage(int pagemap_fd, int pagesize) 79 { 80 char *map; 81 int i, ret; 82 83 if (!thp_is_enabled()) { 84 ksft_print_msg("Transparent Hugepages not available\n"); 85 ksft_test_result_skip("Test %s huge page allocation\n", __func__); 86 ksft_test_result_skip("Test %s huge page dirty bit\n", __func__); 87 return; 88 } 89 90 size_t hpage_len = read_pmd_pagesize(); 91 if (!hpage_len) 92 ksft_exit_fail_msg("Reading PMD pagesize failed"); 93 94 map = memalign(hpage_len, hpage_len); 95 if (!map) 96 ksft_exit_fail_msg("memalign failed\n"); 97 98 ret = madvise(map, hpage_len, MADV_HUGEPAGE); 99 if (ret) 100 ksft_exit_fail_msg("madvise failed %d\n", ret); 101 102 for (i = 0; i < hpage_len; i++) 103 map[i] = (char)i; 104 105 if (check_huge_anon(map, hpage_len, 1, hpage_len)) { 106 ksft_test_result_pass("Test %s huge page allocation\n", __func__); 107 108 clear_softdirty(); 109 for (i = 0 ; i < TEST_ITERATIONS ; i++) { 110 if (pagemap_is_softdirty(pagemap_fd, map) == 1) { 111 ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i); 112 break; 113 } 114 115 clear_softdirty(); 116 // Write something to the page to get the dirty bit enabled on the page 117 map[0]++; 118 119 if (pagemap_is_softdirty(pagemap_fd, map) == 0) { 120 ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i); 121 break; 122 } 123 clear_softdirty(); 124 } 125 126 ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__); 127 } else { 128 // hugepage allocation failed. skip these tests 129 ksft_test_result_skip("Test %s huge page allocation\n", __func__); 130 ksft_test_result_skip("Test %s huge page dirty bit\n", __func__); 131 } 132 free(map); 133 } 134 135 static void test_mprotect(int pagemap_fd, int pagesize, bool anon) 136 { 137 const char *type[] = {"file", "anon"}; 138 const char *fname = "./soft-dirty-test-file"; 139 int test_fd = 0; 140 char *map; 141 142 if (anon) { 143 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, 144 MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); 145 if (map == MAP_FAILED) 146 ksft_exit_fail_msg("anon mmap failed\n"); 147 } else { 148 test_fd = open(fname, O_RDWR | O_CREAT, 0664); 149 if (test_fd < 0) { 150 ksft_test_result_skip("Test %s open() file failed\n", __func__); 151 return; 152 } 153 unlink(fname); 154 if (ftruncate(test_fd, pagesize) != 0) 155 ksft_exit_fail_msg("ftruncate failed\n"); 156 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, 157 MAP_SHARED, test_fd, 0); 158 if (map == MAP_FAILED) 159 ksft_exit_fail_msg("file mmap failed\n"); 160 } 161 162 *map = 1; 163 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 164 "Test %s-%s dirty bit of new written page\n", 165 __func__, type[anon]); 166 clear_softdirty(); 167 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, 168 "Test %s-%s soft-dirty clear after clear_refs\n", 169 __func__, type[anon]); 170 mprotect(map, pagesize, PROT_READ); 171 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, 172 "Test %s-%s soft-dirty clear after marking RO\n", 173 __func__, type[anon]); 174 mprotect(map, pagesize, PROT_READ|PROT_WRITE); 175 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, 176 "Test %s-%s soft-dirty clear after marking RW\n", 177 __func__, type[anon]); 178 *map = 2; 179 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 180 "Test %s-%s soft-dirty after rewritten\n", 181 __func__, type[anon]); 182 183 munmap(map, pagesize); 184 185 if (!anon) 186 close(test_fd); 187 } 188 189 static void test_merge(int pagemap_fd, int pagesize) 190 { 191 char *reserved, *map, *map2; 192 193 /* 194 * Reserve space for tests: 195 * 196 * ---padding to --- 197 * | avoid adj. | 198 * v merge v 199 * |---|---|---|---|---| 200 * | | 1 | 2 | 3 | | 201 * |---|---|---|---|---| 202 */ 203 reserved = mmap(NULL, 5 * pagesize, PROT_NONE, 204 MAP_ANON | MAP_PRIVATE, -1, 0); 205 if (reserved == MAP_FAILED) 206 ksft_exit_fail_msg("mmap failed\n"); 207 munmap(reserved, 4 * pagesize); 208 209 /* 210 * Establish initial VMA: 211 * 212 * S/D 213 * |---|---|---|---|---| 214 * | | 1 | | | | 215 * |---|---|---|---|---| 216 */ 217 map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE, 218 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 219 if (map == MAP_FAILED) 220 ksft_exit_fail_msg("mmap failed\n"); 221 222 /* This will clear VM_SOFTDIRTY too. */ 223 clear_softdirty(); 224 225 /* 226 * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from 227 * map: 228 * 229 * - S/D 230 * |---|---|---|---|---| 231 * | | 1 | | 2 | | 232 * |---|---|---|---|---| 233 */ 234 map2 = mmap(&reserved[3 * pagesize], pagesize, PROT_READ | PROT_WRITE, 235 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 236 if (map2 == MAP_FAILED) 237 ksft_exit_fail_msg("mmap failed\n"); 238 239 /* 240 * Now remap it immediately adjacent to map, if the merge correctly 241 * propagates VM_SOFTDIRTY, we should then observe the VMA as a whole 242 * being marked soft-dirty: 243 * 244 * merge 245 * S/D 246 * |---|-------|---|---| 247 * | | 1 | | | 248 * |---|-------|---|---| 249 */ 250 map2 = mremap(map2, pagesize, pagesize, MREMAP_FIXED | MREMAP_MAYMOVE, 251 &reserved[2 * pagesize]); 252 if (map2 == MAP_FAILED) 253 ksft_exit_fail_msg("mremap failed\n"); 254 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 255 "Test %s-anon soft-dirty after remap merge 1st pg\n", 256 __func__); 257 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, 258 "Test %s-anon soft-dirty after remap merge 2nd pg\n", 259 __func__); 260 261 munmap(map, 2 * pagesize); 262 263 /* 264 * Now establish another VMA: 265 * 266 * S/D 267 * |---|---|---|---|---| 268 * | | 1 | | | | 269 * |---|---|---|---|---| 270 */ 271 map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE, 272 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 273 if (map == MAP_FAILED) 274 ksft_exit_fail_msg("mmap failed\n"); 275 276 /* Clear VM_SOFTDIRTY... */ 277 clear_softdirty(); 278 /* ...and establish incompatible adjacent VMA: 279 * 280 * - S/D 281 * |---|---|---|---|---| 282 * | | 1 | 2 | | | 283 * |---|---|---|---|---| 284 */ 285 map2 = mmap(&reserved[2 * pagesize], pagesize, 286 PROT_READ | PROT_WRITE | PROT_EXEC, 287 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 288 if (map2 == MAP_FAILED) 289 ksft_exit_fail_msg("mmap failed\n"); 290 291 /* 292 * Now mprotect() VMA 1 so it's compatible with 2 and therefore merges: 293 * 294 * merge 295 * S/D 296 * |---|-------|---|---| 297 * | | 1 | | | 298 * |---|-------|---|---| 299 */ 300 if (mprotect(map, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC)) 301 ksft_exit_fail_msg("mprotect failed\n"); 302 303 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, 304 "Test %s-anon soft-dirty after mprotect merge 1st pg\n", 305 __func__); 306 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, 307 "Test %s-anon soft-dirty after mprotect merge 2nd pg\n", 308 __func__); 309 310 munmap(map, 2 * pagesize); 311 } 312 313 static void test_mprotect_anon(int pagemap_fd, int pagesize) 314 { 315 test_mprotect(pagemap_fd, pagesize, true); 316 } 317 318 static void test_mprotect_file(int pagemap_fd, int pagesize) 319 { 320 test_mprotect(pagemap_fd, pagesize, false); 321 } 322 323 int main(int argc, char **argv) 324 { 325 int pagemap_fd; 326 int pagesize; 327 328 ksft_print_header(); 329 330 if (!softdirty_supported()) 331 ksft_exit_skip("soft-dirty is not support\n"); 332 333 ksft_set_plan(19); 334 pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY); 335 if (pagemap_fd < 0) 336 ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH); 337 338 pagesize = getpagesize(); 339 340 test_simple(pagemap_fd, pagesize); 341 test_vma_reuse(pagemap_fd, pagesize); 342 test_hugepage(pagemap_fd, pagesize); 343 test_mprotect_anon(pagemap_fd, pagesize); 344 test_mprotect_file(pagemap_fd, pagesize); 345 test_merge(pagemap_fd, pagesize); 346 347 close(pagemap_fd); 348 349 ksft_finished(); 350 } 351