2 * holetest -- test simultaneous page faults on hole-backed pages
3 * Copyright (C) 2015 Hewlett Packard Enterprise Development LP
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 * gcc -Wall -pthread -o holetest holetest.c
26 * This test tool exercises page faults on hole-y portions of an mmapped
27 * file. The file is created, sized using various methods, mmapped, and
28 * then two threads race to write a marker to different offsets within
29 * each mapped page. Once the threads have finished marking each page,
30 * the pages are checked for the presence of the markers.
32 * The file is sized four different ways: explicitly zero-filled by the
33 * test, posix_fallocate(), fallocate(), and ftruncate(). The explicit
34 * zero-fill does not really test simultaneous page faults on hole-backed
35 * pages, but rather serves as control of sorts.
39 * holetest [-f] FILENAME FILESIZEinMB
43 * FILENAME is the name of a non-existent test file to create
45 * FILESIZEinMB is the desired size of the test file in MiB
47 * If the test is successful, FILENAME will be unlinked. By default,
48 * if the test detects an error in the page markers, then the test exits
49 * immediately and FILENAME is left. If -f is given, then the test
50 * continues after a marker error and FILENAME is unlinked, but will
51 * still exit with a non-0 status.
69 long page_offs[THREADS];
78 return (uint64_t) pthread_self();
82 void prefault_mapping(char *addr, long npages)
86 for (i = 0; i < npages; i++) {
87 if (addr[i * page_size] != 0) {
88 fprintf(stderr, "Prefaulting found non-zero value in "
89 "page %ld: %d\n", i, (int)addr[i * page_size]);
94 int verify_mapping(char *vastart, long npages, uint64_t *expect)
100 for (va = vastart; npages > 0; va += page_size, npages--) {
101 for (i = 0; i < THREADS; i++) {
102 if (*(uint64_t*)(va + page_offs[i]) != expect[i]) {
103 printf("ERROR: thread %d, "
104 "offset %08llx, %08llx != %08llx\n", i,
105 (unsigned long long) (va + page_offs[i] - vastart),
106 (unsigned long long) *(uint64_t*)(va + page_offs[i]),
107 (unsigned long long) expect[i]);
115 void *pt_page_marker(void *args)
118 char *va = (char *)a[1];
119 long npages = (long)a[2];
121 long pgoff = page_offs[(long)a[3]];
122 uint64_t tid = get_id();
125 if (prefault && use_fork)
126 prefault_mapping(va, npages);
129 for (i = 0; i < npages; i++)
130 *(uint64_t *)(va + pgoff + i * page_size) = tid;
132 if (use_private && use_fork) {
133 uint64_t expect[THREADS] = {};
135 expect[(long)a[3]] = tid;
136 errors = verify_mapping(va, npages, expect);
139 return (void *)errors;
140 } /* pt_page_marker() */
142 void *pt_write_marker(void *args)
146 long npages = (long)a[2];
147 long pgoff = page_offs[(long)a[3]];
148 uint64_t tid = get_id();
152 for (i = 0; i < npages; i++)
153 pwrite(fd, &tid, sizeof(tid), i * page_size + pgoff);
158 int test_this(int fd, loff_t sz)
162 void *targs[THREADS][4];
163 pthread_t t[THREADS];
164 uint64_t tid[THREADS];
168 npages = sz / page_size;
169 printf("INFO: sz = %llu\n", (unsigned long long)sz);
172 vastart = mmap(NULL, sz, PROT_READ | PROT_WRITE,
173 use_private ? MAP_PRIVATE : MAP_SHARED, fd, 0);
174 if (MAP_FAILED == vastart) {
179 if (prefault && !use_fork)
180 prefault_mapping(vastart, npages);
182 /* prepare the thread args */
183 for (i = 0; i < THREADS; i++) {
184 targs[i][0] = (void *)(long)fd;
185 targs[i][1] = vastart;
186 targs[i][2] = (void *)npages;
187 targs[i][3] = (void *)(long)i;
190 for (i = 0; i < THREADS; i++) {
192 /* start two threads */
193 if (pthread_create(&t[i], NULL,
194 use_wr[i] ? pt_write_marker : pt_page_marker,
196 perror("pthread_create");
199 tid[i] = (uint64_t)t[i];
200 printf("INFO: thread %d created\n", i);
204 * Flush stdout before fork, otherwise some lines get
205 * duplicated... ?!?!?
213 for (j = 0; j < i; j++)
214 waitpid(tid[j], NULL, 0);
221 ret = pt_write_marker(&targs[i]);
223 ret = pt_page_marker(&targs[i]);
227 printf("INFO: process %d created\n", i);
231 /* wait for them to finish */
232 for (i = 0; i < THREADS; i++) {
236 pthread_join(t[i], &status);
242 waitpid(tid[i], &status, 0);
243 if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
248 /* check markers on each page */
249 /* For private mappings & fork we should see no writes happen */
250 if (use_private && use_fork)
251 for (i = 0; i < THREADS; i++)
253 errcnt = verify_mapping(vastart, npages, tid);
257 /* Check that no writes propagated into original file */
258 for (i = 0; i < THREADS; i++)
260 vastart = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);
261 if (vastart == MAP_FAILED) {
265 errcnt += verify_mapping(vastart, npages, tid);
269 printf("INFO: %d error(s) detected\n", errcnt);
275 int main(int argc, char **argv)
287 page_size = getpagesize();
288 step = page_size / THREADS;
289 page_offs[0] = step / 2;
290 for (i = 1; i < THREADS; i++)
291 page_offs[i] = page_offs[i-1] + step;
293 while ((opt = getopt(argc, argv, "fwrFp")) > 0) {
300 /* use writes instead of mmap for one thread */
304 /* prefault mmapped area by reading it */
308 /* create processes instead of threads */
312 /* Use private mappings for testing */
316 fprintf(stderr, "ERROR: Unknown option character.\n");
321 if (optind != argc - 2) {
322 fprintf(stderr, "ERROR: usage: holetest [-fwrFp] "
323 "FILENAME FILESIZEinMB\n");
326 if (use_private && use_wr[0]) {
327 fprintf(stderr, "ERROR: Combinations of writes and private"
328 "mappings not supported.\n");
333 sz = strtol(argv[optind + 1], &endch, 10);
334 if (*endch || sz < 1) {
335 fprintf(stderr, "ERROR: bad FILESIZEinMB\n");
341 * we're going to run our test in several different ways:
343 * 1. explictly zero-filled
344 * 2. posix_fallocated
350 * explicitly zero-filled
352 printf("\nINFO: zero-filled test...\n");
354 /* create the file */
355 fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644);
361 /* truncate it to size */
362 if (ftruncate(fd, sz)) {
363 perror("ftruncate()");
367 /* explicitly zero-fill */
369 char* va = mmap(NULL, sz, PROT_READ | PROT_WRITE,
371 if (MAP_FAILED == va) {
380 errcnt = test_this(fd, sz);
383 if (stoponerror && errcnt > 0)
396 printf("\nINFO: posix_fallocate test...\n");
398 /* create the file */
399 fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644);
405 /* fill it to size */
406 if (posix_fallocate(fd, 0, sz)) {
407 perror("posix_fallocate()");
412 errcnt = test_this(fd, sz);
415 if (stoponerror && errcnt > 0)
427 printf("\nINFO: ftruncate test...\n");
429 /* create the file */
430 fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644);
436 /* truncate it to size */
437 if (ftruncate(fd, sz)) {
438 perror("ftruncate()");
443 errcnt = test_this(fd, sz);
446 if (stoponerror && errcnt > 0)