Commit 62515b5f authored by Peter Xu's avatar Peter Xu Committed by Andrew Morton

selftests/mm: move uffd minor test to unit test

This moves the minor test to the new unit test.

Rewrite the content check with char* opeartions to avoid fiddling with
my_bcmp().

Drop global vars test_uffdio_minor and test_collapse, just assume test them
always in common code for now.

OTOH make this single test into five tests:

  - minor test on [shmem, hugetlb] with wp=false
  - minor test on [shmem, hugetlb] with wp=true
  - minor test + collapse on shmem only

One thing to mention that we used to test COLLAPSE+WP but that doesn't
sound right at all.  It's possible it's silently broken but unnoticed
because COLLAPSE is not part of the default test suite.

Make the MADV_COLLAPSE test fail-able (by skip it when failing), because
it's not guaranteed to success anyway.

Drop a bunch of useless code after the move, because the unit test always
use aligned num of pages and has nothing to do with n_cpus.

Link: https://lkml.kernel.org/r/20230412164357.328779-1-peterx@redhat.comSigned-off-by: default avatarPeter Xu <peterx@redhat.com>
Cc: Zach O'Keefe <zokeefe@google.com>
Cc: Axel Rasmussen <axelrasmussen@google.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Dmitry Safonov <0x7f454c46@gmail.com>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: Mike Rapoport (IBM) <rppt@kernel.org>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent 8bda424f
......@@ -13,8 +13,8 @@ volatile bool test_uffdio_copy_eexist = true;
unsigned long nr_cpus, nr_pages, nr_pages_per_cpu, page_size;
char *area_src, *area_src_alias, *area_dst, *area_dst_alias, *area_remap;
int uffd = -1, uffd_flags, finished, *pipefd, test_type;
bool map_shared, test_collapse, test_dev_userfaultfd;
bool test_uffdio_wp = true, test_uffdio_minor = false;
bool map_shared, test_dev_userfaultfd;
bool test_uffdio_wp = true;
unsigned long long *count_verify;
uffd_test_ops_t *uffd_test_ops;
......@@ -128,15 +128,14 @@ static int shmem_allocate_area(void **alloc_area, bool is_src)
char *p = NULL, *p_alias = NULL;
int mem_fd = uffd_mem_fd_create(bytes * 2, false);
if (test_collapse) {
p = BASE_PMD_ADDR;
if (!is_src)
/* src map + alias + interleaved hpages */
p += 2 * (bytes + hpage_size);
p_alias = p;
p_alias += bytes;
p_alias += hpage_size; /* Prevent src/dst VMA merge */
}
/* TODO: clean this up. Use a static addr is ugly */
p = BASE_PMD_ADDR;
if (!is_src)
/* src map + alias + interleaved hpages */
p += 2 * (bytes + hpage_size);
p_alias = p;
p_alias += bytes;
p_alias += hpage_size; /* Prevent src/dst VMA merge */
*alloc_area = mmap(p, bytes, PROT_READ | PROT_WRITE, MAP_SHARED,
mem_fd, offset);
......@@ -144,7 +143,7 @@ static int shmem_allocate_area(void **alloc_area, bool is_src)
*alloc_area = NULL;
return -errno;
}
if (test_collapse && *alloc_area != p)
if (*alloc_area != p)
err("mmap of memfd failed at %p", p);
area_alias = mmap(p_alias, bytes, PROT_READ | PROT_WRITE, MAP_SHARED,
......@@ -154,7 +153,7 @@ static int shmem_allocate_area(void **alloc_area, bool is_src)
*alloc_area = NULL;
return -errno;
}
if (test_collapse && area_alias != p_alias)
if (area_alias != p_alias)
err("mmap of anonymous memory failed at %p", p_alias);
if (is_src)
......
......@@ -90,8 +90,8 @@ typedef struct uffd_test_ops uffd_test_ops_t;
extern unsigned long nr_cpus, nr_pages, nr_pages_per_cpu, page_size;
extern char *area_src, *area_src_alias, *area_dst, *area_dst_alias, *area_remap;
extern int uffd, uffd_flags, finished, *pipefd, test_type;
extern bool map_shared, test_collapse, test_dev_userfaultfd;
extern bool test_uffdio_wp, test_uffdio_minor;
extern bool map_shared, test_dev_userfaultfd;
extern bool test_uffdio_wp;
extern unsigned long long *count_verify;
extern volatile bool test_uffdio_copy_eexist;
......
......@@ -52,8 +52,6 @@ pthread_attr_t attr;
#define swap(a, b) \
do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)
#define factor_of_2(x) ((x) ^ ((x) & ((x) - 1)))
const char *examples =
"# Run anonymous memory test on 100MiB region with 99999 bounces:\n"
"./userfaultfd anon 100 99999\n\n"
......@@ -79,8 +77,6 @@ static void usage(void)
"Supported mods:\n");
fprintf(stderr, "\tsyscall - Use userfaultfd(2) (default)\n");
fprintf(stderr, "\tdev - Use /dev/userfaultfd instead of userfaultfd(2)\n");
fprintf(stderr, "\tcollapse - Test MADV_COLLAPSE of UFFDIO_REGISTER_MODE_MINOR\n"
"memory\n");
fprintf(stderr, "\nExample test mod usage:\n");
fprintf(stderr, "# Run anonymous memory test with /dev/userfaultfd:\n");
fprintf(stderr, "./userfaultfd anon:dev 100 99999\n\n");
......@@ -584,92 +580,6 @@ static int userfaultfd_sig_test(void)
return userfaults != 0;
}
void check_memory_contents(char *p)
{
unsigned long i;
uint8_t expected_byte;
void *expected_page;
if (posix_memalign(&expected_page, page_size, page_size))
err("out of memory");
for (i = 0; i < nr_pages; ++i) {
expected_byte = ~((uint8_t)(i % ((uint8_t)-1)));
memset(expected_page, expected_byte, page_size);
if (my_bcmp(expected_page, p + (i * page_size), page_size))
err("unexpected page contents after minor fault");
}
free(expected_page);
}
static int userfaultfd_minor_test(void)
{
unsigned long p;
pthread_t uffd_mon;
char c;
struct uffd_args args = { 0 };
if (!test_uffdio_minor)
return 0;
printf("testing minor faults: ");
fflush(stdout);
uffd_test_ctx_init(uffd_minor_feature());
if (uffd_register(uffd, area_dst_alias, nr_pages * page_size,
false, test_uffdio_wp, true))
err("register failure");
/*
* After registering with UFFD, populate the non-UFFD-registered side of
* the shared mapping. This should *not* trigger any UFFD minor faults.
*/
for (p = 0; p < nr_pages; ++p) {
memset(area_dst + (p * page_size), p % ((uint8_t)-1),
page_size);
}
args.apply_wp = test_uffdio_wp;
if (pthread_create(&uffd_mon, &attr, uffd_poll_thread, &args))
err("uffd_poll_thread create");
/*
* Read each of the pages back using the UFFD-registered mapping. We
* expect that the first time we touch a page, it will result in a minor
* fault. uffd_poll_thread will resolve the fault by bit-flipping the
* page's contents, and then issuing a CONTINUE ioctl.
*/
check_memory_contents(area_dst_alias);
if (write(pipefd[1], &c, sizeof(c)) != sizeof(c))
err("pipe write");
if (pthread_join(uffd_mon, NULL))
return 1;
uffd_stats_report(&args, 1);
if (test_collapse) {
printf("testing collapse of uffd memory into PMD-mapped THPs:");
if (madvise(area_dst_alias, nr_pages * page_size,
MADV_COLLAPSE))
err("madvise(MADV_COLLAPSE)");
uffd_test_ops->check_pmd_mapping(area_dst,
nr_pages * page_size /
read_pmd_pagesize());
/*
* This won't cause uffd-fault - it purely just makes sure there
* was no corruption.
*/
check_memory_contents(area_dst_alias);
printf(" done.\n");
}
return args.missing_faults != 0 || args.minor_faults != nr_pages;
}
static int userfaultfd_stress(void)
{
void *area;
......@@ -782,7 +692,7 @@ static int userfaultfd_stress(void)
}
return userfaultfd_zeropage_test() || userfaultfd_sig_test()
|| userfaultfd_events_test() || userfaultfd_minor_test();
|| userfaultfd_events_test();
}
static void set_test_type(const char *type)
......@@ -797,13 +707,10 @@ static void set_test_type(const char *type)
map_shared = true;
test_type = TEST_HUGETLB;
uffd_test_ops = &hugetlb_uffd_test_ops;
/* Minor faults require shared hugetlb; only enable here. */
test_uffdio_minor = true;
} else if (!strcmp(type, "shmem")) {
map_shared = true;
test_type = TEST_SHMEM;
uffd_test_ops = &shmem_uffd_test_ops;
test_uffdio_minor = true;
}
}
......@@ -821,8 +728,6 @@ static void parse_test_type_arg(const char *raw_type)
test_dev_userfaultfd = true;
else if (!strcmp(token, "syscall"))
test_dev_userfaultfd = false;
else if (!strcmp(token, "collapse"))
test_collapse = true;
else
err("unrecognized test mod '%s'", token);
}
......@@ -830,9 +735,6 @@ static void parse_test_type_arg(const char *raw_type)
if (!test_type)
err("failed to parse test type argument: '%s'", raw_type);
if (test_collapse && test_type != TEST_SHMEM)
err("Unsupported test: %s", raw_type);
if (test_type == TEST_HUGETLB)
page_size = default_huge_page_size();
else
......@@ -854,8 +756,6 @@ static void parse_test_type_arg(const char *raw_type)
test_uffdio_wp = test_uffdio_wp &&
(features & UFFD_FEATURE_PAGEFAULT_FLAG_WP);
test_uffdio_minor = test_uffdio_minor &&
(features & uffd_minor_feature());
close(uffd);
uffd = -1;
......@@ -872,7 +772,6 @@ static void sigalrm(int sig)
int main(int argc, char **argv)
{
size_t bytes;
size_t hpage_size = read_pmd_pagesize();
if (argc < 4)
usage();
......@@ -884,36 +783,8 @@ int main(int argc, char **argv)
parse_test_type_arg(argv[1]);
bytes = atol(argv[2]) * 1024 * 1024;
if (test_collapse && bytes & (hpage_size - 1))
err("MiB must be multiple of %lu if :collapse mod set",
hpage_size >> 20);
nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
if (test_collapse) {
/* nr_cpus must divide (bytes / page_size), otherwise,
* area allocations of (nr_pages * paze_size) won't be a
* multiple of hpage_size, even if bytes is a multiple of
* hpage_size.
*
* This means that nr_cpus must divide (N * (2 << (H-P))
* where:
* bytes = hpage_size * N
* hpage_size = 2 << H
* page_size = 2 << P
*
* And we want to chose nr_cpus to be the largest value
* satisfying this constraint, not larger than the number
* of online CPUs. Unfortunately, prime factorization of
* N and nr_cpus may be arbitrary, so have to search for it.
* Instead, just use the highest power of 2 dividing both
* nr_cpus and (bytes / page_size).
*/
int x = factor_of_2(nr_cpus);
int y = factor_of_2(bytes / page_size);
nr_cpus = x < y ? x : y;
}
nr_pages_per_cpu = bytes / page_size / nr_cpus;
if (!nr_pages_per_cpu) {
_err("invalid MiB");
......
......@@ -329,6 +329,103 @@ static void uffd_pagemap_test(void)
uffd_test_pass();
}
static void check_memory_contents(char *p)
{
unsigned long i, j;
uint8_t expected_byte;
for (i = 0; i < nr_pages; ++i) {
expected_byte = ~((uint8_t)(i % ((uint8_t)-1)));
for (j = 0; j < page_size; j++) {
uint8_t v = *(uint8_t *)(p + (i * page_size) + j);
if (v != expected_byte)
err("unexpected page contents");
}
}
}
static void uffd_minor_test_common(bool test_collapse, bool test_wp)
{
unsigned long p;
pthread_t uffd_mon;
char c;
struct uffd_args args = { 0 };
/*
* NOTE: MADV_COLLAPSE is not yet compatible with WP, so testing
* both do not make much sense.
*/
assert(!(test_collapse && test_wp));
if (uffd_register(uffd, area_dst_alias, nr_pages * page_size,
/* NOTE! MADV_COLLAPSE may not work with uffd-wp */
false, test_wp, true))
err("register failure");
/*
* After registering with UFFD, populate the non-UFFD-registered side of
* the shared mapping. This should *not* trigger any UFFD minor faults.
*/
for (p = 0; p < nr_pages; ++p)
memset(area_dst + (p * page_size), p % ((uint8_t)-1),
page_size);
args.apply_wp = test_wp;
if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args))
err("uffd_poll_thread create");
/*
* Read each of the pages back using the UFFD-registered mapping. We
* expect that the first time we touch a page, it will result in a minor
* fault. uffd_poll_thread will resolve the fault by bit-flipping the
* page's contents, and then issuing a CONTINUE ioctl.
*/
check_memory_contents(area_dst_alias);
if (write(pipefd[1], &c, sizeof(c)) != sizeof(c))
err("pipe write");
if (pthread_join(uffd_mon, NULL))
err("join() failed");
if (test_collapse) {
if (madvise(area_dst_alias, nr_pages * page_size,
MADV_COLLAPSE)) {
/* It's fine to fail for this one... */
uffd_test_skip("MADV_COLLAPSE failed");
return;
}
uffd_test_ops->check_pmd_mapping(area_dst,
nr_pages * page_size /
read_pmd_pagesize());
/*
* This won't cause uffd-fault - it purely just makes sure there
* was no corruption.
*/
check_memory_contents(area_dst_alias);
}
if (args.missing_faults != 0 || args.minor_faults != nr_pages)
uffd_test_fail("stats check error");
else
uffd_test_pass();
}
void uffd_minor_test(void)
{
uffd_minor_test_common(false, false);
}
void uffd_minor_wp_test(void)
{
uffd_minor_test_common(false, true);
}
void uffd_minor_collapse_test(void)
{
uffd_minor_test_common(true, false);
}
uffd_test_case_t uffd_tests[] = {
{
.name = "pagemap",
......@@ -343,6 +440,29 @@ uffd_test_case_t uffd_tests[] = {
.uffd_feature_required =
UFFD_FEATURE_PAGEFAULT_FLAG_WP | UFFD_FEATURE_WP_UNPOPULATED,
},
{
.name = "minor",
.uffd_fn = uffd_minor_test,
.mem_targets = MEM_SHMEM | MEM_HUGETLB,
.uffd_feature_required =
UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM,
},
{
.name = "minor-wp",
.uffd_fn = uffd_minor_wp_test,
.mem_targets = MEM_SHMEM | MEM_HUGETLB,
.uffd_feature_required =
UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM |
UFFD_FEATURE_PAGEFAULT_FLAG_WP,
},
{
.name = "minor-collapse",
.uffd_fn = uffd_minor_collapse_test,
/* MADV_COLLAPSE only works with shmem */
.mem_targets = MEM_SHMEM,
/* We can't test MADV_COLLAPSE, so try our luck */
.uffd_feature_required = UFFD_FEATURE_MINOR_SHMEM,
},
};
int main(int argc, char *argv[])
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment