copy.mode = 0;
 
                r = ioctl(uffd, UFFDIO_COPY, ©);
-               if (r == -1) {
-                       pr_info("Failed UFFDIO_COPY in 0x%lx from thread %d with errno: %d\n",
+               /*
+                * With multiple vCPU threads fault on a single page and there are
+                * multiple readers for the UFFD, at least one of the UFFDIO_COPYs
+                * will fail with EEXIST: handle that case without signaling an
+                * error.
+                *
+                * Note that this also suppress any EEXISTs occurring from,
+                * e.g., the first UFFDIO_COPY/CONTINUEs on a page. That never
+                * happens here, but a realistic VMM might potentially maintain
+                * some external state to correctly surface EEXISTs to userspace
+                * (or prevent duplicate COPY/CONTINUEs in the first place).
+                */
+               if (r == -1 && errno != EEXIST) {
+                       pr_info("Failed UFFDIO_COPY in 0x%lx from thread %d, errno = %d\n",
                                addr, tid, errno);
                        return r;
                }
                cont.range.len = demand_paging_size;
 
                r = ioctl(uffd, UFFDIO_CONTINUE, &cont);
-               if (r == -1) {
-                       pr_info("Failed UFFDIO_CONTINUE in 0x%lx from thread %d with errno: %d\n",
+               /*
+                * With multiple vCPU threads fault on a single page and there are
+                * multiple readers for the UFFD, at least one of the UFFDIO_COPYs
+                * will fail with EEXIST: handle that case without signaling an
+                * error.
+                *
+                * Note that this also suppress any EEXISTs occurring from,
+                * e.g., the first UFFDIO_COPY/CONTINUEs on a page. That never
+                * happens here, but a realistic VMM might potentially maintain
+                * some external state to correctly surface EEXISTs to userspace
+                * (or prevent duplicate COPY/CONTINUEs in the first place).
+                */
+               if (r == -1 && errno != EEXIST) {
+                       pr_info("Failed UFFDIO_CONTINUE in 0x%lx, thread %d, errno = %d\n",
                                addr, tid, errno);
                        return r;
                }
 
 struct test_params {
        int uffd_mode;
+       bool single_uffd;
        useconds_t uffd_delay;
+       int readers_per_uffd;
        enum vm_mem_backing_src_type src_type;
        bool partition_vcpu_memory_access;
 };
        struct memstress_vcpu_args *vcpu_args;
        struct test_params *p = arg;
        struct uffd_desc **uffd_descs = NULL;
+       uint64_t uffd_region_size;
        struct timespec start;
        struct timespec ts_diff;
        double vcpu_paging_rate;
        struct kvm_vm *vm;
-       int i;
+       int i, num_uffds = 0;
 
        vm = memstress_create_vm(mode, nr_vcpus, guest_percpu_mem_size, 1,
                                 p->src_type, p->partition_vcpu_memory_access);
        memset(guest_data_prototype, 0xAB, demand_paging_size);
 
        if (p->uffd_mode == UFFDIO_REGISTER_MODE_MINOR) {
-               for (i = 0; i < nr_vcpus; i++) {
+               num_uffds = p->single_uffd ? 1 : nr_vcpus;
+               for (i = 0; i < num_uffds; i++) {
                        vcpu_args = &memstress_args.vcpu_args[i];
                        prefault_mem(addr_gpa2alias(vm, vcpu_args->gpa),
                                     vcpu_args->pages * memstress_args.guest_page_size);
        }
 
        if (p->uffd_mode) {
-               uffd_descs = malloc(nr_vcpus * sizeof(struct uffd_desc *));
+               num_uffds = p->single_uffd ? 1 : nr_vcpus;
+               uffd_region_size = nr_vcpus * guest_percpu_mem_size / num_uffds;
+
+               uffd_descs = malloc(num_uffds * sizeof(struct uffd_desc *));
                TEST_ASSERT(uffd_descs, "Memory allocation failed");
-               for (i = 0; i < nr_vcpus; i++) {
+               for (i = 0; i < num_uffds; i++) {
+                       struct memstress_vcpu_args *vcpu_args;
                        void *vcpu_hva;
 
                        vcpu_args = &memstress_args.vcpu_args[i];
                         */
                        uffd_descs[i] = uffd_setup_demand_paging(
                                p->uffd_mode, p->uffd_delay, vcpu_hva,
-                               vcpu_args->pages * memstress_args.guest_page_size,
+                               uffd_region_size,
+                               p->readers_per_uffd,
                                &handle_uffd_page_request);
                }
        }
 
        if (p->uffd_mode) {
                /* Tell the user fault fd handler threads to quit */
-               for (i = 0; i < nr_vcpus; i++)
+               for (i = 0; i < num_uffds; i++)
                        uffd_stop_demand_paging(uffd_descs[i]);
        }
 
 static void help(char *name)
 {
        puts("");
-       printf("usage: %s [-h] [-m vm_mode] [-u uffd_mode] [-d uffd_delay_usec]\n"
-              "          [-b memory] [-s type] [-v vcpus] [-c cpu_list] [-o]\n", name);
+       printf("usage: %s [-h] [-m vm_mode] [-u uffd_mode] [-a]\n"
+                  "          [-d uffd_delay_usec] [-r readers_per_uffd] [-b memory]\n"
+                  "          [-s type] [-v vcpus] [-c cpu_list] [-o]\n", name);
        guest_modes_help();
        printf(" -u: use userfaultfd to handle vCPU page faults. Mode is a\n"
               "     UFFD registration mode: 'MISSING' or 'MINOR'.\n");
        kvm_print_vcpu_pinning_help();
+       printf(" -a: Use a single userfaultfd for all of guest memory, instead of\n"
+              "     creating one for each region paged by a unique vCPU\n"
+              "     Set implicitly with -o, and no effect without -u.\n");
        printf(" -d: add a delay in usec to the User Fault\n"
               "     FD handler to simulate demand paging\n"
               "     overheads. Ignored without -u.\n");
+       printf(" -r: Set the number of reader threads per uffd.\n");
        printf(" -b: specify the size of the memory region which should be\n"
               "     demand paged by each vCPU. e.g. 10M or 3G.\n"
               "     Default: 1G\n");
        struct test_params p = {
                .src_type = DEFAULT_VM_MEM_SRC,
                .partition_vcpu_memory_access = true,
+               .readers_per_uffd = 1,
+               .single_uffd = false,
        };
        int opt;
 
        guest_modes_append_default();
 
-       while ((opt = getopt(argc, argv, "hm:u:d:b:s:v:c:o")) != -1) {
+       while ((opt = getopt(argc, argv, "ahom:u:d:b:s:v:c:r:")) != -1) {
                switch (opt) {
                case 'm':
                        guest_modes_cmdline(optarg);
                                p.uffd_mode = UFFDIO_REGISTER_MODE_MINOR;
                        TEST_ASSERT(p.uffd_mode, "UFFD mode must be 'MISSING' or 'MINOR'.");
                        break;
+               case 'a':
+                       p.single_uffd = true;
+                       break;
                case 'd':
                        p.uffd_delay = strtoul(optarg, NULL, 0);
                        TEST_ASSERT(p.uffd_delay >= 0, "A negative UFFD delay is not supported.");
                        break;
                case 'o':
                        p.partition_vcpu_memory_access = false;
+                       p.single_uffd = true;
+                       break;
+               case 'r':
+                       p.readers_per_uffd = atoi(optarg);
+                       TEST_ASSERT(p.readers_per_uffd >= 1,
+                                   "Invalid number of readers per uffd %d: must be >=1",
+                                   p.readers_per_uffd);
                        break;
                case 'h':
                default:
 
 
 static void *uffd_handler_thread_fn(void *arg)
 {
-       struct uffd_desc *uffd_desc = (struct uffd_desc *)arg;
-       int uffd = uffd_desc->uffd;
-       int pipefd = uffd_desc->pipefds[0];
-       useconds_t delay = uffd_desc->delay;
+       struct uffd_reader_args *reader_args = (struct uffd_reader_args *)arg;
+       int uffd = reader_args->uffd;
        int64_t pages = 0;
        struct timespec start;
        struct timespec ts_diff;
 
                pollfd[0].fd = uffd;
                pollfd[0].events = POLLIN;
-               pollfd[1].fd = pipefd;
+               pollfd[1].fd = reader_args->pipe;
                pollfd[1].events = POLLIN;
 
                r = poll(pollfd, 2, -1);
                if (!(msg.event & UFFD_EVENT_PAGEFAULT))
                        continue;
 
-               if (delay)
-                       usleep(delay);
-               r = uffd_desc->handler(uffd_desc->uffd_mode, uffd, &msg);
+               if (reader_args->delay)
+                       usleep(reader_args->delay);
+               r = reader_args->handler(reader_args->uffd_mode, uffd, &msg);
                if (r < 0)
                        return NULL;
                pages++;
 
 struct uffd_desc *uffd_setup_demand_paging(int uffd_mode, useconds_t delay,
                                           void *hva, uint64_t len,
+                                          uint64_t num_readers,
                                           uffd_handler_t handler)
 {
        struct uffd_desc *uffd_desc;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;
        uint64_t expected_ioctls = ((uint64_t) 1) << _UFFDIO_COPY;
-       int ret;
+       int ret, i;
 
        PER_PAGE_DEBUG("Userfaultfd %s mode, faults resolved with %s\n",
                       is_minor ? "MINOR" : "MISSING",
                       is_minor ? "UFFDIO_CONINUE" : "UFFDIO_COPY");
 
        uffd_desc = malloc(sizeof(struct uffd_desc));
-       TEST_ASSERT(uffd_desc, "malloc failed");
+       TEST_ASSERT(uffd_desc, "Failed to malloc uffd descriptor");
+
+       uffd_desc->pipefds = calloc(sizeof(int), num_readers);
+       TEST_ASSERT(uffd_desc->pipefds, "Failed to alloc pipes");
+
+       uffd_desc->readers = calloc(sizeof(pthread_t), num_readers);
+       TEST_ASSERT(uffd_desc->readers, "Failed to alloc reader threads");
+
+       uffd_desc->reader_args = calloc(sizeof(struct uffd_reader_args), num_readers);
+       TEST_ASSERT(uffd_desc->reader_args, "Failed to alloc reader_args");
+
+       uffd_desc->num_readers = num_readers;
 
        /* In order to get minor faults, prefault via the alias. */
        if (is_minor)
        TEST_ASSERT((uffdio_register.ioctls & expected_ioctls) ==
                    expected_ioctls, "missing userfaultfd ioctls");
 
-       ret = pipe2(uffd_desc->pipefds, O_CLOEXEC | O_NONBLOCK);
-       TEST_ASSERT(!ret, "Failed to set up pipefd");
-
-       uffd_desc->uffd_mode = uffd_mode;
        uffd_desc->uffd = uffd;
-       uffd_desc->delay = delay;
-       uffd_desc->handler = handler;
-       pthread_create(&uffd_desc->thread, NULL, uffd_handler_thread_fn,
-                      uffd_desc);
+       for (i = 0; i < uffd_desc->num_readers; ++i) {
+               int pipes[2];
+
+               ret = pipe2((int *) &pipes, O_CLOEXEC | O_NONBLOCK);
+               TEST_ASSERT(!ret, "Failed to set up pipefd %i for uffd_desc %p",
+                           i, uffd_desc);
 
-       PER_VCPU_DEBUG("Created uffd thread for HVA range [%p, %p)\n",
-                      hva, hva + len);
+               uffd_desc->pipefds[i] = pipes[1];
+
+               uffd_desc->reader_args[i].uffd_mode = uffd_mode;
+               uffd_desc->reader_args[i].uffd = uffd;
+               uffd_desc->reader_args[i].delay = delay;
+               uffd_desc->reader_args[i].handler = handler;
+               uffd_desc->reader_args[i].pipe = pipes[0];
+
+               pthread_create(&uffd_desc->readers[i], NULL, uffd_handler_thread_fn,
+                              &uffd_desc->reader_args[i]);
+
+               PER_VCPU_DEBUG("Created uffd thread %i for HVA range [%p, %p)\n",
+                              i, hva, hva + len);
+       }
 
        return uffd_desc;
 }
 void uffd_stop_demand_paging(struct uffd_desc *uffd)
 {
        char c = 0;
-       int ret;
+       int i;
 
-       ret = write(uffd->pipefds[1], &c, 1);
-       TEST_ASSERT(ret == 1, "Unable to write to pipefd");
+       for (i = 0; i < uffd->num_readers; ++i)
+               TEST_ASSERT(write(uffd->pipefds[i], &c, 1) == 1,
+                           "Unable to write to pipefd %i for uffd_desc %p", i, uffd);
 
-       ret = pthread_join(uffd->thread, NULL);
-       TEST_ASSERT(ret == 0, "Pthread_join failed.");
+       for (i = 0; i < uffd->num_readers; ++i)
+               TEST_ASSERT(!pthread_join(uffd->readers[i], NULL),
+                           "Pthread_join failed on reader %i for uffd_desc %p", i, uffd);
 
        close(uffd->uffd);
 
-       close(uffd->pipefds[1]);
-       close(uffd->pipefds[0]);
+       for (i = 0; i < uffd->num_readers; ++i) {
+               close(uffd->pipefds[i]);
+               close(uffd->reader_args[i].pipe);
+       }
 
+       free(uffd->pipefds);
+       free(uffd->readers);
+       free(uffd->reader_args);
        free(uffd);
 }