Skip to content

Commit df4ec5a

Browse files
anlshsean-jc
authored andcommitted
KVM: selftests: Allow many vCPUs and reader threads per UFFD in demand paging test
At the moment, demand_paging_test does not support profiling/testing multiple vCPU threads concurrently faulting on a single uffd because (a) "-u" (run test in userfaultfd mode) creates a uffd for each vCPU's region, so that each uffd services a single vCPU thread. (b) "-u -o" (userfaultfd mode + overlapped vCPU memory accesses) simply doesn't work: the test tries to register the same memory to multiple uffds, causing an error. Add support for many vcpus per uffd by (1) Keeping "-u" behavior unchanged. (2) Making "-u -a" create a single uffd for all of guest memory. (3) Making "-u -o" implicitly pass "-a", solving the problem in (b). In cases (2) and (3) all vCPU threads fault on a single uffd. With potentially multiple vCPUs per UFFD, it makes sense to allow configuring the number of reader threads per UFFD as well: add the "-r" flag to do so. Signed-off-by: Anish Moorthy <[email protected]> Acked-by: James Houghton <[email protected]> Link: https://lore.kernel.org/r/[email protected] [sean: fix kernel style violations, use calloc() for arrays] Signed-off-by: Sean Christopherson <[email protected]>
1 parent 2ca76c1 commit df4ec5a

File tree

4 files changed

+132
-45
lines changed

4 files changed

+132
-45
lines changed

tools/testing/selftests/kvm/aarch64/page_fault_test.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,14 +375,14 @@ static void setup_uffd(struct kvm_vm *vm, struct test_params *p,
375375
*pt_uffd = uffd_setup_demand_paging(uffd_mode, 0,
376376
pt_args.hva,
377377
pt_args.paging_size,
378-
test->uffd_pt_handler);
378+
1, test->uffd_pt_handler);
379379

380380
*data_uffd = NULL;
381381
if (test->uffd_data_handler)
382382
*data_uffd = uffd_setup_demand_paging(uffd_mode, 0,
383383
data_args.hva,
384384
data_args.paging_size,
385-
test->uffd_data_handler);
385+
1, test->uffd_data_handler);
386386
}
387387

