selftests/vm: anon_cow: add liburing test cases
io_uring provides a simple mechanism to test long-term, R/W GUP pins
-- via fixed buffers -- and can be used to verify that GUP pins stay
in sync with the pages in the page table even if a page would
temporarily get mapped R/O or concurrent fork() could accidentially
end up sharing pinned pages with the child.
Note that this essentially re-introduces local_config support that was
removed recently in commit 6f83d6c74e
("Kselftests: remove support of
libhugetlbfs from kselftests").
[david@redhat.com: s/size_t/ssize_t/ on `cur', `total'.]
Link: https://lkml.kernel.org/r/445fe1ae-9e22-0d1d-4d09-272231d2f84a@redhat.com
Link: https://lkml.kernel.org/r/20220927110120.106906-6-david@redhat.com
Signed-off-by: David Hildenbrand <david@redhat.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Christoph von Recklinghausen <crecklin@redhat.com>
Cc: Don Dutile <ddutile@redhat.com>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Cc: John Hubbard <jhubbard@nvidia.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Nadav Amit <namit@vmware.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
parent
7dad331be7
commit
e487ebbd12
|
@ -1,7 +1,9 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Makefile for vm selftests
|
||||
|
||||
LOCAL_HDRS += $(top_srcdir)/mm/gup_test.h
|
||||
LOCAL_HDRS += $(selfdir)/vm/local_config.h $(top_srcdir)/mm/gup_test.h
|
||||
|
||||
include local_config.mk
|
||||
|
||||
uname_M := $(shell uname -m 2>/dev/null || echo not)
|
||||
MACHINE ?= $(shell echo $(uname_M) | sed -e 's/aarch64.*/arm64/' -e 's/ppc64.*/ppc64/')
|
||||
|
@ -152,8 +154,25 @@ warn_32bit_failure:
|
|||
endif
|
||||
endif
|
||||
|
||||
# ANON_COW_EXTRA_LIBS may get set in local_config.mk, or it may be left empty.
|
||||
$(OUTPUT)/anon_cow: LDLIBS += $(ANON_COW_EXTRA_LIBS)
|
||||
|
||||
$(OUTPUT)/mlock-random-test $(OUTPUT)/memfd_secret: LDLIBS += -lcap
|
||||
|
||||
$(OUTPUT)/ksm_tests: LDLIBS += -lnuma
|
||||
|
||||
$(OUTPUT)/migration: LDLIBS += -lnuma
|
||||
|
||||
local_config.mk local_config.h: check_config.sh
|
||||
/bin/sh ./check_config.sh $(CC)
|
||||
|
||||
EXTRA_CLEAN += local_config.mk local_config.h
|
||||
|
||||
ifeq ($(ANON_COW_EXTRA_LIBS),)
|
||||
all: warn_missing_liburing
|
||||
|
||||
warn_missing_liburing:
|
||||
@echo ; \
|
||||
echo "Warning: missing liburing support. Some COW tests will be skipped." ; \
|
||||
echo
|
||||
endif
|
||||
|
|
|
@ -19,6 +19,11 @@
|
|||
#include <sys/mman.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "local_config.h"
|
||||
#ifdef LOCAL_CONFIG_HAVE_LIBURING
|
||||
#include <liburing.h>
|
||||
#endif /* LOCAL_CONFIG_HAVE_LIBURING */
|
||||
|
||||
#include "../kselftest.h"
|
||||
#include "vm_util.h"
|
||||
|
||||
|
@ -334,6 +339,170 @@ static void test_vmsplice_after_fork(char *mem, size_t size)
|
|||
do_test_vmsplice_in_parent(mem, size, false);
|
||||
}
|
||||
|
||||
#ifdef LOCAL_CONFIG_HAVE_LIBURING
|
||||
static void do_test_iouring(char *mem, size_t size, bool use_fork)
|
||||
{
|
||||
struct comm_pipes comm_pipes;
|
||||
struct io_uring_cqe *cqe;
|
||||
struct io_uring_sqe *sqe;
|
||||
struct io_uring ring;
|
||||
ssize_t cur, total;
|
||||
struct iovec iov;
|
||||
char *buf, *tmp;
|
||||
int ret, fd;
|
||||
FILE *file;
|
||||
|
||||
ret = setup_comm_pipes(&comm_pipes);
|
||||
if (ret) {
|
||||
ksft_test_result_fail("pipe() failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
file = tmpfile();
|
||||
if (!file) {
|
||||
ksft_test_result_fail("tmpfile() failed\n");
|
||||
goto close_comm_pipes;
|
||||
}
|
||||
fd = fileno(file);
|
||||
assert(fd);
|
||||
|
||||
tmp = malloc(size);
|
||||
if (!tmp) {
|
||||
ksft_test_result_fail("malloc() failed\n");
|
||||
goto close_file;
|
||||
}
|
||||
|
||||
/* Skip on errors, as we might just lack kernel support. */
|
||||
ret = io_uring_queue_init(1, &ring, 0);
|
||||
if (ret < 0) {
|
||||
ksft_test_result_skip("io_uring_queue_init() failed\n");
|
||||
goto free_tmp;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register the range as a fixed buffer. This will FOLL_WRITE | FOLL_PIN
|
||||
* | FOLL_LONGTERM the range.
|
||||
*
|
||||
* Skip on errors, as we might just lack kernel support or might not
|
||||
* have sufficient MEMLOCK permissions.
|
||||
*/
|
||||
iov.iov_base = mem;
|
||||
iov.iov_len = size;
|
||||
ret = io_uring_register_buffers(&ring, &iov, 1);
|
||||
if (ret) {
|
||||
ksft_test_result_skip("io_uring_register_buffers() failed\n");
|
||||
goto queue_exit;
|
||||
}
|
||||
|
||||
if (use_fork) {
|
||||
/*
|
||||
* fork() and keep the child alive until we're done. Note that
|
||||
* we expect the pinned page to not get shared with the child.
|
||||
*/
|
||||
ret = fork();
|
||||
if (ret < 0) {
|
||||
ksft_test_result_fail("fork() failed\n");
|
||||
goto unregister_buffers;
|
||||
} else if (!ret) {
|
||||
write(comm_pipes.child_ready[1], "0", 1);
|
||||
while (read(comm_pipes.parent_ready[0], &buf, 1) != 1)
|
||||
;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
while (read(comm_pipes.child_ready[0], &buf, 1) != 1)
|
||||
;
|
||||
} else {
|
||||
/*
|
||||
* Map the page R/O into the page table. Enable softdirty
|
||||
* tracking to stop the page from getting mapped R/W immediately
|
||||
* again by mprotect() optimizations. Note that we don't have an
|
||||
* easy way to test if that worked (the pagemap does not export
|
||||
* if the page is mapped R/O vs. R/W).
|
||||
*/
|
||||
ret = mprotect(mem, size, PROT_READ);
|
||||
clear_softdirty();
|
||||
ret |= mprotect(mem, size, PROT_READ | PROT_WRITE);
|
||||
if (ret) {
|
||||
ksft_test_result_fail("mprotect() failed\n");
|
||||
goto unregister_buffers;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Modify the page and write page content as observed by the fixed
|
||||
* buffer pin to the file so we can verify it.
|
||||
*/
|
||||
memset(mem, 0xff, size);
|
||||
sqe = io_uring_get_sqe(&ring);
|
||||
if (!sqe) {
|
||||
ksft_test_result_fail("io_uring_get_sqe() failed\n");
|
||||
goto quit_child;
|
||||
}
|
||||
io_uring_prep_write_fixed(sqe, fd, mem, size, 0, 0);
|
||||
|
||||
ret = io_uring_submit(&ring);
|
||||
if (ret < 0) {
|
||||
ksft_test_result_fail("io_uring_submit() failed\n");
|
||||
goto quit_child;
|
||||
}
|
||||
|
||||
ret = io_uring_wait_cqe(&ring, &cqe);
|
||||
if (ret < 0) {
|
||||
ksft_test_result_fail("io_uring_wait_cqe() failed\n");
|
||||
goto quit_child;
|
||||
}
|
||||
|
||||
if (cqe->res != size) {
|
||||
ksft_test_result_fail("write_fixed failed\n");
|
||||
goto quit_child;
|
||||
}
|
||||
io_uring_cqe_seen(&ring, cqe);
|
||||
|
||||
/* Read back the file content to the temporary buffer. */
|
||||
total = 0;
|
||||
while (total < size) {
|
||||
cur = pread(fd, tmp + total, size - total, total);
|
||||
if (cur < 0) {
|
||||
ksft_test_result_fail("pread() failed\n");
|
||||
goto quit_child;
|
||||
}
|
||||
total += cur;
|
||||
}
|
||||
|
||||
/* Finally, check if we read what we expected. */
|
||||
ksft_test_result(!memcmp(mem, tmp, size),
|
||||
"Longterm R/W pin is reliable\n");
|
||||
|
||||
quit_child:
|
||||
if (use_fork) {
|
||||
write(comm_pipes.parent_ready[1], "0", 1);
|
||||
wait(&ret);
|
||||
}
|
||||
unregister_buffers:
|
||||
io_uring_unregister_buffers(&ring);
|
||||
queue_exit:
|
||||
io_uring_queue_exit(&ring);
|
||||
free_tmp:
|
||||
free(tmp);
|
||||
close_file:
|
||||
fclose(file);
|
||||
close_comm_pipes:
|
||||
close_comm_pipes(&comm_pipes);
|
||||
}
|
||||
|
||||
static void test_iouring_ro(char *mem, size_t size)
|
||||
{
|
||||
do_test_iouring(mem, size, false);
|
||||
}
|
||||
|
||||
static void test_iouring_fork(char *mem, size_t size)
|
||||
{
|
||||
do_test_iouring(mem, size, true);
|
||||
}
|
||||
|
||||
#endif /* LOCAL_CONFIG_HAVE_LIBURING */
|
||||
|
||||
typedef void (*test_fn)(char *mem, size_t size);
|
||||
|
||||
static void do_run_with_base_page(test_fn fn, bool swapout)
|
||||
|
@ -660,6 +829,27 @@ static const struct test_case test_cases[] = {
|
|||
"vmsplice() + unmap in parent after fork()",
|
||||
test_vmsplice_after_fork,
|
||||
},
|
||||
#ifdef LOCAL_CONFIG_HAVE_LIBURING
|
||||
/*
|
||||
* Take a R/W longterm pin and then map the page R/O into the page
|
||||
* table to trigger a write fault on next access. When modifying the
|
||||
* page, the page content must be visible via the pin.
|
||||
*/
|
||||
{
|
||||
"R/O-mapping a page registered as iouring fixed buffer",
|
||||
test_iouring_ro,
|
||||
},
|
||||
/*
|
||||
* Take a R/W longterm pin and then fork() a child. When modifying the
|
||||
* page, the page content must be visible via the pin. We expect the
|
||||
* pinned page to not get shared with the child.
|
||||
*/
|
||||
{
|
||||
"fork() with an iouring fixed buffer",
|
||||
test_iouring_fork,
|
||||
},
|
||||
|
||||
#endif /* LOCAL_CONFIG_HAVE_LIBURING */
|
||||
};
|
||||
|
||||
static void run_test_case(struct test_case const *test_case)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Probe for libraries and create header files to record the results. Both C
|
||||
# header files and Makefile include fragments are created.
|
||||
|
||||
OUTPUT_H_FILE=local_config.h
|
||||
OUTPUT_MKFILE=local_config.mk
|
||||
|
||||
tmpname=$(mktemp)
|
||||
tmpfile_c=${tmpname}.c
|
||||
tmpfile_o=${tmpname}.o
|
||||
|
||||
# liburing
|
||||
echo "#include <sys/types.h>" > $tmpfile_c
|
||||
echo "#include <liburing.h>" >> $tmpfile_c
|
||||
echo "int func(void) { return 0; }" >> $tmpfile_c
|
||||
|
||||
CC=${1:?"Usage: $0 <compiler> # example compiler: gcc"}
|
||||
$CC -c $tmpfile_c -o $tmpfile_o >/dev/null 2>&1
|
||||
|
||||
if [ -f $tmpfile_o ]; then
|
||||
echo "#define LOCAL_CONFIG_HAVE_LIBURING 1" > $OUTPUT_H_FILE
|
||||
echo "ANON_COW_EXTRA_LIBS = -luring" > $OUTPUT_MKFILE
|
||||
else
|
||||
echo "// No liburing support found" > $OUTPUT_H_FILE
|
||||
echo "# No liburing support found, so:" > $OUTPUT_MKFILE
|
||||
echo "ANON_COW_EXTRA_LIBS = " >> $OUTPUT_MKFILE
|
||||
fi
|
||||
|
||||
rm ${tmpname}.*
|
Loading…
Reference in New Issue