1 // SPDX-License-Identifier: GPL-2.0+
3 * holetest -- test simultaneous page faults on hole-backed pages
4 * Copyright (C) 2015 Hewlett Packard Enterprise Development LP
11 * gcc -Wall -pthread -o holetest holetest.c
13 * This test tool exercises page faults on hole-y portions of an mmapped
14 * file. The file is created, sized using various methods, mmapped, and
15 * then two threads race to write a marker to different offsets within
16 * each mapped page. Once the threads have finished marking each page,
17 * the pages are checked for the presence of the markers.
19 * The file is sized four different ways: explicitly zero-filled by the
20 * test, posix_fallocate(), fallocate(), and ftruncate(). The explicit
21 * zero-fill does not really test simultaneous page faults on hole-backed
22 * pages, but rather serves as control of sorts.
26 * holetest [-f] FILENAME FILESIZEinMB
30 * FILENAME is the name of a non-existent test file to create
32 * FILESIZEinMB is the desired size of the test file in MiB
34 * If the test is successful, FILENAME will be unlinked. By default,
35 * if the test detects an error in the page markers, then the test exits
36 * immediately and FILENAME is left. If -f is given, then the test
37 * continues after a marker error and FILENAME is unlinked, but will
38 * still exit with a non-0 status.
56 long page_offs[THREADS];
65 return (uint64_t) pthread_self();
69 void prefault_mapping(char *addr, long npages)
73 for (i = 0; i < npages; i++) {
74 if (addr[i * page_size] != 0) {
75 fprintf(stderr, "Prefaulting found non-zero value in "
76 "page %ld: %d\n", i, (int)addr[i * page_size]);
81 int verify_mapping(char *vastart, long npages, uint64_t *expect)
87 for (va = vastart; npages > 0; va += page_size, npages--) {
88 for (i = 0; i < THREADS; i++) {
89 if (*(uint64_t*)(va + page_offs[i]) != expect[i]) {
90 printf("ERROR: thread %d, "
91 "offset %08llx, %08llx != %08llx\n", i,
92 (unsigned long long) (va + page_offs[i] - vastart),
93 (unsigned long long) *(uint64_t*)(va + page_offs[i]),
94 (unsigned long long) expect[i]);
102 void *pt_page_marker(void *args)
105 char *va = (char *)a[1];
106 long npages = (long)a[2];
108 long pgoff = page_offs[(long)a[3]];
109 uint64_t tid = get_id();
112 if (prefault && use_fork)
113 prefault_mapping(va, npages);
116 for (i = 0; i < npages; i++)
117 *(uint64_t *)(va + pgoff + i * page_size) = tid;
119 if (use_private && use_fork) {
120 uint64_t expect[THREADS] = {};
122 expect[(long)a[3]] = tid;
123 errors = verify_mapping(va, npages, expect);
126 return (void *)errors;
127 } /* pt_page_marker() */
129 void *pt_write_marker(void *args)
133 long npages = (long)a[2];
134 long pgoff = page_offs[(long)a[3]];
135 uint64_t tid = get_id();
139 for (i = 0; i < npages; i++)
140 pwrite(fd, &tid, sizeof(tid), i * page_size + pgoff);
145 int test_this(int fd, loff_t sz)
149 void *targs[THREADS][4];
150 pthread_t t[THREADS];
151 uint64_t tid[THREADS];
155 npages = sz / page_size;
156 printf("INFO: sz = %llu\n", (unsigned long long)sz);
159 vastart = mmap(NULL, sz, PROT_READ | PROT_WRITE,
160 use_private ? MAP_PRIVATE : MAP_SHARED, fd, 0);
161 if (MAP_FAILED == vastart) {
166 if (prefault && !use_fork)
167 prefault_mapping(vastart, npages);
169 /* prepare the thread args */
170 for (i = 0; i < THREADS; i++) {
171 targs[i][0] = (void *)(long)fd;
172 targs[i][1] = vastart;
173 targs[i][2] = (void *)npages;
174 targs[i][3] = (void *)(long)i;
177 for (i = 0; i < THREADS; i++) {
179 /* start two threads */
180 if (pthread_create(&t[i], NULL,
181 use_wr[i] ? pt_write_marker : pt_page_marker,
183 perror("pthread_create");
186 tid[i] = (uint64_t)t[i];
187 printf("INFO: thread %d created\n", i);
191 * Flush stdout before fork, otherwise some lines get
192 * duplicated... ?!?!?
200 for (j = 0; j < i; j++)
201 waitpid(tid[j], NULL, 0);
208 ret = pt_write_marker(&targs[i]);
210 ret = pt_page_marker(&targs[i]);
214 printf("INFO: process %d created\n", i);
218 /* wait for them to finish */
219 for (i = 0; i < THREADS; i++) {
223 pthread_join(t[i], &status);
229 waitpid(tid[i], &status, 0);
230 if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
235 /* check markers on each page */
236 /* For private mappings & fork we should see no writes happen */
237 if (use_private && use_fork)
238 for (i = 0; i < THREADS; i++)
240 errcnt = verify_mapping(vastart, npages, tid);
244 /* Check that no writes propagated into original file */
245 for (i = 0; i < THREADS; i++)
247 vastart = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);
248 if (vastart == MAP_FAILED) {
252 errcnt += verify_mapping(vastart, npages, tid);
256 printf("INFO: %d error(s) detected\n", errcnt);
262 int main(int argc, char **argv)
274 page_size = getpagesize();
275 step = page_size / THREADS;
276 page_offs[0] = step / 2;
277 for (i = 1; i < THREADS; i++)
278 page_offs[i] = page_offs[i-1] + step;
280 while ((opt = getopt(argc, argv, "fwrFp")) > 0) {
287 /* use writes instead of mmap for one thread */
291 /* prefault mmapped area by reading it */
295 /* create processes instead of threads */
299 /* Use private mappings for testing */
303 fprintf(stderr, "ERROR: Unknown option character.\n");
308 if (optind != argc - 2) {
309 fprintf(stderr, "ERROR: usage: holetest [-fwrFp] "
310 "FILENAME FILESIZEinMB\n");
313 if (use_private && use_wr[0]) {
314 fprintf(stderr, "ERROR: Combinations of writes and private"
315 "mappings not supported.\n");
320 sz = strtol(argv[optind + 1], &endch, 10);
321 if (*endch || sz < 1) {
322 fprintf(stderr, "ERROR: bad FILESIZEinMB\n");
328 * we're going to run our test in several different ways:
330 * 1. explictly zero-filled
331 * 2. posix_fallocated
337 * explicitly zero-filled
339 printf("\nINFO: zero-filled test...\n");
341 /* create the file */
342 fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644);
348 /* truncate it to size */
349 if (ftruncate(fd, sz)) {
350 perror("ftruncate()");
354 /* explicitly zero-fill */
356 char* va = mmap(NULL, sz, PROT_READ | PROT_WRITE,
358 if (MAP_FAILED == va) {
367 errcnt = test_this(fd, sz);
370 if (stoponerror && errcnt > 0)
383 printf("\nINFO: posix_fallocate test...\n");
385 /* create the file */
386 fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644);
392 /* fill it to size */
393 if (posix_fallocate(fd, 0, sz)) {
394 perror("posix_fallocate()");
399 errcnt = test_this(fd, sz);
402 if (stoponerror && errcnt > 0)
414 printf("\nINFO: ftruncate test...\n");
416 /* create the file */
417 fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644);
423 /* truncate it to size */
424 if (ftruncate(fd, sz)) {
425 perror("ftruncate()");
430 errcnt = test_this(fd, sz);
433 if (stoponerror && errcnt > 0)