/* Arguments to be passed over to each uffd unit test */ struct uffd_test_args {
mem_type_t *mem_type;
}; typedefstruct uffd_test_args uffd_test_args_t;
if (use_dev)
uffd = uffd_open_dev(UFFD_FLAGS); else
uffd = uffd_open_sys(UFFD_FLAGS); if (uffd < 0) {
uffd_test_skip("cannot open userfaultfd handle"); return0;
}
/* Test wrong UFFD_API */
uffdio_api.api = 0xab;
uffdio_api.features = 0; if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) {
uffd_test_fail("UFFDIO_API should fail with wrong api but didn't"); goto out;
}
/* Test wrong feature bit */
uffdio_api.api = UFFD_API;
uffdio_api.features = BIT_ULL(63); if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) {
uffd_test_fail("UFFDIO_API should fail with wrong feature but didn't"); goto out;
}
/* Test normal UFFDIO_API */
uffdio_api.api = UFFD_API;
uffdio_api.features = 0; if (ioctl(uffd, UFFDIO_API, &uffdio_api)) {
uffd_test_fail("UFFDIO_API should succeed but failed"); goto out;
}
/* Test double requests of UFFDIO_API with a random feature set */
uffdio_api.features = BIT_ULL(0); if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) {
uffd_test_fail("UFFDIO_API should reject initialized uffd"); goto out;
}
uffd_test_pass();
out:
close(uffd); /* We have a valid uffd handle */ return1;
}
/* Ensure we have at least 2 pages */
nr_pages = MAX(UFFD_TEST_MEM_SIZE, page_size * 2) / page_size; /* TODO: remove this global var.. it's so ugly */
nr_parallel = 1;
/* Initialize test arguments */
args->mem_type = mem_type;
staticint pagemap_open(void)
{ int fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0)
err("open pagemap");
return fd;
}
/* This macro let __LINE__ works in err() */ #define pagemap_check_wp(value, wp) do { \ if (!!(value & PM_UFFD_WP) != wp) \
err("pagemap uffd-wp bit error: 0x%"PRIx64, value); \
} while (0)
typedefstruct { int parent_uffd, child_uffd;
} fork_event_args;
args->gup_fd = open("/sys/kernel/debug/gup_test", O_RDWR); if (args->gup_fd < 0) return -errno;
if (ioctl(args->gup_fd, PIN_LONGTERM_TEST_START, &test)) { /* Even if gup_test existed, can be an old gup_test / kernel */
close(args->gup_fd); return -errno;
}
args->pinned = true; return0;
}
staticvoid unpin_pages(pin_args *args)
{ if (!args->pinned)
err("unpin without pin first"); if (ioctl(args->gup_fd, PIN_LONGTERM_TEST_STOP))
err("PIN_LONGTERM_TEST_STOP");
close(args->gup_fd);
args->pinned = false;
}
/* Prepare a thread to resolve EVENT_FORK */ if (with_event) {
ready_for_fork = false; if (pthread_create(&thread, NULL, fork_event_consumer, &args))
err("pthread_create()"); while (!ready_for_fork)
; /* Wait for the poll_thread to start executing before forking */
}
child = fork(); if (!child) { /* Open the pagemap fd of the child itself */
pin_args args = {};
fd = pagemap_open();
if (test_pin && pin_pages(&args, area_dst, page_size)) /* *Normallywhenreachherewehavepinnedin *previoustests,soshouldn'tfailanymore
*/
err("pin page failed in child");
/* Test applying pte marker to anon unpopulated */
wp_range(uffd, (uint64_t)area_dst, page_size, true);
value = pagemap_get_entry(pagemap_fd, area_dst);
pagemap_check_wp(value, true);
/* Test unprotect on anon pte marker */
wp_range(uffd, (uint64_t)area_dst, page_size, false);
value = pagemap_get_entry(pagemap_fd, area_dst);
pagemap_check_wp(value, false);
/* Test zap on anon marker */
wp_range(uffd, (uint64_t)area_dst, page_size, true); if (madvise(area_dst, page_size, MADV_DONTNEED))
err("madvise(MADV_DONTNEED) failed");
value = pagemap_get_entry(pagemap_fd, area_dst);
pagemap_check_wp(value, false);
/* Test fault in after marker removed */
*area_dst = 1;
value = pagemap_get_entry(pagemap_fd, area_dst);
pagemap_check_wp(value, false); /* Drop it to make pte none again */ if (madvise(area_dst, page_size, MADV_DONTNEED))
err("madvise(MADV_DONTNEED) failed");
/* Test read-zero-page upon pte marker */
wp_range(uffd, (uint64_t)area_dst, page_size, true);
*(volatilechar *)area_dst; /* Drop it to make pte none again */ if (madvise(area_dst, page_size, MADV_DONTNEED))
err("madvise(MADV_DONTNEED) failed");
uffd_test_pass();
}
staticvoid uffd_wp_fork_test_common(uffd_test_args_t *args, bool with_event)
{ int pagemap_fd;
uint64_t value;
/* Uffd-wp should persist even swapped out */
value = pagemap_get_entry(pagemap_fd, area_dst);
pagemap_check_wp(value, true); if (pagemap_test_fork(uffd, with_event, false)) {
uffd_test_fail("Detected %s uffd-wp bit in child in zapped pte",
with_event ? "missing" : "stall"); goto out;
}
/* Unprotect; this tests swap pte modifications */
wp_range(uffd, (uint64_t)area_dst, page_size, false);
value = pagemap_get_entry(pagemap_fd, area_dst);
pagemap_check_wp(value, false);
/* Fault in the page from disk */
*area_dst = 2;
value = pagemap_get_entry(pagemap_fd, area_dst);
pagemap_check_wp(value, false);
uffd_test_pass();
out: if (uffd_unregister(uffd, area_dst, nr_pages * page_size))
err("unregister failed");
close(pagemap_fd);
}
if (pagemap_test_fork(uffd, with_event, false)) {
uffd_test_fail("Detected %s uffd-wp bit in early CoW of fork()",
with_event ? "missing" : "stall");
unpin_pages(&pin_args); goto out;
}
unpin_pages(&pin_args);
/* *2.Firstfork(),thenpin(inthechild,wheretest_pin==true). *ThistestsCOR,aka,pageunsharingonprivatememories.
*/ if (pagemap_test_fork(uffd, with_event, true)) {
uffd_test_fail("Detected %s uffd-wp bit when RO pin",
with_event ? "missing" : "stall"); goto out;
}
uffd_test_pass();
out: if (uffd_unregister(uffd, area_dst, page_size))
err("register failed");
close(pagemap_fd);
}
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");
/* *AfterregisteringwithUFFD,populatethenon-UFFD-registeredsideof *thesharedmapping.Thisshould*not*triggeranyUFFDminorfaults.
*/ 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");
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;
}
for (nr = 0; nr < nr_pages; nr++) for (i = 0; i < page_size; i++) if (*(area_dst + nr * page_size + i) != 0)
err("page %lu offset %lu is not zero", nr, i);
args.apply_wp = wp; if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args))
err("uffd_poll_thread create");
while (!ready_for_fork)
; /* Wait for the poll_thread to start executing before forking */
pid = fork(); if (pid < 0)
err("fork");
if (!pid) exit(faulting_process(0, wp));
waitpid(pid, &err, 0); if (err)
err("faulting process failed"); if (write(pipefd[1], &c, sizeof(c)) != sizeof(c))
err("pipe write"); if (pthread_join(uffd_mon, NULL))
err("pthread_join()");
if (args.missing_faults != nr_pages)
uffd_test_fail("Fault counts wrong"); else
uffd_test_pass();
}
has_zeropage = uffd_register_detect_zeropage(uffd, area_dst, page_size); if (area_dst_alias) /* Ignore the retval; we already have it */
uffd_register_detect_zeropage(uffd, area_dst_alias, page_size);
if (do_uffdio_zeropage(uffd, has_zeropage)) for (i = 0; i < page_size; i++) if (area_dst[i] != 0)
err("data non-zero at offset %d\n", i);
if (uffd_unregister(uffd, area_dst, page_size))
err("unregister");
if (area_dst_alias && uffd_unregister(uffd, area_dst_alias, page_size))
err("unregister");
/* Prevent source pages from being mapped more than once */ if (madvise(area_src, nr_pages * page_size, MADV_DONTFORK))
err("madvise(MADV_DONTFORK) failure");
/* *ReadeachofthepagesbackusingtheUFFD-registeredmapping.We *expectthatthefirsttimewetouchapage,itwillresultinamissing *fault.uffd_poll_threadwillresolvethefaultbymovingsource *pagetodestination.
*/ for (nr = 0; nr < step_count * step_size; nr += step_size) { unsignedlong i;
/* Check area_src content */ for (i = 0; i < step_size; i++) {
count = *area_count(area_src, nr + i); if (count != count_verify[src_offs + nr + i])
err("nr %lu source memory invalid %llu %llu\n",
nr + i, count, count_verify[src_offs + nr + i]);
}
/* Faulting into area_dst should move the page or the huge page */ for (i = 0; i < step_size; i++) {
count = *area_count(area_dst, nr + i); if (count != count_verify[dst_offs + nr + i])
err("nr %lu memory corruption %llu %llu\n",
nr, count, count_verify[dst_offs + nr + i]);
}
/* Re-check area_src content which should be empty */ for (i = 0; i < step_size; i++) {
count = *area_count(area_src, nr + i); if (count != 0)
err("nr %lu move failed %llu %llu\n",
nr, count, count_verify[src_offs + nr + i]);
}
} if (chunk_size > page_size) {
area_src = orig_area_src;
area_dst = orig_area_dst;
}
if (write(pipefd[1], &c, sizeof(c)) != sizeof(c))
err("pipe write"); if (pthread_join(uffd_mon, NULL))
err("join() failed");
staticbool
uffdio_verify_results(constchar *name, int ret, int error, long result)
{ /* *Shouldalwaysreturn-1witherrno=EAGAIN,withcorresponding *resultfieldupdatedinioctl()argstobe-EAGAINtoo *(e.g.copy.copyfieldforUFFDIO_COPY).
*/ if (ret != -1) {
uffd_test_fail("%s should have returned -1", name); returnfalse;
}
if (error != EAGAIN) {
uffd_test_fail("%s should have errno==EAGAIN", name); returnfalse;
}
if (result != -EAGAIN) {
uffd_test_fail("%s should have been updated for -EAGAIN",
name); returnfalse;
}
/* Unfortunately, it's only fetch-able from the thread itself.. */
assert(*pid == 0);
*pid = syscall(SYS_gettid);
/* Inject an event, this will hang solid until the event read */
ret = madvise(area_dst, page_size, MADV_REMOVE); if (ret)
err("madvise(MADV_REMOVE) failed");
/* Create a thread to generate the racy event */
ret = pthread_create(&tid, NULL, uffd_mmap_changing_thread, &pid); if (ret)
err("pthread_create() failed");
/* *Waituntilthethreadsetupthepid.Usevolatiletomakesure *itreadsfromRAMnotregs.
*/ while (!(volatile pid_t)pid)
sleep_short();
/* Wait until the thread hangs at REMOVE event */
thread_state_until(pid, THR_STATE_UNINTERRUPTIBLE);
if (!uffdio_mmap_changing_test_copy(uffd)) return;
if (!uffdio_mmap_changing_test_zeropage(uffd)) return;
if (!uffdio_mmap_changing_test_move(uffd)) return;
if (!uffdio_mmap_changing_test_poison(uffd)) return;
if (!uffdio_mmap_changing_test_continue(uffd)) return;
staticint prevent_hugepages(constchar **errmsg)
{ /* This should be done before source area is populated */ if (madvise(area_src, nr_pages * page_size, MADV_NOHUGEPAGE)) { /* Ignore only if CONFIG_TRANSPARENT_HUGEPAGE=n */ if (errno != EINVAL) { if (errmsg)
*errmsg = "madvise(MADV_NOHUGEPAGE) failed"; return -errno;
}
} return0;
}
staticint request_hugepages(constchar **errmsg)
{ /* This should be done before source area is populated */ if (madvise(area_src, nr_pages * page_size, MADV_HUGEPAGE)) { if (errmsg) {
*errmsg = (errno == EINVAL) ? "CONFIG_TRANSPARENT_HUGEPAGE is not set" : "madvise(MADV_HUGEPAGE) failed";
} return -errno;
} return0;
}
/* UFFDIO_REGISTER should succeed, then check ioctls returned */ if (miss)
expected |= BIT_ULL(_UFFDIO_COPY); if (wp)
expected |= BIT_ULL(_UFFDIO_WRITEPROTECT); if (minor)
expected |= BIT_ULL(_UFFDIO_CONTINUE);
staticvoid usage(constchar *prog)
{
printf("usage: %s [-f TESTNAME]\n", prog);
puts("");
puts(" -f: test name to filter (e.g., event)");
puts(" -h: show the help msg");
puts(" -l: list tests only");
puts(""); exit(KSFT_FAIL);
}
int main(int argc, char *argv[])
{ int n_tests = sizeof(uffd_tests) / sizeof(uffd_test_case_t); int n_mems = sizeof(mem_types) / sizeof(mem_type_t); constchar *test_filter = NULL; bool list_only = false;
uffd_test_case_t *test;
mem_type_t *mem_type;
uffd_test_args_t args; constchar *errmsg; int has_uffd, opt; int i, j;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.