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