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

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.com


Signed-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
Loading
Loading
Loading
Loading
+12 −13
Original line number Diff line number Diff line
@@ -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,7 +128,7 @@ 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) {
	/* TODO: clean this up.  Use a static addr is ugly */
	p = BASE_PMD_ADDR;
	if (!is_src)
		/* src map + alias + interleaved hpages */
@@ -136,7 +136,6 @@ static int shmem_allocate_area(void **alloc_area, bool is_src)
	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)
+2 −2
Original line number Diff line number Diff line
@@ -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;

+1 −130
Original line number Diff line number Diff line
@@ -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");
+120 −0
Original line number Diff line number Diff line
@@ -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[])