388388
static void free_uffd(struct test_desc *test, struct uffd_desc *pt_uffd,

tools/testing/selftests/kvm/demand_paging_test.c

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,20 @@ static int handle_uffd_page_request(int uffd_mode, int uffd,
7777
copy.mode = 0;
7878

7979
r = ioctl(uffd, UFFDIO_COPY, &copy);
80-
if (r == -1) {
81-
pr_info("Failed UFFDIO_COPY in 0x%lx from thread %d with errno: %d\n",
80+
/*
81+
* With multiple vCPU threads fault on a single page and there are
82+
* multiple readers for the UFFD, at least one of the UFFDIO_COPYs
83+
* will fail with EEXIST: handle that case without signaling an
84+
* error.
85+
*
86+
* Note that this also suppress any EEXISTs occurring from,
87+
* e.g., the first UFFDIO_COPY/CONTINUEs on a page. That never
88+
* happens here, but a realistic VMM might potentially maintain
89+
* some external state to correctly surface EEXISTs to userspace
90+
* (or prevent duplicate COPY/CONTINUEs in the first place).
91+
*/
92+
if (r == -1 && errno != EEXIST) {
93+
pr_info("Failed UFFDIO_COPY in 0x%lx from thread %d, errno = %d\n",
8294
addr, tid, errno);
8395
return r;
8496
}
@@ -89,8 +101,20 @@ static int handle_uffd_page_request(int uffd_mode, int uffd,
89101
cont.range.len = demand_paging_size;
90102

91103
r = ioctl(uffd, UFFDIO_CONTINUE, &cont);
92-
if (r == -1) {
93-
pr_info("Failed UFFDIO_CONTINUE in 0x%lx from thread %d with errno: %d\n",
104+
/*
105+
* With multiple vCPU threads fault on a single page and there are
106+
* multiple readers for the UFFD, at least one of the UFFDIO_COPYs
107+
* will fail with EEXIST: handle that case without signaling an
108+
* error.
109+
*
110+
* Note that this also suppress any EEXISTs occurring from,
111+
* e.g., the first UFFDIO_COPY/CONTINUEs on a page. That never
112+
* happens here, but a realistic VMM might potentially maintain
113+
* some external state to correctly surface EEXISTs to userspace
114+
* (or prevent duplicate COPY/CONTINUEs in the first place).
115+
*/
116+
if (r == -1 && errno != EEXIST) {
117+
pr_info("Failed UFFDIO_CONTINUE in 0x%lx, thread %d, errno = %d\n",
94118
addr, tid, errno);
95119
return r;
96120
}
@@ -110,7 +134,9 @@ static int handle_uffd_page_request(int uffd_mode, int uffd,
110134

111135
struct test_params {
112136
int uffd_mode;
137+
bool single_uffd;
113138
useconds_t uffd_delay;
139+
int readers_per_uffd;
114140
enum vm_mem_backing_src_type src_type;
115141
bool partition_vcpu_memory_access;
116142
};
@@ -131,11 +157,12 @@ static void run_test(enum vm_guest_mode mode, void *arg)
131157
struct memstress_vcpu_args *vcpu_args;
132158
struct test_params *p = arg;
133159
struct uffd_desc **uffd_descs = NULL;
160+
uint64_t uffd_region_size;
134161
struct timespec start;
135162
struct timespec ts_diff;
136163
double vcpu_paging_rate;
137164
struct kvm_vm *vm;
138-
int i;
165+
int i, num_uffds = 0;
139166

140167
vm = memstress_create_vm(mode, nr_vcpus, guest_percpu_mem_size, 1,
141168
p->src_type, p->partition_vcpu_memory_access);
@@ -148,17 +175,22 @@ static void run_test(enum vm_guest_mode mode, void *arg)
148175
memset(guest_data_prototype, 0xAB, demand_paging_size);
149176

150177
if (p->uffd_mode == UFFDIO_REGISTER_MODE_MINOR) {
151-
for (i = 0; i < nr_vcpus; i++) {
178+
num_uffds = p->single_uffd ? 1 : nr_vcpus;
179+
for (i = 0; i < num_uffds; i++) {
152180
vcpu_args = &memstress_args.vcpu_args[i];
153181
prefault_mem(addr_gpa2alias(vm, vcpu_args->gpa),
154182
vcpu_args->pages * memstress_args.guest_page_size);
155183
}
156184
}
157185

158186
if (p->uffd_mode) {
159-
uffd_descs = malloc(nr_vcpus * sizeof(struct uffd_desc *));
187+
num_uffds = p->single_uffd ? 1 : nr_vcpus;
188+
uffd_region_size = nr_vcpus * guest_percpu_mem_size / num_uffds;
189+
190+
uffd_descs = malloc(num_uffds * sizeof(struct uffd_desc *));
160191
TEST_ASSERT(uffd_descs, "Memory allocation failed");
161-
for (i = 0; i < nr_vcpus; i++) {
192+
for (i = 0; i < num_uffds; i++) {
193+
struct memstress_vcpu_args *vcpu_args;
162194
void *vcpu_hva;
163195

164196
vcpu_args = &memstress_args.vcpu_args[i];
@@ -171,7 +203,8 @@ static void run_test(enum vm_guest_mode mode, void *arg)
171203
*/
172204
uffd_descs[i] = uffd_setup_demand_paging(
173205
p->uffd_mode, p->uffd_delay, vcpu_hva,
174-
vcpu_args->pages * memstress_args.guest_page_size,
206+
uffd_region_size,
207+
p->readers_per_uffd,
175208
&handle_uffd_page_request);
176209
}
177210
}
@@ -188,7 +221,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
188221

189222
if (p->uffd_mode) {
190223
/* Tell the user fault fd handler threads to quit */
191-
for (i = 0; i < nr_vcpus; i++)
224+
for (i = 0; i < num_uffds; i++)
192225
uffd_stop_demand_paging(uffd_descs[i]);
193226
}
194227

@@ -212,15 +245,20 @@ static void run_test(enum vm_guest_mode mode, void *arg)
212245
static void help(char *name)
213246
{
214247
puts("");
215-
printf("usage: %s [-h] [-m vm_mode] [-u uffd_mode] [-d uffd_delay_usec]\n"
216-
" [-b memory] [-s type] [-v vcpus] [-c cpu_list] [-o]\n", name);
248+
printf("usage: %s [-h] [-m vm_mode] [-u uffd_mode] [-a]\n"
249+
" [-d uffd_delay_usec] [-r readers_per_uffd] [-b memory]\n"
250+
" [-s type] [-v vcpus] [-c cpu_list] [-o]\n", name);
217251
guest_modes_help();
218252
printf(" -u: use userfaultfd to handle vCPU page faults. Mode is a\n"
219253
" UFFD registration mode: 'MISSING' or 'MINOR'.\n");
220254
kvm_print_vcpu_pinning_help();
255+
printf(" -a: Use a single userfaultfd for all of guest memory, instead of\n"
256+
" creating one for each region paged by a unique vCPU\n"
257+
" Set implicitly with -o, and no effect without -u.\n");
221258
printf(" -d: add a delay in usec to the User Fault\n"
222259
" FD handler to simulate demand paging\n"
223260
" overheads. Ignored without -u.\n");
261+
printf(" -r: Set the number of reader threads per uffd.\n");
224262
printf(" -b: specify the size of the memory region which should be\n"
225263
" demand paged by each vCPU. e.g. 10M or 3G.\n"
226264
" Default: 1G\n");
@@ -239,12 +277,14 @@ int main(int argc, char *argv[])
239277
struct test_params p = {
240278
.src_type = DEFAULT_VM_MEM_SRC,
241279
.partition_vcpu_memory_access = true,
280+
.readers_per_uffd = 1,
281+
.single_uffd = false,
242282
};
243283
int opt;
244284

245285
guest_modes_append_default();
246286

247-
while ((opt = getopt(argc, argv, "hm:u:d:b:s:v:c:o")) != -1) {
287+
while ((opt = getopt(argc, argv, "ahom:u:d:b:s:v:c:r:")) != -1) {
248288
switch (opt) {
249289
case 'm':
250290
guest_modes_cmdline(optarg);
@@ -256,6 +296,9 @@ int main(int argc, char *argv[])
256296
p.uffd_mode = UFFDIO_REGISTER_MODE_MINOR;
257297
TEST_ASSERT(p.uffd_mode, "UFFD mode must be 'MISSING' or 'MINOR'.");
258298
break;
299+
case 'a':
300+
p.single_uffd = true;
301+
break;
259302
case 'd':
260303
p.uffd_delay = strtoul(optarg, NULL, 0);
261304
TEST_ASSERT(p.uffd_delay >= 0, "A negative UFFD delay is not supported.");
@@ -276,6 +319,13 @@ int main(int argc, char *argv[])
276319
break;
277320
case 'o':
278321
p.partition_vcpu_memory_access = false;
322+
p.single_uffd = true;
323+
break;
324+
case 'r':
325+
p.readers_per_uffd = atoi(optarg);
326+
TEST_ASSERT(p.readers_per_uffd >= 1,
327+
"Invalid number of readers per uffd %d: must be >=1",
328+
p.readers_per_uffd);
279329
break;
280330
case 'h':
281331
default:

tools/testing/selftests/kvm/include/userfaultfd_util.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,27 @@
1717

1818
typedef int (*uffd_handler_t)(int uffd_mode, int uffd, struct uffd_msg *msg);
1919

20-
struct uffd_desc {
20+
struct uffd_reader_args {
2121
int uffd_mode;
2222
int uffd;
23-
int pipefds[2];
2423
useconds_t delay;
2524
uffd_handler_t handler;
26-
pthread_t thread;
25+
/* Holds the read end of the pipe for killing the reader. */
26+
int pipe;
27+
};
28+
29+
struct uffd_desc {
30+
int uffd;
31+
uint64_t num_readers;
32+
/* Holds the write ends of the pipes for killing the readers. */
33+
int *pipefds;
34+
pthread_t *readers;
35+
struct uffd_reader_args *reader_args;
2736
};
2837

2938
struct uffd_desc *uffd_setup_demand_paging(int uffd_mode, useconds_t delay,
3039
void *hva, uint64_t len,
40+
uint64_t num_readers,
3141
uffd_handler_t handler);
3242

3343
void uffd_stop_demand_paging(struct uffd_desc *uffd);

tools/testing/selftests/kvm/lib/userfaultfd_util.c

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@
2727

2828
static void *uffd_handler_thread_fn(void *arg)
2929
{
30-
struct uffd_desc *uffd_desc = (struct uffd_desc *)arg;
31-
int uffd = uffd_desc->uffd;
32-
int pipefd = uffd_desc->pipefds[0];
33-
useconds_t delay = uffd_desc->delay;
30+
struct uffd_reader_args *reader_args = (struct uffd_reader_args *)arg;
31+
int uffd = reader_args->uffd;
3432
int64_t pages = 0;
3533
struct timespec start;
3634
struct timespec ts_diff;
@@ -44,7 +42,7 @@ static void *uffd_handler_thread_fn(void *arg)
4442

4543
pollfd[0].fd = uffd;
4644
pollfd[0].events = POLLIN;
47-
pollfd[1].fd = pipefd;
45+
pollfd[1].fd = reader_args->pipe;
4846
pollfd[1].events = POLLIN;
4947

5048
r = poll(pollfd, 2, -1);
@@ -92,9 +90,9 @@ static void *uffd_handler_thread_fn(void *arg)
9290
if (!(msg.event & UFFD_EVENT_PAGEFAULT))
9391
continue;
9492

95-
if (delay)
96-
usleep(delay);
97-
r = uffd_desc->handler(uffd_desc->uffd_mode, uffd, &msg);
93+
if (reader_args->delay)
94+
usleep(reader_args->delay);
95+
r = reader_args->handler(reader_args->uffd_mode, uffd, &msg);
9896
if (r < 0)
9997
return NULL;
10098
pages++;
@@ -110,6 +108,7 @@ static void *uffd_handler_thread_fn(void *arg)
110108

111109
struct uffd_desc *uffd_setup_demand_paging(int uffd_mode, useconds_t delay,
112110
void *hva, uint64_t len,
111+
uint64_t num_readers,
113112
uffd_handler_t handler)
114113
{
115114
struct uffd_desc *uffd_desc;
@@ -118,14 +117,25 @@ struct uffd_desc *uffd_setup_demand_paging(int uffd_mode, useconds_t delay,
118117
struct uffdio_api uffdio_api;
119118
struct uffdio_register uffdio_register;
120119
uint64_t expected_ioctls = ((uint64_t) 1) << _UFFDIO_COPY;
121-
int ret;
120+
int ret, i;
122121

123122
PER_PAGE_DEBUG("Userfaultfd %s mode, faults resolved with %s\n",
124123
is_minor ? "MINOR" : "MISSING",
125124
is_minor ? "UFFDIO_CONINUE" : "UFFDIO_COPY");
126125

127126
uffd_desc = malloc(sizeof(struct uffd_desc));
128-
TEST_ASSERT(uffd_desc, "malloc failed");
127+
TEST_ASSERT(uffd_desc, "Failed to malloc uffd descriptor");
128+
129+
uffd_desc->pipefds = calloc(sizeof(int), num_readers);
130+
TEST_ASSERT(uffd_desc->pipefds, "Failed to alloc pipes");
131+
132+
uffd_desc->readers = calloc(sizeof(pthread_t), num_readers);
133+
TEST_ASSERT(uffd_desc->readers, "Failed to alloc reader threads");
134+
135+
uffd_desc->reader_args = calloc(sizeof(struct uffd_reader_args), num_readers);
136+
TEST_ASSERT(uffd_desc->reader_args, "Failed to alloc reader_args");
137+
138+
uffd_desc->num_readers = num_readers;
129139

130140
/* In order to get minor faults, prefault via the alias. */
131141
if (is_minor)
@@ -148,38 +158,55 @@ struct uffd_desc *uffd_setup_demand_paging(int uffd_mode, useconds_t delay,
148158
TEST_ASSERT((uffdio_register.ioctls & expected_ioctls) ==
149159
expected_ioctls, "missing userfaultfd ioctls");
150160

151-
ret = pipe2(uffd_desc->pipefds, O_CLOEXEC | O_NONBLOCK);
152-
TEST_ASSERT(!ret, "Failed to set up pipefd");
153-
154-
uffd_desc->uffd_mode = uffd_mode;
155161
uffd_desc->uffd = uffd;
156-
uffd_desc->delay = delay;
157-
uffd_desc->handler = handler;
158-
pthread_create(&uffd_desc->thread, NULL, uffd_handler_thread_fn,
159-
uffd_desc);
162+
for (i = 0; i < uffd_desc->num_readers; ++i) {
163+
int pipes[2];
164+
165+
ret = pipe2((int *) &pipes, O_CLOEXEC | O_NONBLOCK);
166+
TEST_ASSERT(!ret, "Failed to set up pipefd %i for uffd_desc %p",
167+
i, uffd_desc);
160168

161-
PER_VCPU_DEBUG("Created uffd thread for HVA range [%p, %p)\n",
162-
hva, hva + len);
169+
uffd_desc->pipefds[i] = pipes[1];
170+
171+
uffd_desc->reader_args[i].uffd_mode = uffd_mode;
172+
uffd_desc->reader_args[i].uffd = uffd;
173+
uffd_desc->reader_args[i].delay = delay;
174+
uffd_desc->reader_args[i].handler = handler;
175+
uffd_desc->reader_args[i].pipe = pipes[0];
176+
177+
pthread_create(&uffd_desc->readers[i], NULL, uffd_handler_thread_fn,
178+
&uffd_desc->reader_args[i]);
179+
180+
PER_VCPU_DEBUG("Created uffd thread %i for HVA range [%p, %p)\n",
181+
i, hva, hva + len);
182+
}
163183

164184
return uffd_desc;
165185
}
166186

167187
void uffd_stop_demand_paging(struct uffd_desc *uffd)
168188
{
169189
char c = 0;
170-
int ret;
190+
int i;
171191

172-
ret = write(uffd->pipefds[1], &c, 1);
173-
TEST_ASSERT(ret == 1, "Unable to write to pipefd");
192+
for (i = 0; i < uffd->num_readers; ++i)
193+
TEST_ASSERT(write(uffd->pipefds[i], &c, 1) == 1,
194+
"Unable to write to pipefd %i for uffd_desc %p", i, uffd);
174195

175-
ret = pthread_join(uffd->thread, NULL);
176-
TEST_ASSERT(ret == 0, "Pthread_join failed.");
196+
for (i = 0; i < uffd->num_readers; ++i)
197+
TEST_ASSERT(!pthread_join(uffd->readers[i], NULL),
198+
"Pthread_join failed on reader %i for uffd_desc %p", i, uffd);
177199

178200
close(uffd->uffd);
179201

180-
close(uffd->pipefds[1]);
181-
close(uffd->pipefds[0]);
202+
for (i = 0; i < uffd->num_readers; ++i) {
203+
close(uffd->pipefds[i]);
204+
close(uffd->reader_args[i].pipe);
205+
}
182206

207+
free(uffd->pipefds);
208+
free(uffd->readers);
209+
free(uffd->reader_args);
183210
free(uffd);
184211
}
185212

0 commit comments

Comments
 (0)