blob: a8fded0f236dd15cda4abcdaadf58313606a3901 [file] [log] [blame]
// 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;
}