| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * holetest -- test simultaneous page faults on hole-backed pages | |
| * Copyright (C) 2015 Hewlett Packard Enterprise Development LP | |
| */ | |
| /* | |
| * holetest | |
| * | |
| * gcc -Wall -pthread -o holetest holetest.c | |
| * | |
| * This test tool exercises page faults on hole-y portions of an mmapped | |
| * file. The file is created, sized using various methods, mmapped, and | |
| * then two threads race to write a marker to different offsets within | |
| * each mapped page. Once the threads have finished marking each page, | |
| * the pages are checked for the presence of the markers. | |
| * | |
| * The file is sized four different ways: explicitly zero-filled by the | |
| * test, posix_fallocate(), fallocate(), and ftruncate(). The explicit | |
| * zero-fill does not really test simultaneous page faults on hole-backed | |
| * pages, but rather serves as control of sorts. | |
| * | |
| * Usage: | |
| * | |
| * holetest [-f] FILENAME FILESIZEinMB | |
| * | |
| * Where: | |
| * | |
| * FILENAME is the name of a non-existent test file to create | |
| * | |
| * FILESIZEinMB is the desired size of the test file in MiB | |
| * | |
| * If the test is successful, FILENAME will be unlinked. By default, | |
| * if the test detects an error in the page markers, then the test exits | |
| * immediately and FILENAME is left. If -f is given, then the test | |
| * continues after a marker error and FILENAME is unlinked, but will | |
| * still exit with a non-0 status. | |
| */ | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include <inttypes.h> | |
| #include <sys/mman.h> | |
| #include <fcntl.h> | |
| #include <unistd.h> | |
| #include <pthread.h> | |
| #include <string.h> | |
| #include <getopt.h> | |
| #include <sys/wait.h> | |
| #define THREADS 2 | |
| long page_size; | |
| long page_offs[THREADS]; | |
| int use_wr[THREADS]; | |
| int prefault = 0; | |
| int use_fork = 0; | |
| int use_private = 0; | |
| uint64_t get_id(void) | |
| { | |
| if (!use_fork) | |
| return (uint64_t) pthread_self(); | |
| return getpid(); | |
| } | |
| void prefault_mapping(char *addr, long npages) | |
| { | |
| long i; | |
| for (i = 0; i < npages; i++) { | |
| if (addr[i * page_size] != 0) { | |
| fprintf(stderr, "Prefaulting found non-zero value in " | |
| "page %ld: %d\n", i, (int)addr[i * page_size]); | |
| } | |
| } | |
| } | |
| int verify_mapping(char *vastart, long npages, uint64_t *expect) | |
| { | |
| int errcnt = 0; | |
| int i; | |
| char *va; | |
| for (va = vastart; npages > 0; va += page_size, npages--) { | |
| for (i = 0; i < THREADS; i++) { | |
| if (*(uint64_t*)(va + page_offs[i]) != expect[i]) { | |
| printf("ERROR: thread %d, " | |
| "offset %08llx, %08llx != %08llx\n", i, | |
| (unsigned long long) (va + page_offs[i] - vastart), | |
| (unsigned long long) *(uint64_t*)(va + page_offs[i]), | |
| (unsigned long long) expect[i]); | |
| errcnt++; | |
| } | |
| } | |
| } | |
| return errcnt; | |
| } | |
| void *pt_page_marker(void *args) | |
| { | |
| void **a = args; | |
| char *va = (char *)a[1]; | |
| long npages = (long)a[2]; | |
| long i; | |
| long pgoff = page_offs[(long)a[3]]; | |
| uint64_t tid = get_id(); | |
| long errors = 0; | |
| if (prefault && use_fork) | |
| prefault_mapping(va, npages); | |
| /* mark pages */ | |
| for (i = 0; i < npages; i++) | |
| *(uint64_t *)(va + pgoff + i * page_size) = tid; | |
| if (use_private && use_fork) { | |
| uint64_t expect[THREADS] = {}; | |
| expect[(long)a[3]] = tid; | |
| errors = verify_mapping(va, npages, expect); | |
| } | |
| return (void *)errors; | |
| } /* pt_page_marker() */ | |
| void *pt_write_marker(void *args) | |
| { | |
| void **a = args; | |
| int fd = (long)a[0]; | |
| long npages = (long)a[2]; | |
| long pgoff = page_offs[(long)a[3]]; | |
| uint64_t tid = get_id(); | |
| long i; | |
| /* mark pages */ | |
| for (i = 0; i < npages; i++) | |
| pwrite(fd, &tid, sizeof(tid), i * page_size + pgoff); | |
| return NULL; | |
| } | |
| int test_this(int fd, loff_t sz) | |
| { | |
| long npages; | |
| char *vastart; | |
| void *targs[THREADS][4]; | |
| pthread_t t[THREADS]; | |
| uint64_t tid[THREADS]; | |
| int errcnt = 0; | |
| int i; | |
| npages = sz / page_size; | |
| printf("INFO: sz = %llu\n", (unsigned long long)sz); | |
| /* mmap it */ | |
| vastart = mmap(NULL, sz, PROT_READ | PROT_WRITE, | |
| use_private ? MAP_PRIVATE : MAP_SHARED, fd, 0); | |
| if (MAP_FAILED == vastart) { | |
| perror("mmap()"); | |
| exit(20); | |
| } | |
| if (prefault && !use_fork) | |
| prefault_mapping(vastart, npages); | |
| /* prepare the thread args */ | |
| for (i = 0; i < THREADS; i++) { | |
| targs[i][0] = (void *)(long)fd; | |
| targs[i][1] = vastart; | |
| targs[i][2] = (void *)npages; | |
| targs[i][3] = (void *)(long)i; | |
| } | |
| for (i = 0; i < THREADS; i++) { | |
| if (!use_fork) { | |
| /* start two threads */ | |
| if (pthread_create(&t[i], NULL, | |
| use_wr[i] ? pt_write_marker : pt_page_marker, | |
| &targs[i])) { | |
| perror("pthread_create"); | |
| exit(21); | |
| } | |
| tid[i] = (uint64_t)t[i]; | |
| printf("INFO: thread %d created\n", i); | |
| } else { | |
| pid_t pid; | |
| /* | |
| * Flush stdout before fork, otherwise some lines get | |
| * duplicated... ?!?!? | |
| */ | |
| fflush(stdout); | |
| pid = fork(); | |
| if (pid < 0) { | |
| int j; | |
| perror("fork"); | |
| for (j = 0; j < i; j++) | |
| waitpid(tid[j], NULL, 0); | |
| exit(21); | |
| } else if (!pid) { | |
| /* Child? */ | |
| void *ret; | |
| if (use_wr[i]) | |
| ret = pt_write_marker(&targs[i]); | |
| else | |
| ret = pt_page_marker(&targs[i]); | |
| exit(ret ? 1 : 0); | |
| } | |
| tid[i] = pid; | |
| printf("INFO: process %d created\n", i); | |
| } | |
| } | |
| /* wait for them to finish */ | |
| for (i = 0; i < THREADS; i++) { | |
| if (!use_fork) { | |
| void *status; | |
| pthread_join(t[i], &status); | |
| if (status) | |
| errcnt++; | |
| } else { | |
| int status; | |
| waitpid(tid[i], &status, 0); | |
| if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) | |
| errcnt++; | |
| } | |
| } | |
| /* check markers on each page */ | |
| /* For private mappings & fork we should see no writes happen */ | |
| if (use_private && use_fork) | |
| for (i = 0; i < THREADS; i++) | |
| tid[i] = 0; | |
| errcnt = verify_mapping(vastart, npages, tid); | |
| munmap(vastart, sz); | |
| if (use_private) { | |
| /* Check that no writes propagated into original file */ | |
| for (i = 0; i < THREADS; i++) | |
| tid[i] = 0; | |
| vastart = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0); | |
| if (vastart == MAP_FAILED) { | |
| perror("mmap()"); | |
| exit(20); | |
| } | |
| errcnt += verify_mapping(vastart, npages, tid); | |
| munmap(vastart, sz); | |
| } | |
| printf("INFO: %d error(s) detected\n", errcnt); | |
| return errcnt; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| int stoponerror = 1; | |
| char *path; | |
| loff_t sz; | |
| int fd; | |
| int errcnt; | |
| int toterr = 0; | |
| int i, step; | |
| char *endch; | |
| int opt; | |
| page_size = getpagesize(); | |
| step = page_size / THREADS; | |
| page_offs[0] = step / 2; | |
| for (i = 1; i < THREADS; i++) | |
| page_offs[i] = page_offs[i-1] + step; | |
| while ((opt = getopt(argc, argv, "fwrFp")) > 0) { | |
| switch (opt) { | |
| case 'f': | |
| /* ignore errors */ | |
| stoponerror = 0; | |
| break; | |
| case 'w': | |
| /* use writes instead of mmap for one thread */ | |
| use_wr[0] = 1; | |
| break; | |
| case 'r': | |
| /* prefault mmapped area by reading it */ | |
| prefault = 1; | |
| break; | |
| case 'F': | |
| /* create processes instead of threads */ | |
| use_fork = 1; | |
| break; | |
| case 'p': | |
| /* Use private mappings for testing */ | |
| use_private = 1; | |
| break; | |
| default: | |
| fprintf(stderr, "ERROR: Unknown option character.\n"); | |
| exit(1); | |
| } | |
| } | |
| if (optind != argc - 2) { | |
| fprintf(stderr, "ERROR: usage: holetest [-fwrFp] " | |
| "FILENAME FILESIZEinMB\n"); | |
| exit(1); | |
| } | |
| if (use_private && use_wr[0]) { | |
| fprintf(stderr, "ERROR: Combinations of writes and private" | |
| "mappings not supported.\n"); | |
| exit(1); | |
| } | |
| path = argv[optind]; | |
| sz = strtol(argv[optind + 1], &endch, 10); | |
| if (*endch || sz < 1) { | |
| fprintf(stderr, "ERROR: bad FILESIZEinMB\n"); | |
| exit(1); | |
| } | |
| sz <<= 20; | |
| /* | |
| * we're going to run our test in several different ways: | |
| * | |
| * 1. explictly zero-filled | |
| * 2. posix_fallocated | |
| * 3. ftruncated | |
| */ | |
| /* | |
| * explicitly zero-filled | |
| */ | |
| printf("\nINFO: zero-filled test...\n"); | |
| /* create the file */ | |
| fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644); | |
| if (fd < 0) { | |
| perror(path); | |
| exit(2); | |
| } | |
| /* truncate it to size */ | |
| if (ftruncate(fd, sz)) { | |
| perror("ftruncate()"); | |
| exit(3); | |
| } | |
| /* explicitly zero-fill */ | |
| { | |
| char* va = mmap(NULL, sz, PROT_READ | PROT_WRITE, | |
| MAP_SHARED, fd, 0); | |
| if (MAP_FAILED == va) { | |
| perror("mmap()"); | |
| exit(4); | |
| } | |
| memset(va, 0, sz); | |
| munmap(va, sz); | |
| } | |
| /* test it */ | |
| errcnt = test_this(fd, sz); | |
| toterr += errcnt; | |
| close(fd); | |
| if (stoponerror && errcnt > 0) | |
| exit(5); | |
| /* cleanup */ | |
| if (unlink(path)) { | |
| perror("unlink()"); | |
| exit(6); | |
| } | |
| /* | |
| * posix_fallocated | |
| */ | |
| printf("\nINFO: posix_fallocate test...\n"); | |
| /* create the file */ | |
| fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644); | |
| if (fd < 0) { | |
| perror(path); | |
| exit(7); | |
| } | |
| /* fill it to size */ | |
| if (posix_fallocate(fd, 0, sz)) { | |
| perror("posix_fallocate()"); | |
| exit(8); | |
| } | |
| /* test it */ | |
| errcnt = test_this(fd, sz); | |
| toterr += errcnt; | |
| close(fd); | |
| if (stoponerror && errcnt > 0) | |
| exit(9); | |
| /* cleanup */ | |
| if (unlink(path)) { | |
| perror("unlink()"); | |
| exit(10); | |
| } | |
| /* | |
| * ftruncated | |
| */ | |
| printf("\nINFO: ftruncate test...\n"); | |
| /* create the file */ | |
| fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644); | |
| if (fd < 0) { | |
| perror(path); | |
| exit(15); | |
| } | |
| /* truncate it to size */ | |
| if (ftruncate(fd, sz)) { | |
| perror("ftruncate()"); | |
| exit(16); | |
| } | |
| /* test it */ | |
| errcnt = test_this(fd, sz); | |
| toterr += errcnt; | |
| close(fd); | |
| if (stoponerror && errcnt > 0) | |
| exit(17); | |
| /* cleanup */ | |
| if (unlink(path)) { | |
| perror("unlink()"); | |
| exit(18); | |
| } | |
| /* done */ | |
| if (toterr > 0) | |
| exit(19); | |
| return 0; | |
| } |