233 lines
7.3 KiB
Diff
233 lines
7.3 KiB
Diff
|
|
From 6387912710bf3762635e5bd6ddb4be59f50d9b78 Mon Sep 17 00:00:00 2001
|
||
|
|
From: Edward Liaw <edliaw@google.com>
|
||
|
|
Date: Wed, 20 Apr 2022 00:29:06 +0000
|
||
|
|
Subject: [PATCH 16/24] userfaultfd
|
||
|
|
|
||
|
|
Fix ARM related issues in userfaultfd selftest
|
||
|
|
|
||
|
|
Following issues were observed while running userfaultfd selftest on ARM
|
||
|
|
devices. On x86_64 no issues were detected:
|
||
|
|
|
||
|
|
1) posix_memalign returned tagged pointer, which isn't handled well by
|
||
|
|
the kernel. So replace its use with mmap
|
||
|
|
2) pthread_create followed by fork caused deadlock in certain cases
|
||
|
|
wherein fork required some work to be completed by the created thread.
|
||
|
|
Used synchronization to ensure that created thread's start funtion has
|
||
|
|
started before invoking fork.
|
||
|
|
|
||
|
|
(cherry picked from commit 9b11d1b6ab483cb451b102a30d996dc180330161)
|
||
|
|
Bug: 160737021
|
||
|
|
Bug: 169683130
|
||
|
|
Test: atest vts_linux_kselftest_arm_64
|
||
|
|
|
||
|
|
Enable userfaultfd selftest
|
||
|
|
|
||
|
|
Now that the userfaultfd feature is enabled in the Android kernel, it
|
||
|
|
makes sense to have its selftest enabled in the test infra.
|
||
|
|
|
||
|
|
The test source code required the following changes:
|
||
|
|
1) Use UFFD_USER_MODE_ONLY as unprivileged processes are not allowed
|
||
|
|
userfaults from kernel space on Android
|
||
|
|
2) Use random/initstate instead of random_r/initstate_r as bionic
|
||
|
|
doesn't support the latter.
|
||
|
|
3) Since bionic doesn't have pthread_cancel() implemented, we
|
||
|
|
implemented the same functionality using pthread_kill() and longjmp().
|
||
|
|
|
||
|
|
(cherry picked from commit 599ed29be16f2612c1ca1bc2cab1b95e1ae0e6f7)
|
||
|
|
Bug: 160737021
|
||
|
|
Bug: 169683130
|
||
|
|
Test: treehugger
|
||
|
|
---
|
||
|
|
tools/testing/selftests/vm/userfaultfd.c | 74 ++++++++++++++++++++++--
|
||
|
|
1 file changed, 69 insertions(+), 5 deletions(-)
|
||
|
|
|
||
|
|
diff --git a/tools/testing/selftests/vm/userfaultfd.c b/tools/testing/selftests/vm/userfaultfd.c
|
||
|
|
index 3fc1d2ee29485..e7a79f120cbde 100644
|
||
|
|
--- a/tools/testing/selftests/vm/userfaultfd.c
|
||
|
|
+++ b/tools/testing/selftests/vm/userfaultfd.c
|
||
|
|
@@ -93,9 +93,11 @@ static char *huge_fd_off0;
|
||
|
|
static unsigned long long *count_verify;
|
||
|
|
static int uffd = -1;
|
||
|
|
static int uffd_flags, finished, *pipefd;
|
||
|
|
+static volatile bool ready_for_fork;
|
||
|
|
static char *area_src, *area_src_alias, *area_dst, *area_dst_alias;
|
||
|
|
static char *zeropage;
|
||
|
|
pthread_attr_t attr;
|
||
|
|
+pthread_key_t long_jmp_key;
|
||
|
|
|
||
|
|
/* Userfaultfd test statistics */
|
||
|
|
struct uffd_stats {
|
||
|
|
@@ -377,8 +379,13 @@ static void userfaultfd_open(uint64_t *features)
|
||
|
|
struct uffdio_api uffdio_api;
|
||
|
|
|
||
|
|
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY);
|
||
|
|
- if (uffd < 0)
|
||
|
|
- err("userfaultfd syscall not available in this kernel");
|
||
|
|
+ if (uffd < 0) {
|
||
|
|
+ if (errno == ENOSYS) {
|
||
|
|
+ printf("userfaultfd syscall not available in this kernel\n");
|
||
|
|
+ exit(KSFT_SKIP);
|
||
|
|
+ }
|
||
|
|
+ err("userfaultfd syscall failed with errno: %d\n", errno);
|
||
|
|
+ }
|
||
|
|
uffd_flags = fcntl(uffd, F_GETFD, NULL);
|
||
|
|
|
||
|
|
uffdio_api.api = UFFD_API;
|
||
|
|
@@ -720,6 +727,9 @@ static void *uffd_poll_thread(void *arg)
|
||
|
|
pollfd[1].fd = pipefd[cpu*2];
|
||
|
|
pollfd[1].events = POLLIN;
|
||
|
|
|
||
|
|
+ // Notify the main thread that it can now fork.
|
||
|
|
+ ready_for_fork = true;
|
||
|
|
+
|
||
|
|
for (;;) {
|
||
|
|
ret = poll(pollfd, 2, -1);
|
||
|
|
if (ret <= 0) {
|
||
|
|
@@ -766,15 +776,28 @@ static void *uffd_poll_thread(void *arg)
|
||
|
|
|
||
|
|
pthread_mutex_t uffd_read_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||
|
|
|
||
|
|
+static void sigusr1_handler(int signum, siginfo_t *siginfo, void *ptr)
|
||
|
|
+{
|
||
|
|
+ jmp_buf *env;
|
||
|
|
+ env = pthread_getspecific(long_jmp_key);
|
||
|
|
+ longjmp(*env, 1);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
static void *uffd_read_thread(void *arg)
|
||
|
|
{
|
||
|
|
struct uffd_stats *stats = (struct uffd_stats *)arg;
|
||
|
|
struct uffd_msg msg;
|
||
|
|
+ jmp_buf env;
|
||
|
|
+ int setjmp_ret;
|
||
|
|
+
|
||
|
|
+ pthread_setspecific(long_jmp_key, &env);
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&uffd_read_mutex);
|
||
|
|
- /* from here cancellation is ok */
|
||
|
|
|
||
|
|
- for (;;) {
|
||
|
|
+ // One first return setjmp return 0. On second (fake) return from
|
||
|
|
+ // longjmp() it returns the provided value, which will be 1 in our case.
|
||
|
|
+ setjmp_ret = setjmp(env);
|
||
|
|
+ while (!setjmp_ret) {
|
||
|
|
if (uffd_read_msg(uffd, &msg))
|
||
|
|
continue;
|
||
|
|
uffd_handle_page_fault(&msg, stats);
|
||
|
|
@@ -872,7 +895,7 @@ static int stress(struct uffd_stats *uffd_stats)
|
||
|
|
(void *)&uffd_stats[cpu]))
|
||
|
|
return 1;
|
||
|
|
} else {
|
||
|
|
- if (pthread_cancel(uffd_threads[cpu]))
|
||
|
|
+ if (pthread_kill(uffd_threads[cpu], SIGUSR1))
|
||
|
|
return 1;
|
||
|
|
if (pthread_join(uffd_threads[cpu], NULL))
|
||
|
|
return 1;
|
||
|
|
@@ -1112,6 +1135,10 @@ static int userfaultfd_events_test(void)
|
||
|
|
char c;
|
||
|
|
struct uffd_stats stats = { 0 };
|
||
|
|
|
||
|
|
+ // All the syscalls below up to pthread_create will ensure that this
|
||
|
|
+ // write is completed before, the uffd_thread sets it to true.
|
||
|
|
+ ready_for_fork = false;
|
||
|
|
+
|
||
|
|
printf("testing events (fork, remap, remove): ");
|
||
|
|
fflush(stdout);
|
||
|
|
|
||
|
|
@@ -1135,6 +1162,11 @@ static int userfaultfd_events_test(void)
|
||
|
|
if (pthread_create(&uffd_mon, &attr, uffd_poll_thread, &stats))
|
||
|
|
err("uffd_poll_thread create");
|
||
|
|
|
||
|
|
+ // Wait for the poll_thread to start executing before forking. This is
|
||
|
|
+ // required to avoid a deadlock, which can happen if poll_thread doesn't
|
||
|
|
+ // start getting executed by the time fork is invoked.
|
||
|
|
+ while (!ready_for_fork);
|
||
|
|
+
|
||
|
|
pid = fork();
|
||
|
|
if (pid < 0)
|
||
|
|
err("fork");
|
||
|
|
@@ -1165,6 +1197,10 @@ static int userfaultfd_sig_test(void)
|
||
|
|
char c;
|
||
|
|
struct uffd_stats stats = { 0 };
|
||
|
|
|
||
|
|
+ // All the syscalls below up to pthread_create will ensure that this
|
||
|
|
+ // write is completed before, the uffd_thread sets it to true.
|
||
|
|
+ ready_for_fork = false;
|
||
|
|
+
|
||
|
|
printf("testing signal delivery: ");
|
||
|
|
fflush(stdout);
|
||
|
|
|
||
|
|
@@ -1192,6 +1228,11 @@ static int userfaultfd_sig_test(void)
|
||
|
|
if (pthread_create(&uffd_mon, &attr, uffd_poll_thread, &stats))
|
||
|
|
err("uffd_poll_thread create");
|
||
|
|
|
||
|
|
+ // Wait for the poll_thread to start executing before forking. This is
|
||
|
|
+ // required to avoid a deadlock, which can happen if poll_thread doesn't
|
||
|
|
+ // start getting executed by the time fork is invoked.
|
||
|
|
+ while (!ready_for_fork);
|
||
|
|
+
|
||
|
|
pid = fork();
|
||
|
|
if (pid < 0)
|
||
|
|
err("fork");
|
||
|
|
@@ -1421,6 +1462,7 @@ static int userfaultfd_stress(void)
|
||
|
|
char *tmp_area;
|
||
|
|
unsigned long nr;
|
||
|
|
struct uffdio_register uffdio_register;
|
||
|
|
+ struct sigaction act;
|
||
|
|
struct uffd_stats uffd_stats[nr_cpus];
|
||
|
|
|
||
|
|
uffd_test_ctx_init(0);
|
||
|
|
@@ -1435,6 +1477,17 @@ static int userfaultfd_stress(void)
|
||
|
|
pthread_attr_init(&attr);
|
||
|
|
pthread_attr_setstacksize(&attr, 16*1024*1024);
|
||
|
|
|
||
|
|
+ // For handling thread termination of read thread in the absense of
|
||
|
|
+ // pthread_cancel().
|
||
|
|
+ pthread_key_create(&long_jmp_key, NULL);
|
||
|
|
+ memset(&act, 0, sizeof(act));
|
||
|
|
+ act.sa_sigaction = sigusr1_handler;
|
||
|
|
+ act.sa_flags = SA_SIGINFO;
|
||
|
|
+ if (sigaction(SIGUSR1, &act, 0)) {
|
||
|
|
+ perror("sigaction");
|
||
|
|
+ return 1;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
while (bounces--) {
|
||
|
|
printf("bounces: %d, mode:", bounces);
|
||
|
|
if (bounces & BOUNCE_RANDOM)
|
||
|
|
@@ -1555,6 +1608,8 @@ static int userfaultfd_stress(void)
|
||
|
|
userfaultfd_pagemap_test(page_size * 512);
|
||
|
|
}
|
||
|
|
|
||
|
|
+ pthread_key_delete(long_jmp_key);
|
||
|
|
+
|
||
|
|
return userfaultfd_zeropage_test() || userfaultfd_sig_test()
|
||
|
|
|| userfaultfd_events_test() || userfaultfd_minor_test();
|
||
|
|
}
|
||
|
|
@@ -1649,6 +1704,9 @@ static void sigalrm(int sig)
|
||
|
|
|
||
|
|
int main(int argc, char **argv)
|
||
|
|
{
|
||
|
|
+ char randstate[64];
|
||
|
|
+ unsigned int seed;
|
||
|
|
+
|
||
|
|
if (argc < 4)
|
||
|
|
usage();
|
||
|
|
|
||
|
|
@@ -1692,6 +1750,12 @@ int main(int argc, char **argv)
|
||
|
|
nr_pages * page_size * 2))
|
||
|
|
err("fallocate");
|
||
|
|
}
|
||
|
|
+
|
||
|
|
+ seed = (unsigned int) time(NULL);
|
||
|
|
+ bzero(&randstate, sizeof(randstate));
|
||
|
|
+ if (!initstate(seed, randstate, sizeof(randstate)))
|
||
|
|
+ fprintf(stderr, "srandom_r error\n"), exit(1);
|
||
|
|
+
|
||
|
|
printf("nr_pages: %lu, nr_pages_per_cpu: %lu\n",
|
||
|
|
nr_pages, nr_pages_per_cpu);
|
||
|
|
return userfaultfd_stress();
|
||
|
|
--
|
||
|
|
2.36.0.550.gb090851708-goog
|
||
|
|
|